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

matthew_swift
28.47.2010 f2160f4bd1c8ac67e5a86a6710d431e8932877f9
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);
  }
}