From 263d085885df024dca9250cc03c807912b0a7662 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 24 Apr 2012 22:33:21 +0000
Subject: [PATCH] Reformat to comply with new Checkstyle rules.

---
 opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java | 1787 ++++++++++++++++++++++++++---------------------------------
 1 files changed, 788 insertions(+), 999 deletions(-)

diff --git a/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java b/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
index 1988d9d..3659b85 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
+++ b/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
@@ -6,17 +6,16 @@
  * (the "License").  You may not use this file except in compliance
  * with the License.
  *
- * You can obtain a copy of the license at
- * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
  * or http://forgerock.org/license/CDDLv1.0.html.
  * See the License for the specific language governing permissions
  * and limitations under the License.
  *
  * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying information:
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
  *      Portions Copyright [yyyy] [name of copyright owner]
  *
  * CDDL HEADER END
@@ -28,8 +27,6 @@
 
 package com.forgerock.opendj.ldap;
 
-
-
 import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
 
 import java.io.IOException;
@@ -44,9 +41,35 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 
-import org.forgerock.opendj.ldap.*;
-import org.forgerock.opendj.ldap.requests.*;
-import org.forgerock.opendj.ldap.responses.*;
+import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.FutureResult;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.ResultHandler;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindClient;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.GenericBindRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Responses;
+import org.forgerock.opendj.ldap.responses.Result;
 import org.glassfish.grizzly.CompletionHandler;
 import org.glassfish.grizzly.filterchain.Filter;
 import org.glassfish.grizzly.filterchain.FilterChain;
@@ -58,1041 +81,807 @@
 import com.forgerock.opendj.util.StaticUtils;
 import com.forgerock.opendj.util.Validator;
 
-
-
 /**
  * LDAP connection implementation.
  * <p>
  * TODO: handle illegal state exceptions.
  */
-final class LDAPConnection extends AbstractAsynchronousConnection implements
-    Connection
-{
-  private final org.glassfish.grizzly.Connection<?> connection;
-  private Result connectionInvalidReason;
-  private boolean isClosed = false;
-  private final List<ConnectionEventListener> listeners =
-      new CopyOnWriteArrayList<ConnectionEventListener>();
-  private final AtomicInteger nextMsgID = new AtomicInteger(1);
-  private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(
-      false);
-  private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
-      new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
-  private final Object stateLock = new Object();
-  private final LDAPWriter ldapWriter = new LDAPWriter();
-  private final LDAPOptions options;
+final class LDAPConnection extends AbstractAsynchronousConnection implements Connection {
+    private final org.glassfish.grizzly.Connection<?> connection;
+    private Result connectionInvalidReason;
+    private boolean isClosed = false;
+    private final List<ConnectionEventListener> listeners =
+            new CopyOnWriteArrayList<ConnectionEventListener>();
+    private final AtomicInteger nextMsgID = new AtomicInteger(1);
+    private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
+    private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
+            new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
+    private final Object stateLock = new Object();
+    private final LDAPWriter ldapWriter = new LDAPWriter();
+    private final LDAPOptions options;
 
-
-
-  /**
-   * Creates a new LDAP connection.
-   *
-   * @param connection
-   *          The Grizzly connection.
-   * @param options
-   *          The LDAP client options.
-   */
-  LDAPConnection(final org.glassfish.grizzly.Connection<?> connection,
-      final LDAPOptions options)
-  {
-    this.connection = connection;
-    this.options = options;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Void> abandonAsync(final AbandonRequest request)
-  {
-    final AbstractLDAPFutureResultImpl<?> pendingRequest;
-    final int messageID = nextMsgID.getAndIncrement();
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        return new CompletedFutureResult<Void>(
-            newErrorResult(connectionInvalidReason), messageID);
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        final Result errorResult = Responses.newResult(
-            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-            "Bind or Start TLS operation in progress");
-        return new CompletedFutureResult<Void>(newErrorResult(errorResult),
-            messageID);
-      }
-
-      // First remove the future associated with the request to be abandoned.
-      pendingRequest = pendingRequests.remove(request.getRequestID());
+    /**
+     * Creates a new LDAP connection.
+     *
+     * @param connection
+     *            The Grizzly connection.
+     * @param options
+     *            The LDAP client options.
+     */
+    LDAPConnection(final org.glassfish.grizzly.Connection<?> connection, final LDAPOptions options) {
+        this.connection = connection;
+        this.options = options;
     }
 
-    if (pendingRequest == null)
-    {
-      // There has never been a request with the specified message ID or the
-      // response has already been received and handled. We can ignore this
-      // abandon request.
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Void> abandonAsync(final AbandonRequest request) {
+        final AbstractLDAPFutureResultImpl<?> pendingRequest;
+        final int messageID = nextMsgID.getAndIncrement();
 
-      // Message ID will be -1 since no request was sent.
-      return new CompletedFutureResult<Void>((Void) null);
-    }
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                return new CompletedFutureResult<Void>(newErrorResult(connectionInvalidReason),
+                        messageID);
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                final Result errorResult =
+                        Responses.newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                                "Bind or Start TLS operation in progress");
+                return new CompletedFutureResult<Void>(newErrorResult(errorResult), messageID);
+            }
 
-    pendingRequest.cancel(false);
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.abandonRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-        return new CompletedFutureResult<Void>((Void) null, messageID);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      return new CompletedFutureResult<Void>(newErrorResult(errorResult),
-          messageID);
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> addAsync(final AddRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super Result> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID,
-        request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses
-            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-                "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.addRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public void addConnectionEventListener(final ConnectionEventListener listener)
-  {
-    Validator.ensureNotNull(listener);
-    listeners.add(listener);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<BindResult> bindAsync(final BindRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super BindResult> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-
-    BindClient context;
-    try
-    {
-      context = request
-          .createBindClient(connection.getPeerAddress() instanceof InetSocketAddress ?
-              ((InetSocketAddress) connection.getPeerAddress()).getHostName()
-              : connection.getPeerAddress().toString());
-    }
-    catch (final Exception e)
-    {
-      // FIXME: I18N need to have a better error message.
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses
-          .newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-          .setDiagnosticMessage(
-              "An error occurred while creating a bind context").setCause(e);
-      final ErrorResultException error = ErrorResultException
-          .newErrorResult(errorResult);
-      if (resultHandler != null)
-      {
-        resultHandler.handleErrorResult(error);
-      }
-      return new CompletedFutureResult<BindResult>(error, messageID);
-    }
-
-    final LDAPBindFutureResultImpl future = new LDAPBindFutureResultImpl(
-        messageID, context, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (!pendingRequests.isEmpty())
-      {
-        future.setResultOrError(Responses.newBindResult(
-            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-            "There are other operations pending on this connection"));
-        return future;
-      }
-
-      if (!bindOrStartTLSInProgress.compareAndSet(false, true))
-      {
-        future.setResultOrError(Responses.newBindResult(
-            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-            "Bind or Start TLS operation in progress"));
-        return future;
-      }
-
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        // Use the bind client to get the initial request instead of using the
-        // bind request passed to this method.
-        final GenericBindRequest initialRequest = context.nextBindRequest();
-        ldapWriter.bindRequest(asn1Writer, messageID, 3, initialRequest);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      bindOrStartTLSInProgress.set(false);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public void close(final UnbindRequest request, final String reason)
-  {
-    // FIXME: I18N need to internationalize this message.
-    Validator.ensureNotNull(request);
-
-    close(
-        request,
-        false,
-        Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
-            .setDiagnosticMessage(
-                "Connection closed by client"
-                    + (reason != null ? ": " + reason : "")));
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<CompareResult> compareAsync(final CompareRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super CompareResult> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPCompareFutureResultImpl future = new LDAPCompareFutureResultImpl(
-        messageID, request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses.newCompareResult(
-            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-            "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.compareRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> deleteAsync(final DeleteRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super Result> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID,
-        request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses
-            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-                "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.deleteRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
-      final ExtendedRequest<R> request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super R> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPExtendedFutureResultImpl<R> future = new LDAPExtendedFutureResultImpl<R>(
-        messageID, request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (request.getOID().equals(StartTLSExtendedRequest.OID))
-      {
-        if (!pendingRequests.isEmpty())
-        {
-          future.setResultOrError(request.getResultDecoder()
-              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
-                  "There are pending operations on this connection"));
-          return future;
+            // First remove the future associated with the request to be
+            // abandoned.
+            pendingRequest = pendingRequests.remove(request.getRequestID());
         }
-        if (isTLSEnabled())
-        {
-          future.setResultOrError(request.getResultDecoder()
-              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
-                  "This connection is already TLS enabled"));
-          return future;
+
+        if (pendingRequest == null) {
+            // There has never been a request with the specified message ID or
+            // the
+            // response has already been received and handled. We can ignore
+            // this
+            // abandon request.
+
+            // Message ID will be -1 since no request was sent.
+            return new CompletedFutureResult<Void>((Void) null);
         }
-        if (!bindOrStartTLSInProgress.compareAndSet(false, true))
-        {
-          future.setResultOrError(request.getResultDecoder()
-              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
-                  "Bind or Start TLS operation in progress"));
-          return future;
+
+        pendingRequest.cancel(false);
+
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.abandonRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+                return new CompletedFutureResult<Void>((Void) null, messageID);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            return new CompletedFutureResult<Void>(newErrorResult(errorResult), messageID);
         }
-      }
-      else
-      {
-        if (bindOrStartTLSInProgress.get())
-        {
-          future.setResultOrError(request.getResultDecoder()
-              .newExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
-                  "Bind or Start TLS operation in progress"));
-          return future;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Result> addAsync(final AddRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super Result> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPFutureResultImpl future =
+                new LDAPFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
+
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
         }
-      }
-      pendingRequests.put(messageID, future);
-    }
 
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.extendedRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-      bindOrStartTLSInProgress.set(false);
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.addRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
 
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
 
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public boolean isClosed()
-  {
-    synchronized (stateLock)
-    {
-      return isClosed;
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public boolean isValid()
-  {
-    synchronized (stateLock)
-    {
-      return connectionInvalidReason == null && !isClosed;
-    }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> modifyAsync(final ModifyRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super Result> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID,
-        request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
         return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses
-            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-                "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
     }
 
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.modifyRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
+    /**
+     * {@inheritDoc}
+     */
+    public void addConnectionEventListener(final ConnectionEventListener listener) {
+        Validator.ensureNotNull(listener);
+        listeners.add(listener);
     }
 
-    return future;
-  }
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<BindResult> bindAsync(final BindRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super BindResult> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final ResultHandler<? super Result> resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID,
-        request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses
-            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-                "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.modifyDNRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public void removeConnectionEventListener(
-      final ConnectionEventListener listener)
-  {
-    Validator.ensureNotNull(listener);
-    listeners.remove(listener);
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> searchAsync(final SearchRequest request,
-      final IntermediateResponseHandler intermediateResponseHandler,
-      final SearchResultHandler resultHandler)
-  {
-    final int messageID = nextMsgID.getAndIncrement();
-    final LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl(
-        messageID, request, resultHandler, intermediateResponseHandler, this);
-
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-        return future;
-      }
-      if (bindOrStartTLSInProgress.get())
-      {
-        future.setResultOrError(Responses
-            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-                "Bind or Start TLS operation in progress"));
-        return future;
-      }
-      pendingRequests.put(messageID, future);
-    }
-
-    try
-    {
-      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-      try
-      {
-        ldapWriter.searchRequest(asn1Writer, messageID, request);
-        connection.write(asn1Writer.getBuffer(), null);
-      }
-      finally
-      {
-        asn1Writer.recycle();
-      }
-    }
-    catch (final IOException e)
-    {
-      pendingRequests.remove(messageID);
-
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      final Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      future.adaptErrorResult(errorResult);
-    }
-
-    return future;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public String toString()
-  {
-    StringBuilder builder = new StringBuilder();
-    builder.append("LDAPConnection(");
-    builder.append(connection.getLocalAddress());
-    builder.append(',');
-    builder.append(connection.getPeerAddress());
-    builder.append(')');
-    return builder.toString();
-  }
-
-
-
-  int continuePendingBindRequest(final LDAPBindFutureResultImpl future)
-      throws ErrorResultException
-  {
-    final int newMsgID = nextMsgID.getAndIncrement();
-    synchronized (stateLock)
-    {
-      if (connectionInvalidReason != null)
-      {
-        throw newErrorResult(connectionInvalidReason);
-      }
-      pendingRequests.put(newMsgID, future);
-    }
-    return newMsgID;
-  }
-
-
-
-  long cancelExpiredRequests(final long currentTime)
-  {
-    final long timeout = options.getTimeout(TimeUnit.MILLISECONDS);
-    long delay = timeout;
-    if (timeout > 0)
-    {
-      for (int requestID : pendingRequests.keySet())
-      {
-        final AbstractLDAPFutureResultImpl<?> future = pendingRequests
-            .get(requestID);
-        if (future != null)
-        {
-          final long diff = (future.getTimestamp() + timeout) - currentTime;
-          if (diff <= 0 && pendingRequests.remove(requestID) != null)
-          {
-            StaticUtils.DEBUG_LOG.fine("Cancelling expired future result: "
-                + future);
-            final Result result = Responses
-                .newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
-            future.adaptErrorResult(result);
-
-            abandonAsync(Requests.newAbandonRequest(future.getRequestID()));
-          }
-          else
-          {
-            delay = Math.min(delay, diff);
-          }
+        BindClient context;
+        try {
+            context =
+                    request.createBindClient(
+                            connection.getPeerAddress() instanceof InetSocketAddress
+                            ? ((InetSocketAddress) connection.getPeerAddress()).getHostName()
+                            : connection.getPeerAddress().toString());
+        } catch (final Exception e) {
+            // FIXME: I18N need to have a better error message.
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+                            "An error occurred while creating a bind context").setCause(e);
+            final ErrorResultException error = ErrorResultException.newErrorResult(errorResult);
+            if (resultHandler != null) {
+                resultHandler.handleErrorResult(error);
+            }
+            return new CompletedFutureResult<BindResult>(error, messageID);
         }
-      }
-    }
-    return delay;
-  }
 
+        final LDAPBindFutureResultImpl future =
+                new LDAPBindFutureResultImpl(messageID, context, resultHandler,
+                        intermediateResponseHandler, this);
 
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (!pendingRequests.isEmpty()) {
+                future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage(
+                                "There are other operations pending on this connection"));
+                return future;
+            }
 
-  void close(final UnbindRequest unbindRequest,
-      final boolean isDisconnectNotification, final Result reason)
-  {
-    boolean notifyClose = false;
-    boolean notifyErrorOccurred = false;
+            if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
+                future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
 
-    synchronized (stateLock)
-    {
-      if (isClosed)
-      {
-        // Already closed.
-        return;
-      }
-
-      if (connectionInvalidReason != null)
-      {
-        // Already closed.
-        isClosed = true;
-        return;
-      }
-
-      if (unbindRequest != null)
-      {
-        // User closed.
-        isClosed = true;
-        notifyClose = true;
-      }
-      else
-      {
-        notifyErrorOccurred = true;
-      }
-
-      // Mark the connection as invalid.
-      if (!isDisconnectNotification)
-      {
-        // Connection termination was detected locally, so use the provided
-        // reason for all subsequent requests.
-        connectionInvalidReason = reason;
-      }
-      else
-      {
-        // Connection termination was triggered remotely. We don't want to
-        // blindly pass on the result code to requests since it could be
-        // confused for a genuine response. For example, if the disconnect
-        // contained the invalidCredentials result code then this could be
-        // misinterpreted as a genuine authentication failure for subsequent
-        // bind requests.
-        connectionInvalidReason = Responses.newResult(
-            ResultCode.CLIENT_SIDE_SERVER_DOWN).setDiagnosticMessage(
-            "Connection closed by server");
-      }
-    }
-
-    // First abort all outstanding requests.
-    for (int requestID : pendingRequests.keySet())
-    {
-      final AbstractLDAPFutureResultImpl<?> future = pendingRequests
-          .remove(requestID);
-      if (future != null)
-      {
-        future.adaptErrorResult(connectionInvalidReason);
-      }
-    }
-
-    // Now try cleanly closing the connection if possible.
-    // Only send unbind if specified.
-    if (unbindRequest != null && !isDisconnectNotification)
-    {
-      try
-      {
-        final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
-        try
-        {
-          ldapWriter.unbindRequest(asn1Writer, nextMsgID.getAndIncrement(),
-              unbindRequest);
-          connection.write(asn1Writer.getBuffer(), null);
+            pendingRequests.put(messageID, future);
         }
-        finally
-        {
-          asn1Writer.recycle();
+
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                // Use the bind client to get the initial request instead of
+                // using the
+                // bind request passed to this method.
+                final GenericBindRequest initialRequest = context.nextBindRequest();
+                ldapWriter.bindRequest(asn1Writer, messageID, 3, initialRequest);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
+
+            bindOrStartTLSInProgress.set(false);
+
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
         }
-      }
-      catch (final IOException e)
-      {
-        // Underlying channel prob blown up. Just ignore.
-      }
+
+        return future;
     }
 
-    try
-    {
-      connection.close();
-    }
-    catch (final IOException e)
-    {
-      // Ignore.
+    /**
+     * {@inheritDoc}
+     */
+    public void close(final UnbindRequest request, final String reason) {
+        // FIXME: I18N need to internationalize this message.
+        Validator.ensureNotNull(request);
+
+        close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
+                .setDiagnosticMessage(
+                        "Connection closed by client" + (reason != null ? ": " + reason : "")));
     }
 
-    // Notify listeners.
-    if (notifyClose)
-    {
-      for (final ConnectionEventListener listener : listeners)
-      {
-        listener.handleConnectionClosed();
-      }
-    }
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<CompareResult> compareAsync(final CompareRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super CompareResult> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPCompareFutureResultImpl future =
+                new LDAPCompareFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
 
-    if (notifyErrorOccurred)
-    {
-      for (final ConnectionEventListener listener : listeners)
-      {
-        // Use the reason provided in the disconnect notification.
-        listener.handleConnectionError(isDisconnectNotification,
-            newErrorResult(reason));
-      }
-    }
-  }
-
-
-
-  LDAPOptions getLDAPOptions()
-  {
-    return options;
-  }
-
-
-
-  AbstractLDAPFutureResultImpl<?> getPendingRequest(final Integer messageID)
-  {
-    return pendingRequests.get(messageID);
-  }
-
-
-
-  void handleUnsolicitedNotification(final ExtendedResult result)
-  {
-    if (isClosed())
-    {
-      // Don't notify after connection is closed.
-      return;
-    }
-
-    for (final ConnectionEventListener listener : listeners)
-    {
-      listener.handleUnsolicitedNotification(result);
-    }
-  }
-
-
-
-  /**
-   * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP
-   * filter.
-   *
-   * @param filter
-   *          The filter to be installed.
-   */
-  void installFilter(final Filter filter)
-  {
-    synchronized (stateLock)
-    {
-      // Determine the index where the filter should be added.
-      FilterChain oldFilterChain = (FilterChain) connection.getProcessor();
-      int filterIndex = oldFilterChain.size() - 1;
-      if (filter instanceof SSLFilter)
-      {
-        // Beneath any ConnectionSecurityLayerFilters if present, otherwise
-        // beneath the LDAP filter.
-        for (int i = oldFilterChain.size() - 2; i >= 0; i--)
-        {
-          if (!(oldFilterChain.get(i) instanceof ConnectionSecurityLayerFilter))
-          {
-            filterIndex = i + 1;
-            break;
-          }
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newCompareResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
         }
-      }
 
-      // Create the new filter chain.
-      FilterChain newFilterChain = FilterChainBuilder.stateless()
-          .addAll(oldFilterChain)
-          .add(filterIndex, filter)
-          .build();
-      connection.setProcessor(newFilterChain);
-    }
-  }
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.compareRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
 
-
-
-  /**
-   * Indicates whether or not TLS is enabled on this connection.
-   *
-   * @return {@code true} if TLS is enabled on this connection, otherwise
-   *         {@code false}.
-   */
-  boolean isTLSEnabled()
-  {
-    synchronized (stateLock)
-    {
-      final FilterChain currentFilterChain = (FilterChain) connection
-          .getProcessor();
-      for (Filter filter : currentFilterChain)
-      {
-        if (filter instanceof SSLFilter)
-        {
-          return true;
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
         }
-      }
-      return false;
+
+        return future;
     }
-  }
 
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Result> deleteAsync(final DeleteRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super Result> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPFutureResultImpl future =
+                new LDAPFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
 
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
+        }
 
-  AbstractLDAPFutureResultImpl<?> removePendingRequest(final Integer messageID)
-  {
-    return pendingRequests.remove(messageID);
-  }
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.deleteRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
 
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
 
-
-  void setBindOrStartTLSInProgress(final boolean state)
-  {
-    bindOrStartTLSInProgress.set(state);
-  }
-
-
-
-  void startTLS(final SSLContext sslContext,
-      final List<String> protocols, final List<String> cipherSuites,
-      final CompletionHandler<SSLEngine> completionHandler) throws IOException
-  {
-    synchronized (stateLock)
-    {
-      if (isTLSEnabled())
-      {
-        throw new IllegalStateException("TLS already enabled");
-      }
-
-      SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(
-          sslContext, true, false, false);
-      sslEngineConfigurator.setEnabledProtocols(protocols.isEmpty() ? null
-          : protocols.toArray(new String[protocols.size()]));
-      sslEngineConfigurator
-          .setEnabledCipherSuites(cipherSuites.isEmpty() ? null : cipherSuites
-              .toArray(new String[cipherSuites.size()]));
-      SSLFilter sslFilter = new SSLFilter(null, sslEngineConfigurator);
-      installFilter(sslFilter);
-      sslFilter.handshake(connection, completionHandler);
+        return future;
     }
-  }
 
+    /**
+     * {@inheritDoc}
+     */
+    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
+            final ExtendedRequest<R> request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super R> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPExtendedFutureResultImpl<R> future =
+                new LDAPExtendedFutureResultImpl<R>(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
 
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
+                if (!pendingRequests.isEmpty()) {
+                    future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                            ResultCode.OPERATIONS_ERROR, "",
+                            "There are pending operations on this connection"));
+                    return future;
+                }
+                if (isTLSEnabled()) {
+                    future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                            ResultCode.OPERATIONS_ERROR, "",
+                            "This connection is already TLS enabled"));
+                    return future;
+                }
+                if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
+                    future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                            ResultCode.OPERATIONS_ERROR, "",
+                            "Bind or Start TLS operation in progress"));
+                    return future;
+                }
+            } else {
+                if (bindOrStartTLSInProgress.get()) {
+                    future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                            ResultCode.OPERATIONS_ERROR, "",
+                            "Bind or Start TLS operation in progress"));
+                    return future;
+                }
+            }
+            pendingRequests.put(messageID, future);
+        }
 
-  private void connectionErrorOccurred(final Result reason)
-  {
-    close(null, false, reason);
-  }
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.extendedRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
+            bindOrStartTLSInProgress.set(false);
+
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
+
+        return future;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isClosed() {
+        synchronized (stateLock) {
+            return isClosed;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isValid() {
+        synchronized (stateLock) {
+            return connectionInvalidReason == null && !isClosed;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Result> modifyAsync(final ModifyRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super Result> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPFutureResultImpl future =
+                new LDAPFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
+
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
+        }
+
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.modifyRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
+
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
+
+        return future;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final ResultHandler<? super Result> resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPFutureResultImpl future =
+                new LDAPFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
+
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
+        }
+
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.modifyDNRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
+
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
+
+        return future;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeConnectionEventListener(final ConnectionEventListener listener) {
+        Validator.ensureNotNull(listener);
+        listeners.remove(listener);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureResult<Result> searchAsync(final SearchRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler,
+            final SearchResultHandler resultHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final LDAPSearchFutureResultImpl future =
+                new LDAPSearchFutureResultImpl(messageID, request, resultHandler,
+                        intermediateResponseHandler, this);
+
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+                return future;
+            }
+            if (bindOrStartTLSInProgress.get()) {
+                future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                        .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+                return future;
+            }
+            pendingRequests.put(messageID, future);
+        }
+
+        try {
+            final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+            try {
+                ldapWriter.searchRequest(asn1Writer, messageID, request);
+                connection.write(asn1Writer.getBuffer(), null);
+            } finally {
+                asn1Writer.recycle();
+            }
+        } catch (final IOException e) {
+            pendingRequests.remove(messageID);
+
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            final Result errorResult =
+                    Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
+            connectionErrorOccurred(errorResult);
+            future.adaptErrorResult(errorResult);
+        }
+
+        return future;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("LDAPConnection(");
+        builder.append(connection.getLocalAddress());
+        builder.append(',');
+        builder.append(connection.getPeerAddress());
+        builder.append(')');
+        return builder.toString();
+    }
+
+    int continuePendingBindRequest(final LDAPBindFutureResultImpl future)
+            throws ErrorResultException {
+        final int newMsgID = nextMsgID.getAndIncrement();
+        synchronized (stateLock) {
+            if (connectionInvalidReason != null) {
+                throw newErrorResult(connectionInvalidReason);
+            }
+            pendingRequests.put(newMsgID, future);
+        }
+        return newMsgID;
+    }
+
+    long cancelExpiredRequests(final long currentTime) {
+        final long timeout = options.getTimeout(TimeUnit.MILLISECONDS);
+        long delay = timeout;
+        if (timeout > 0) {
+            for (int requestID : pendingRequests.keySet()) {
+                final AbstractLDAPFutureResultImpl<?> future = pendingRequests.get(requestID);
+                if (future != null) {
+                    final long diff = (future.getTimestamp() + timeout) - currentTime;
+                    if (diff <= 0 && pendingRequests.remove(requestID) != null) {
+                        StaticUtils.DEBUG_LOG.fine("Cancelling expired future result: " + future);
+                        final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
+                        future.adaptErrorResult(result);
+
+                        abandonAsync(Requests.newAbandonRequest(future.getRequestID()));
+                    } else {
+                        delay = Math.min(delay, diff);
+                    }
+                }
+            }
+        }
+        return delay;
+    }
+
+    void close(final UnbindRequest unbindRequest, final boolean isDisconnectNotification,
+            final Result reason) {
+        boolean notifyClose = false;
+        boolean notifyErrorOccurred = false;
+
+        synchronized (stateLock) {
+            if (isClosed) {
+                // Already closed.
+                return;
+            }
+
+            if (connectionInvalidReason != null) {
+                // Already closed.
+                isClosed = true;
+                return;
+            }
+
+            if (unbindRequest != null) {
+                // User closed.
+                isClosed = true;
+                notifyClose = true;
+            } else {
+                notifyErrorOccurred = true;
+            }
+
+            // Mark the connection as invalid.
+            if (!isDisconnectNotification) {
+                // Connection termination was detected locally, so use the
+                // provided
+                // reason for all subsequent requests.
+                connectionInvalidReason = reason;
+            } else {
+                // Connection termination was triggered remotely. We don't want
+                // to
+                // blindly pass on the result code to requests since it could be
+                // confused for a genuine response. For example, if the
+                // disconnect
+                // contained the invalidCredentials result code then this could
+                // be
+                // misinterpreted as a genuine authentication failure for
+                // subsequent
+                // bind requests.
+                connectionInvalidReason =
+                        Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN)
+                                .setDiagnosticMessage("Connection closed by server");
+            }
+        }
+
+        // First abort all outstanding requests.
+        for (int requestID : pendingRequests.keySet()) {
+            final AbstractLDAPFutureResultImpl<?> future = pendingRequests.remove(requestID);
+            if (future != null) {
+                future.adaptErrorResult(connectionInvalidReason);
+            }
+        }
+
+        // Now try cleanly closing the connection if possible.
+        // Only send unbind if specified.
+        if (unbindRequest != null && !isDisconnectNotification) {
+            try {
+                final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+                try {
+                    ldapWriter
+                            .unbindRequest(asn1Writer, nextMsgID.getAndIncrement(), unbindRequest);
+                    connection.write(asn1Writer.getBuffer(), null);
+                } finally {
+                    asn1Writer.recycle();
+                }
+            } catch (final IOException e) {
+                // Underlying channel prob blown up. Just ignore.
+            }
+        }
+
+        try {
+            connection.close();
+        } catch (final IOException e) {
+            // Ignore.
+        }
+
+        // Notify listeners.
+        if (notifyClose) {
+            for (final ConnectionEventListener listener : listeners) {
+                listener.handleConnectionClosed();
+            }
+        }
+
+        if (notifyErrorOccurred) {
+            for (final ConnectionEventListener listener : listeners) {
+                // Use the reason provided in the disconnect notification.
+                listener.handleConnectionError(isDisconnectNotification, newErrorResult(reason));
+            }
+        }
+    }
+
+    LDAPOptions getLDAPOptions() {
+        return options;
+    }
+
+    AbstractLDAPFutureResultImpl<?> getPendingRequest(final Integer messageID) {
+        return pendingRequests.get(messageID);
+    }
+
+    void handleUnsolicitedNotification(final ExtendedResult result) {
+        if (isClosed()) {
+            // Don't notify after connection is closed.
+            return;
+        }
+
+        for (final ConnectionEventListener listener : listeners) {
+            listener.handleUnsolicitedNotification(result);
+        }
+    }
+
+    /**
+     * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP
+     * filter.
+     *
+     * @param filter
+     *            The filter to be installed.
+     */
+    void installFilter(final Filter filter) {
+        synchronized (stateLock) {
+            // Determine the index where the filter should be added.
+            FilterChain oldFilterChain = (FilterChain) connection.getProcessor();
+            int filterIndex = oldFilterChain.size() - 1;
+            if (filter instanceof SSLFilter) {
+                // Beneath any ConnectionSecurityLayerFilters if present,
+                // otherwise
+                // beneath the LDAP filter.
+                for (int i = oldFilterChain.size() - 2; i >= 0; i--) {
+                    if (!(oldFilterChain.get(i) instanceof ConnectionSecurityLayerFilter)) {
+                        filterIndex = i + 1;
+                        break;
+                    }
+                }
+            }
+
+            // Create the new filter chain.
+            FilterChain newFilterChain =
+                    FilterChainBuilder.stateless().addAll(oldFilterChain).add(filterIndex, filter)
+                            .build();
+            connection.setProcessor(newFilterChain);
+        }
+    }
+
+    /**
+     * Indicates whether or not TLS is enabled on this connection.
+     *
+     * @return {@code true} if TLS is enabled on this connection, otherwise
+     *         {@code false}.
+     */
+    boolean isTLSEnabled() {
+        synchronized (stateLock) {
+            final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
+            for (Filter filter : currentFilterChain) {
+                if (filter instanceof SSLFilter) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    AbstractLDAPFutureResultImpl<?> removePendingRequest(final Integer messageID) {
+        return pendingRequests.remove(messageID);
+    }
+
+    void setBindOrStartTLSInProgress(final boolean state) {
+        bindOrStartTLSInProgress.set(state);
+    }
+
+    void startTLS(final SSLContext sslContext, final List<String> protocols,
+            final List<String> cipherSuites, final CompletionHandler<SSLEngine> completionHandler)
+            throws IOException {
+        synchronized (stateLock) {
+            if (isTLSEnabled()) {
+                throw new IllegalStateException("TLS already enabled");
+            }
+
+            SSLEngineConfigurator sslEngineConfigurator =
+                    new SSLEngineConfigurator(sslContext, true, false, false);
+            sslEngineConfigurator.setEnabledProtocols(protocols.isEmpty() ? null : protocols
+                    .toArray(new String[protocols.size()]));
+            sslEngineConfigurator.setEnabledCipherSuites(cipherSuites.isEmpty() ? null
+                    : cipherSuites.toArray(new String[cipherSuites.size()]));
+            SSLFilter sslFilter = new SSLFilter(null, sslEngineConfigurator);
+            installFilter(sslFilter);
+            sslFilter.handshake(connection, completionHandler);
+        }
+    }
+
+    private void connectionErrorOccurred(final Result reason) {
+        close(null, false, reason);
+    }
 }

--
Gitblit v1.10.0