From f2160f4bd1c8ac67e5a86a6710d431e8932877f9 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 28 May 2010 11:47:51 +0000
Subject: [PATCH] Synchronize SDK on java.net with internal repository.

---
 sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java | 1898 +++++++++++++++++++---------------------------------------
 1 files changed, 630 insertions(+), 1,268 deletions(-)

diff --git a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
index 4f6c80e..d3bed34 100644
--- a/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
+++ b/sdk/src/com/sun/opends/sdk/ldap/LDAPConnection.java
@@ -29,37 +29,29 @@
 
 
 
-import static com.sun.opends.sdk.ldap.LDAPConstants.*;
-import org.opends.sdk.ldap.LDAPEncoder;
-import org.opends.sdk.ldap.ResolvedSchema;
-
-import java.io.EOFException;
 import java.io.IOException;
-import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLContext;
-import javax.security.sasl.SaslException;
+import javax.net.ssl.SSLEngine;
 
 import org.opends.sdk.*;
-import org.opends.sdk.controls.Control;
-import org.opends.sdk.controls.ControlDecoder;
-import org.opends.sdk.extensions.StartTLSRequest;
 import org.opends.sdk.requests.*;
 import org.opends.sdk.responses.*;
-import org.opends.sdk.sasl.SASLBindRequest;
-import org.opends.sdk.sasl.SASLContext;
-import org.opends.sdk.schema.Schema;
 
+import com.sun.grizzly.CompletionHandler;
+import com.sun.grizzly.filterchain.DefaultFilterChain;
 import com.sun.grizzly.filterchain.Filter;
 import com.sun.grizzly.filterchain.FilterChain;
-import com.sun.grizzly.filterchain.StreamTransformerFilter;
-import com.sun.grizzly.ssl.*;
-import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.ssl.SSLEngineConfigurator;
+import com.sun.grizzly.ssl.SSLFilter;
+import com.sun.opends.sdk.util.CompletedFutureResult;
+import com.sun.opends.sdk.util.StaticUtils;
 import com.sun.opends.sdk.util.Validator;
 
 
@@ -69,625 +61,48 @@
  * <p>
  * TODO: handle illegal state exceptions.
  */
-public final class LDAPConnection extends
-    AbstractAsynchronousConnection implements AsynchronousConnection
+final class LDAPConnection extends AbstractAsynchronousConnection implements
+    AsynchronousConnection
 {
-
-  private final class LDAPMessageHandlerImpl extends
-      AbstractLDAPMessageHandler
-  {
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleAddResult(int messageID, Result result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPFutureResultImpl)
-        {
-          LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest;
-          if (future.getRequest() instanceof AddRequest)
-          {
-            future.setResultOrError(result);
-            return;
-          }
-        }
-        handleIncorrectResponse(pendingRequest);
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleBindResult(int messageID, BindResult result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPBindFutureResultImpl)
-        {
-          LDAPBindFutureResultImpl future = ((LDAPBindFutureResultImpl) pendingRequest);
-          BindRequest request = future.getRequest();
-
-          if (request instanceof SASLBindRequest<?>)
-          {
-            SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
-            SASLContext saslContext = future.getSASLContext();
-
-            if ((result.getResultCode() == ResultCode.SUCCESS || result
-                .getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
-                && !saslContext.isComplete())
-            {
-              try
-              {
-                saslContext.evaluateCredentials(result
-                    .getServerSASLCredentials());
-              }
-              catch (SaslException e)
-              {
-                pendingBindOrStartTLS = -1;
-
-                // FIXME: I18N need to have a better error message.
-                // FIXME: Is this the best result code?
-                Result errorResult = Responses.newResult(
-                    ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-                    .setDiagnosticMessage(
-                        "An error occurred during SASL authentication")
-                    .setCause(e);
-                future.adaptErrorResult(errorResult);
-                return;
-              }
-            }
-
-            if (result.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
-            {
-              // The server is expecting a multi stage bind response.
-              messageID = nextMsgID.getAndIncrement();
-              ASN1StreamWriter asn1Writer = connFactory
-                  .getASN1Writer(streamWriter);
-
-              try
-              {
-                synchronized (writeLock)
-                {
-                  pendingRequests.put(messageID, future);
-                  try
-                  {
-                    LDAPEncoder.encodeBindRequest(asn1Writer,
-                        messageID, 3, saslBind, saslContext
-                            .getSASLCredentials());
-                    asn1Writer.flush();
-                  }
-                  catch (IOException e)
-                  {
-                    pendingRequests.remove(messageID);
-
-                    // FIXME: what other sort of IOExceptions can be
-                    // thrown?
-                    // FIXME: Is this the best result code?
-                    Result errorResult = Responses.newResult(
-                        ResultCode.CLIENT_SIDE_ENCODING_ERROR)
-                        .setCause(e);
-                    connectionErrorOccurred(errorResult);
-                    future.adaptErrorResult(errorResult);
-                  }
-                }
-              }
-              finally
-              {
-                connFactory.releaseASN1Writer(asn1Writer);
-              }
-              return;
-            }
-
-            if ((result.getResultCode() == ResultCode.SUCCESS)
-                && saslContext.isSecure())
-            {
-              // The connection needs to be secured by the SASL
-              // mechanism.
-              installFilter(SASLFilter.getInstance(saslContext,
-                  connection));
-            }
-          }
-          pendingBindOrStartTLS = -1;
-          future.setResultOrError(result);
-        }
-        else
-        {
-          handleIncorrectResponse(pendingRequest);
-        }
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleCompareResult(int messageID, CompareResult result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPCompareFutureResultImpl)
-        {
-          LDAPCompareFutureResultImpl future = (LDAPCompareFutureResultImpl) pendingRequest;
-          future.setResultOrError(result);
-        }
-        else
-        {
-          handleIncorrectResponse(pendingRequest);
-        }
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleDeleteResult(int messageID, Result result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPFutureResultImpl)
-        {
-          LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest;
-          if (future.getRequest() instanceof DeleteRequest)
-          {
-            future.setResultOrError(result);
-            return;
-          }
-        }
-        handleIncorrectResponse(pendingRequest);
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public void handleException(Throwable throwable)
-    {
-      Result errorResult;
-      if (throwable instanceof EOFException)
-      {
-        // FIXME: Is this the best result code?
-        errorResult = Responses.newResult(
-            ResultCode.CLIENT_SIDE_SERVER_DOWN).setCause(throwable);
-      }
-      else
-      {
-        // FIXME: what other sort of IOExceptions can be thrown?
-        // FIXME: Is this the best result code?
-        errorResult = Responses.newResult(
-            ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(throwable);
-      }
-      connectionErrorOccurred(errorResult);
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleExtendedResult(int messageID,
-        GenericExtendedResult result)
-    {
-      if (messageID == 0)
-      {
-        if ((result.getResponseName() != null)
-            && result.getResponseName().equals(
-                OID_NOTICE_OF_DISCONNECTION))
-        {
-
-          Result errorResult = Responses.newResult(
-              result.getResultCode()).setDiagnosticMessage(
-              result.getDiagnosticMessage());
-          close(null, true, errorResult);
-          return;
-        }
-        else
-        {
-          // Unsolicited notification received.
-          synchronized (writeLock)
-          {
-            if (isClosed)
-            {
-              // Don't notify after connection is closed.
-              return;
-            }
-
-            for (ConnectionEventListener listener : listeners)
-            {
-              listener
-                  .connectionReceivedUnsolicitedNotification(result);
-            }
-          }
-        }
-      }
-
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-
-      if (pendingRequest instanceof LDAPExtendedFutureResultImpl<?>)
-      {
-        LDAPExtendedFutureResultImpl<?> extendedFuture = ((LDAPExtendedFutureResultImpl<?>) pendingRequest);
-        try
-        {
-          handleExtendedResult0(extendedFuture, result);
-        }
-        catch (DecodeException de)
-        {
-          // FIXME: should the connection be closed as well?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_DECODING_ERROR)
-              .setDiagnosticMessage(de.getLocalizedMessage()).setCause(
-                  de);
-          extendedFuture.adaptErrorResult(errorResult);
-        }
-      }
-      else
-      {
-        handleIncorrectResponse(pendingRequest);
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleIntermediateResponse(int messageID,
-        GenericIntermediateResponse response)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        handleIncorrectResponse(pendingRequest);
-
-        // FIXME: intermediate responses can occur for all operations.
-
-        // if (pendingRequest instanceof LDAPExtendedFutureResultImpl)
-        // {
-        // LDAPExtendedFutureResultImpl extendedFuture =
-        // ((LDAPExtendedFutureResultImpl) pendingRequest);
-        // ExtendedRequest request = extendedFuture.getRequest();
-        //
-        // try
-        // {
-        // IntermediateResponse decodedResponse =
-        // request.getExtendedOperation()
-        // .decodeIntermediateResponse(
-        // response.getResponseName(),
-        // response.getResponseValue());
-        // extendedFuture.handleIntermediateResponse(decodedResponse);
-        // }
-        // catch (DecodeException de)
-        // {
-        // pendingRequest.failure(de);
-        // }
-        // }
-        // else
-        // {
-        // handleIncorrectResponse(pendingRequest);
-        // }
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleModifyDNResult(int messageID, Result result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPFutureResultImpl)
-        {
-          LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest;
-          if (future.getRequest() instanceof ModifyDNRequest)
-          {
-            future.setResultOrError(result);
-            return;
-          }
-        }
-        handleIncorrectResponse(pendingRequest);
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleModifyResult(int messageID, Result result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPFutureResultImpl)
-        {
-          LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest;
-          if (future.getRequest() instanceof ModifyRequest)
-          {
-            future.setResultOrError(result);
-            return;
-          }
-        }
-        handleIncorrectResponse(pendingRequest);
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleSearchResult(int messageID, Result result)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .remove(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPSearchFutureResultImpl)
-        {
-          ((LDAPSearchFutureResultImpl) pendingRequest)
-              .setResultOrError(result);
-        }
-        else
-        {
-          handleIncorrectResponse(pendingRequest);
-        }
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleSearchResultEntry(int messageID,
-        SearchResultEntry entry)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .get(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPSearchFutureResultImpl)
-        {
-          ((LDAPSearchFutureResultImpl) pendingRequest)
-              .handleSearchResultEntry(entry);
-        }
-        else
-        {
-          handleIncorrectResponse(pendingRequest);
-        }
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void handleSearchResultReference(int messageID,
-        SearchResultReference reference)
-    {
-      AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
-          .get(messageID);
-      if (pendingRequest != null)
-      {
-        if (pendingRequest instanceof LDAPSearchFutureResultImpl)
-        {
-          ((LDAPSearchFutureResultImpl) pendingRequest)
-              .handleSearchResultReference(reference);
-        }
-        else
-        {
-          handleIncorrectResponse(pendingRequest);
-        }
-      }
-    }
-
-
-
-    @Override
-    public Control decodeResponseControl(int messageID, String oid,
-        boolean isCritical, ByteString value, Schema schema)
-        throws DecodeException
-    {
-      ControlDecoder<?> decoder = connFactory.getControlDecoder(oid);
-      if (decoder != null)
-      {
-        return decoder.decode(isCritical, value, schema);
-      }
-      return super.decodeResponseControl(messageID, oid, isCritical,
-          value, schema);
-    }
-
-
-
-    public ResolvedSchema resolveSchema(String dn)
-        throws DecodeException
-    {
-      DN initialDN;
-
-      try
-      {
-        initialDN = DN.valueOf(dn, schema);
-      }
-      catch (LocalizedIllegalArgumentException e)
-      {
-        throw DecodeException.error(e.getMessageObject());
-      }
-
-      return new ResolvedSchemaImpl(schema, initialDN);
-    }
-
-
-
-    public Schema getDefaultSchema()
-    {
-      return schema;
-    }
-  }
-
-
-
-  private static final class ResolvedSchemaImpl implements
-      ResolvedSchema
-  {
-    private final DN initialDN;
-
-    private final Schema schema;
-
-
-
-    private ResolvedSchemaImpl(Schema schema, DN initialDN)
-    {
-      this.schema = schema;
-      this.initialDN = initialDN;
-    }
-
-
-
-    public AttributeDescription decodeAttributeDescription(
-        String attributeDescription) throws DecodeException
-    {
-      try
-      {
-        return AttributeDescription.valueOf(attributeDescription,
-            schema);
-      }
-      catch (LocalizedIllegalArgumentException e)
-      {
-        throw DecodeException.error(e.getMessageObject());
-      }
-    }
-
-
-
-    public DN decodeDN(String dn) throws DecodeException
-    {
-      try
-      {
-        return DN.valueOf(dn, schema);
-      }
-      catch (LocalizedIllegalArgumentException e)
-      {
-        throw DecodeException.error(e.getMessageObject());
-      }
-    }
-
-
-
-    public RDN decodeRDN(String rdn) throws DecodeException
-    {
-      try
-      {
-        return RDN.valueOf(rdn, schema);
-      }
-      catch (LocalizedIllegalArgumentException e)
-      {
-        throw DecodeException.error(e.getMessageObject());
-      }
-    }
-
-
-
-    public DN getInitialDN()
-    {
-      return initialDN;
-    }
-
-
-
-    public Schema getSchema()
-    {
-      return schema;
-    }
-
-  }
-
-
-
-  private final Schema schema;
-
   private final com.sun.grizzly.Connection<?> connection;
 
   private Result connectionInvalidReason;
 
-  private final LDAPConnectionFactoryImpl connFactory;
-
   private FilterChain customFilterChain;
 
-  private final LDAPMessageHandler handler = new LDAPMessageHandlerImpl();
-
   private boolean isClosed = false;
 
-  private final List<ConnectionEventListener> listeners = new CopyOnWriteArrayList<ConnectionEventListener>();
+  private final List<ConnectionEventListener> listeners =
+    new CopyOnWriteArrayList<ConnectionEventListener>();
 
   private final AtomicInteger nextMsgID = new AtomicInteger(1);
 
-  private volatile int pendingBindOrStartTLS = -1;
+  private boolean bindOrStartTLSInProgress = false;
 
-  private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests = new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
+  private final HashMap<Integer, AbstractLDAPFutureResultImpl<?>>
+    pendingRequests = new HashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
 
-  private final InetSocketAddress serverAddress;
+  private final Object stateLock = new Object();
 
-  private StreamWriter streamWriter;
+  private final LDAPWriter ldapWriter = new LDAPWriter();
 
-  private final Object writeLock = new Object();
+  private final LDAPOptions options;
+
+
 
   /**
    * Creates a new LDAP connection.
    *
    * @param connection
    *          The Grizzly connection.
-   * @param serverAddress
-   *          The address of the server.
-   * @param schema
-   *          The schema which will be used to decode responses from the
-   *          server.
-   * @param connFactory
-   *          The associated connection factory.
+   * @param options
+   *          The LDAP client options.
    */
-  LDAPConnection(com.sun.grizzly.Connection<?> connection,
-      InetSocketAddress serverAddress, Schema schema,
-      LDAPConnectionFactoryImpl connFactory)
+  LDAPConnection(final com.sun.grizzly.Connection<?> connection,
+      final LDAPOptions options)
   {
     this.connection = connection;
-    this.serverAddress = serverAddress;
-    this.schema = schema;
-    this.connFactory = connFactory;
-    this.streamWriter = getFilterChainStreamWriter();
+    this.options = options;
   }
 
 
@@ -695,105 +110,121 @@
   /**
    * {@inheritDoc}
    */
-  public void abandon(AbandonRequest request)
+  public FutureResult<Void> abandon(final AbandonRequest request)
   {
-    AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
+    // First remove the future associated with the request to be abandoned.
+    final AbstractLDAPFutureResultImpl<?> pendingRequest = pendingRequests
         .remove(request.getMessageID());
-    if (pendingRequest != null)
-    {
-      pendingRequest.cancel(false);
-      int messageID = nextMsgID.getAndIncrement();
-      ASN1StreamWriter asn1Writer = connFactory
-          .getASN1Writer(streamWriter);
 
-      try
+    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);
+    }
+
+    pendingRequest.cancel(false);
+    final int messageID = nextMsgID.getAndIncrement();
+
+    synchronized (stateLock)
+    {
+      if (connectionInvalidReason != null)
       {
-        synchronized (writeLock)
-        {
-          if (connectionInvalidReason != null)
-          {
-            return;
-          }
-          if (pendingBindOrStartTLS > 0)
-          {
-            // This is not allowed. We will just ignore this
-            // abandon request.
-          }
-          try
-          {
-            LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
-                request);
-            asn1Writer.flush();
-          }
-          catch (IOException e)
-          {
-            // FIXME: what other sort of IOExceptions can be thrown?
-            // FIXME: Is this the best result code?
-            Result errorResult = Responses.newResult(
-                ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-            connectionErrorOccurred(errorResult);
-          }
-        }
+        return new CompletedFutureResult<Void>(ErrorResultException
+            .wrap(connectionInvalidReason), messageID);
       }
-      finally
+      if (bindOrStartTLSInProgress)
       {
-        connFactory.releaseASN1Writer(asn1Writer);
+        final Result errorResult = Responses.newResult(
+            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+            "Bind or Start TLS operation in progress");
+        return new CompletedFutureResult<Void>(ErrorResultException
+            .wrap(errorResult), messageID);
       }
     }
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public FutureResult<Result> add(AddRequest request,
-      ResultHandler<Result> handler)
-  {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request,
-        handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeAddRequest(asn1Writer, messageID, request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.abandonRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+        return new CompletedFutureResult<Void>((Void) null, messageID);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      // 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>(ErrorResultException
+          .wrap(errorResult), messageID);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public FutureResult<Result> add(final AddRequest request,
+      final ResultHandler<Result> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
+  {
+    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)
+      {
+        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;
@@ -804,9 +235,8 @@
   /**
    * {@inheritDoc}
    */
-  public void addConnectionEventListener(
-      ConnectionEventListener listener) throws IllegalStateException,
-      NullPointerException
+  public void addConnectionEventListener(final ConnectionEventListener listener)
+      throws IllegalStateException, NullPointerException
   {
     Validator.ensureNotNull(listener);
     listeners.add(listener);
@@ -817,98 +247,88 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<BindResult> bind(BindRequest request,
-      ResultHandler<? super BindResult> handler)
+  public FutureResult<BindResult> bind(final BindRequest request,
+      final ResultHandler<? super BindResult> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPBindFutureResultImpl future = new LDAPBindFutureResultImpl(messageID,
-        request, handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    final int messageID = nextMsgID.getAndIncrement();
+
+    BindClient context;
+    try
+    {
+      context = request
+          .createBindClient(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.wrap(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 (bindOrStartTLSInProgress)
+      {
+        future.setResultOrError(Responses.newBindResult(
+            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+            "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      if (!pendingRequests.isEmpty())
+      {
+        future.setResultOrError(Responses.newBindResult(
+            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+            "There are other operations pending on this connection"));
+        return future;
+      }
+
+      pendingRequests.put(messageID, future);
+      bindOrStartTLSInProgress = true;
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newBindResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        if (!pendingRequests.isEmpty())
-        {
-          future.setResultOrError(Responses.newBindResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "There are other operations pending on this connection"));
-          return future;
-        }
-
-        pendingRequests.put(messageID, future);
-        pendingBindOrStartTLS = messageID;
-
-        try
-        {
-          if (request instanceof SASLBindRequest<?>)
-          {
-            try
-            {
-              SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
-              SASLContext saslContext = saslBind
-                  .getClientContext(serverAddress.getHostName());
-              future.setSASLContext(saslContext);
-              LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
-                  saslBind, saslContext.getSASLCredentials());
-            }
-            catch (SaslException e)
-            {
-              // FIXME: I18N need to have a better error message.
-              // FIXME: Is this the best result code?
-              Result errorResult = Responses.newResult(
-                  ResultCode.CLIENT_SIDE_LOCAL_ERROR)
-                  .setDiagnosticMessage(
-                      "An error occurred during SASL authentication")
-                  .setCause(e);
-              future.adaptErrorResult(errorResult);
-              return future;
-            }
-          }
-          else if (request instanceof SimpleBindRequest)
-          {
-            LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
-                (SimpleBindRequest) request);
-          }
-          else
-          {
-            pendingRequests.remove(messageID);
-            future.setResultOrError(Responses.newBindResult(
-                ResultCode.CLIENT_SIDE_AUTH_UNKNOWN)
-                .setDiagnosticMessage("Auth type not supported"));
-          }
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        // 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();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -919,7 +339,7 @@
   /**
    * {@inheritDoc}
    */
-  public void close(UnbindRequest request, String reason)
+  public void close(final UnbindRequest request, final String reason)
       throws NullPointerException
   {
     // FIXME: I18N need to internationalize this message.
@@ -927,8 +347,7 @@
 
     close(request, false, Responses.newResult(
         ResultCode.CLIENT_SIDE_USER_CANCELLED).setDiagnosticMessage(
-        "Connection closed by client"
-            + (reason != null ? ": " + reason : "")));
+        "Connection closed by client" + (reason != null ? ": " + reason : "")));
   }
 
 
@@ -936,54 +355,54 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<CompareResult> compare(CompareRequest request,
-      ResultHandler<? super CompareResult> handler)
+  public FutureResult<CompareResult> compare(final CompareRequest request,
+      final ResultHandler<? super CompareResult> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPCompareFutureResultImpl future = new LDAPCompareFutureResultImpl(
-        messageID, request, handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    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)
+      {
+        future.setResultOrError(Responses.newCompareResult(
+            ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+            "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newCompareResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeCompareRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.compareRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -994,54 +413,54 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<Result> delete(DeleteRequest request,
-      ResultHandler<Result> handler)
+  public FutureResult<Result> delete(final DeleteRequest request,
+      final ResultHandler<Result> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request,
-        handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    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)
+      {
+        future.setResultOrError(Responses
+            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeDeleteRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.deleteRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -1052,73 +471,72 @@
   /**
    * {@inheritDoc}
    */
-  public <R extends Result> FutureResult<R> extendedRequest(
-      ExtendedRequest<R> request, ResultHandler<? super R> handler)
+  public <R extends ExtendedResult> FutureResult<R> extendedRequest(
+      final ExtendedRequest<R> request,
+      final ResultHandler<? super R> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPExtendedFutureResultImpl<R> future = new LDAPExtendedFutureResultImpl<R>(
-        messageID, request, handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    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 (bindOrStartTLSInProgress)
+      {
+        future.setResultOrError(request.getResultDecoder()
+            .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+                "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      if (request.getOID().equals(StartTLSExtendedRequest.OID))
+      {
+        if (!pendingRequests.isEmpty())
+        {
+          future.setResultOrError(request.getResultDecoder()
+              .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+                  "There are pending operations on this connection"));
+          return future;
+        }
+        if (isTLSEnabled())
+        {
+          future.setResultOrError(request.getResultDecoder()
+              .adaptExtendedErrorResult(ResultCode.OPERATIONS_ERROR, "",
+                  "This connection is already TLS enabled"));
+        }
+        bindOrStartTLSInProgress = true;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(request.getExtendedOperation()
-              .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
-                  "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        if (request.getRequestName().equals(
-            StartTLSRequest.OID_START_TLS_REQUEST))
-        {
-          if (!pendingRequests.isEmpty())
-          {
-            future.setResultOrError(request.getExtendedOperation()
-                .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
-                    "There are pending operations on this connection"));
-            return future;
-          }
-          if (isTLSEnabled())
-          {
-            future.setResultOrError(request.getExtendedOperation()
-                .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
-                    "This connection is already TLS enabled"));
-          }
-          pendingBindOrStartTLS = messageID;
-        }
-        pendingRequests.put(messageID, future);
-
-        try
-        {
-          LDAPEncoder.encodeExtendedRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.extendedRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -1129,54 +547,74 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<Result> modify(ModifyRequest request,
-      ResultHandler<Result> handler)
+  public boolean isClosed()
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request,
-        handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    return isClosed;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isValid()
+  {
+    return connectionInvalidReason == null && !isClosed;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public FutureResult<Result> modify(final ModifyRequest request,
+      final ResultHandler<Result> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
+  {
+    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)
+      {
+        future.setResultOrError(Responses
+            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeModifyRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.modifyRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -1187,54 +625,54 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<Result> modifyDN(ModifyDNRequest request,
-      ResultHandler<Result> handler)
+  public FutureResult<Result> modifyDN(final ModifyDNRequest request,
+      final ResultHandler<Result> resultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request,
-        handler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    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)
+      {
+        future.setResultOrError(Responses
+            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeModifyDNRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.modifyDNRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -1246,7 +684,7 @@
    * {@inheritDoc}
    */
   public void removeConnectionEventListener(
-      ConnectionEventListener listener) throws NullPointerException
+      final ConnectionEventListener listener) throws NullPointerException
   {
     Validator.ensureNotNull(listener);
     listeners.remove(listener);
@@ -1257,55 +695,56 @@
   /**
    * {@inheritDoc}
    */
-  public FutureResult<Result> search(SearchRequest request,
-      ResultHandler<Result> resultHandler,
-      SearchResultHandler searchResulthandler)
+  public FutureResult<Result> search(final SearchRequest request,
+      final ResultHandler<Result> resultHandler,
+      final SearchResultHandler searchResultHandler,
+      final IntermediateResponseHandler intermediateResponseHandler)
   {
-    int messageID = nextMsgID.getAndIncrement();
-    LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl(
-        messageID, request, resultHandler, searchResulthandler, this);
-    ASN1StreamWriter asn1Writer = connFactory
-        .getASN1Writer(streamWriter);
+    final int messageID = nextMsgID.getAndIncrement();
+    final LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl(
+        messageID, request, resultHandler, searchResultHandler,
+        intermediateResponseHandler, this);
+
+    synchronized (stateLock)
+    {
+      if (connectionInvalidReason != null)
+      {
+        future.adaptErrorResult(connectionInvalidReason);
+        return future;
+      }
+      if (bindOrStartTLSInProgress)
+      {
+        future.setResultOrError(Responses
+            .newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                "Bind or Start TLS operation in progress"));
+        return future;
+      }
+      pendingRequests.put(messageID, future);
+    }
 
     try
     {
-      synchronized (writeLock)
+      final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+      try
       {
-        if (connectionInvalidReason != null)
-        {
-          future.adaptErrorResult(connectionInvalidReason);
-          return future;
-        }
-        if (pendingBindOrStartTLS > 0)
-        {
-          future.setResultOrError(Responses.newResult(
-              ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
-              "Bind or Start TLS operation in progress"));
-          return future;
-        }
-        pendingRequests.put(messageID, future);
-        try
-        {
-          LDAPEncoder.encodeSearchRequest(asn1Writer, messageID,
-              request);
-          asn1Writer.flush();
-        }
-        catch (IOException e)
-        {
-          pendingRequests.remove(messageID);
-
-          // FIXME: what other sort of IOExceptions can be thrown?
-          // FIXME: Is this the best result code?
-          Result errorResult = Responses.newResult(
-              ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-          connectionErrorOccurred(errorResult);
-          future.adaptErrorResult(errorResult);
-        }
+        ldapWriter.searchRequest(asn1Writer, messageID, request);
+        connection.write(asn1Writer.getBuffer(), null);
+      }
+      finally
+      {
+        asn1Writer.recycle();
       }
     }
-    finally
+    catch (final IOException e)
     {
-      connFactory.releaseASN1Writer(asn1Writer);
+      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;
@@ -1313,40 +752,58 @@
 
 
 
-  /**
-   * Returns the LDAP message handler associated with this connection.
-   *
-   * @return The LDAP message handler associated with this connection.
-   */
-  LDAPMessageHandler getLDAPMessageHandler()
+  int addPendingRequest(final AbstractLDAPFutureResultImpl<?> request)
   {
-    return handler;
+    final int newMsgID = nextMsgID.getAndIncrement();
+    pendingRequests.put(newMsgID, request);
+    return newMsgID;
   }
 
 
 
-  /**
-   * 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()
+  long cancelExpiredRequests(final long currentTime)
   {
-    FilterChain currentFilterChain = (FilterChain) connection
-        .getProcessor();
-    return currentFilterChain.get(2) instanceof SSLFilter;
+    final long timeout = options.getTimeout(TimeUnit.MILLISECONDS);
+    long delay = timeout;
+    if (timeout > 0)
+    {
+      synchronized (stateLock)
+      {
+        for (final Iterator<AbstractLDAPFutureResultImpl<?>> i = pendingRequests
+            .values().iterator(); i.hasNext();)
+        {
+          final AbstractLDAPFutureResultImpl<?> future = i.next();
+          final long diff = (future.getTimestamp() + timeout) - currentTime;
+          if (diff <= 0)
+          {
+            StaticUtils.DEBUG_LOG.fine("Cancelling expired future result: "
+                + future);
+            final Result result = Responses
+                .newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
+            future.adaptErrorResult(result);
+            i.remove();
+
+            abandon(Requests.newAbandonRequest(future.getRequestID()));
+          }
+          else
+          {
+            delay = Math.min(delay, diff);
+          }
+        }
+      }
+    }
+    return delay;
   }
 
 
 
-  private void close(UnbindRequest unbindRequest,
-      boolean isDisconnectNotification, Result reason)
+  void close(final UnbindRequest unbindRequest,
+      final boolean isDisconnectNotification, final Result reason)
   {
     boolean notifyClose = false;
     boolean notifyErrorOccurred = false;
 
-    synchronized (writeLock)
+    synchronized (stateLock)
     {
       if (isClosed)
       {
@@ -1376,30 +833,31 @@
     }
 
     // First abort all outstanding requests.
-    for (AbstractLDAPFutureResultImpl<?> future : pendingRequests
+    for (final AbstractLDAPFutureResultImpl<?> future : pendingRequests
         .values())
     {
-      if (pendingBindOrStartTLS <= 0)
+      if (!bindOrStartTLSInProgress)
       {
-        ASN1StreamWriter asn1Writer = connFactory
-            .getASN1Writer(streamWriter);
-        int messageID = nextMsgID.getAndIncrement();
-        AbandonRequest abandon = Requests.newAbandonRequest(future
+        final int messageID = nextMsgID.getAndIncrement();
+        final AbandonRequest abandon = Requests.newAbandonRequest(future
             .getRequestID());
         try
         {
-          LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
-                                           abandon);
-          asn1Writer.flush();
+          final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+          try
+          {
+            ldapWriter.abandonRequest(asn1Writer, messageID, abandon);
+            connection.write(asn1Writer.getBuffer(), null);
+          }
+          finally
+          {
+            asn1Writer.recycle();
+          }
         }
-        catch (IOException e)
+        catch (final IOException e)
         {
           // Underlying channel probably blown up. Just ignore.
         }
-        finally
-        {
-          connFactory.releaseASN1Writer(asn1Writer);
-        }
       }
 
       future.adaptErrorResult(reason);
@@ -1407,45 +865,34 @@
     pendingRequests.clear();
 
     // Now try cleanly closing the connection if possible.
-    try
+    // Only send unbind if specified.
+    if (unbindRequest != null)
     {
-      ASN1StreamWriter asn1Writer = connFactory
-          .getASN1Writer(streamWriter);
-      if (unbindRequest == null)
-      {
-        unbindRequest = Requests.newUnbindRequest();
-      }
-
       try
       {
-        LDAPEncoder.encodeUnbindRequest(asn1Writer, nextMsgID
-            .getAndIncrement(), unbindRequest);
-        asn1Writer.flush();
+        final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
+        try
+        {
+          ldapWriter.unbindRequest(asn1Writer, nextMsgID.getAndIncrement(),
+              unbindRequest);
+          connection.write(asn1Writer.getBuffer(), null);
+        }
+        finally
+        {
+          asn1Writer.recycle();
+        }
       }
-      finally
+      catch (final IOException e)
       {
-        connFactory.releaseASN1Writer(asn1Writer);
+        // Underlying channel prob blown up. Just ignore.
       }
     }
-    catch (IOException e)
-    {
-      // Underlying channel prob blown up. Just ignore.
-    }
-
-    try
-    {
-      streamWriter.close();
-    }
-    catch (IOException e)
-    {
-      // Ignore.
-    }
 
     try
     {
       connection.close();
     }
-    catch (IOException e)
+    catch (final IOException e)
     {
       // Ignore.
     }
@@ -1453,205 +900,120 @@
     // Notify listeners.
     if (notifyClose)
     {
-      // TODO: uncomment if close notification is required.
-      // for (ConnectionEventListener listener : listeners)
-      // {
-      // listener.connectionClosed(this);
-      // }
+      for (final ConnectionEventListener listener : listeners)
+      {
+        listener.connectionClosed();
+      }
     }
 
     if (notifyErrorOccurred)
     {
-      for (ConnectionEventListener listener : listeners)
+      for (final ConnectionEventListener listener : listeners)
       {
         listener.connectionErrorOccurred(isDisconnectNotification,
-                                         ErrorResultException.wrap(reason));
+            ErrorResultException.wrap(reason));
       }
     }
   }
 
 
 
-  private void connectionErrorOccurred(Result reason)
+  LDAPOptions getLDAPOptions()
   {
-    close(null, false, reason);
+    return options;
   }
 
 
 
-  // TODO uncomment if we decide these methods are useful.
-  /**
-   * {@inheritDoc}
-   */
-  public boolean isClosed()
+  AbstractLDAPFutureResultImpl<?> getPendingRequest(final Integer messageID)
   {
-    return isClosed;
+    return pendingRequests.get(messageID);
   }
 
 
 
-  /**
-   * {@inheritDoc}
-   */
-  public boolean isValid()
+  synchronized void handleUnsolicitedNotification(final ExtendedResult result)
   {
-    return connectionInvalidReason == null && !isClosed;
-  }
-  //
-  //
-  //
-  // /**
-  // * {@inheritDoc}
-  // */
-  // public boolean isValid(long timeout, TimeUnit unit)
-  // throws InterruptedException, TimeoutException
-  // {
-  // // FIXME: no support for timeout.
-  // return isValid();
-  // }
-
-  private StreamWriter getFilterChainStreamWriter()
-  {
-    StreamWriter writer = connection.getStreamWriter();
-    FilterChain currentFilterChain = (FilterChain) connection
-        .getProcessor();
-    for (Filter filter : currentFilterChain)
+    if (isClosed)
     {
-      if (filter instanceof StreamTransformerFilter)
-      {
-        writer = ((StreamTransformerFilter) filter)
-            .getStreamWriter(writer);
-      }
+      // Don't notify after connection is closed.
+      return;
     }
 
-    return writer;
-  }
-
-
-
-  // Needed in order to expose type information.
-  private <R extends Result> void handleExtendedResult0(
-      LDAPExtendedFutureResultImpl<R> future, GenericExtendedResult result)
-      throws DecodeException
-  {
-    R decodedResponse = future.decodeResponse(result.getResultCode(),
-        result.getMatchedDN(), result.getDiagnosticMessage(), result
-            .getResponseName(), result.getResponseValue());
-
-    if (future.getRequest() instanceof StartTLSRequest)
+    for (final ConnectionEventListener listener : listeners)
     {
-      if (result.getResultCode() == ResultCode.SUCCESS)
-      {
-        StartTLSRequest request = (StartTLSRequest) future.getRequest();
-        try
-        {
-          startTLS(request.getSSLContext());
-        }
-        catch (ErrorResultException e)
-        {
-          future.adaptErrorResult(e.getResult());
-          return;
-        }
-      }
-      pendingBindOrStartTLS = -1;
-    }
-
-    future.setResultOrError(decodedResponse);
-  }
-
-
-
-  private void handleIncorrectResponse(
-      AbstractLDAPFutureResultImpl<?> pendingRequest)
-  {
-    // FIXME: I18N need to have a better error message.
-    Result errorResult = Responses.newResult(
-        ResultCode.CLIENT_SIDE_DECODING_ERROR).setDiagnosticMessage(
-        "LDAP response message did not match request");
-
-    pendingRequest.adaptErrorResult(errorResult);
-    connectionErrorOccurred(errorResult);
-  }
-
-
-
-  private void startTLS(SSLContext sslContext)
-      throws ErrorResultException
-  {
-    SSLHandshaker sslHandshaker = connFactory.getSslHandshaker();
-    SSLFilter sslFilter;
-    SSLEngineConfigurator sslEngineConfigurator;
-    if (sslContext == connFactory.getSSLContext())
-    {
-      // Use factory SSL objects since it is the same SSLContext
-      sslFilter = connFactory.getSSlFilter();
-      sslEngineConfigurator = connFactory.getSSlEngineConfigurator();
-    }
-    else
-    {
-      sslEngineConfigurator = new SSLEngineConfigurator(sslContext,
-          true, false, false);
-      sslFilter = new SSLFilter(sslEngineConfigurator, sslHandshaker);
-    }
-    installFilter(sslFilter);
-
-    performSSLHandshake(sslHandshaker, sslEngineConfigurator);
-  }
-
-
-
-  void performSSLHandshake(SSLHandshaker sslHandshaker,
-      SSLEngineConfigurator sslEngineConfigurator)
-      throws ErrorResultException
-  {
-    SSLStreamReader reader = new SSLStreamReader(connection
-        .getStreamReader());
-    SSLStreamWriter writer = new SSLStreamWriter(connection
-        .getStreamWriter());
-
-    try
-    {
-      sslHandshaker.handshake(reader, writer, sslEngineConfigurator)
-          .get();
-    }
-    catch (ExecutionException ee)
-    {
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(ee.getCause());
-      connectionErrorOccurred(errorResult);
-      throw ErrorResultException.wrap(errorResult);
-    }
-    catch (Exception e)
-    {
-      // FIXME: what other sort of IOExceptions can be thrown?
-      // FIXME: Is this the best result code?
-      Result errorResult = Responses.newResult(
-          ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(e);
-      connectionErrorOccurred(errorResult);
-      throw ErrorResultException.wrap(errorResult);
+      listener.connectionReceivedUnsolicitedNotification(result);
     }
   }
 
 
 
-  synchronized void installFilter(Filter filter)
+  void installFilter(final Filter filter)
   {
     if (customFilterChain == null)
     {
-      customFilterChain = connFactory.getDefaultFilterChainFactory()
-          .create();
+      customFilterChain = new DefaultFilterChain((FilterChain) connection
+          .getProcessor());
       connection.setProcessor(customFilterChain);
     }
 
     // Install the SSLFilter in the custom filter chain
-    Filter oldFilter = customFilterChain.remove(customFilterChain
-        .size() - 1);
-    customFilterChain.add(filter);
-    customFilterChain.add(oldFilter);
+    customFilterChain.add(customFilterChain.size() - 1, filter);
+  }
 
-    // Update stream writer
-    streamWriter = getFilterChainStreamWriter();
+
+
+  /**
+   * 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()
+  {
+    final FilterChain currentFilterChain = (FilterChain) connection
+        .getProcessor();
+    return currentFilterChain.get(currentFilterChain.size() - 2) instanceof SSLFilter;
+  }
+
+
+
+  AbstractLDAPFutureResultImpl<?> removePendingRequest(final Integer messageID)
+  {
+    return pendingRequests.remove(messageID);
+  }
+
+
+
+  void setBindOrStartTLSInProgress(final boolean state)
+  {
+    bindOrStartTLSInProgress = state;
+  }
+
+
+
+  synchronized void startTLS(final SSLContext sslContext,
+      final CompletionHandler<SSLEngine> completionHandler) throws IOException
+  {
+    if (isTLSEnabled())
+    {
+      return;
+    }
+
+    SSLFilter sslFilter;
+    SSLEngineConfigurator sslEngineConfigurator;
+
+    sslEngineConfigurator = new SSLEngineConfigurator(sslContext, true, false,
+        false);
+    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