From 21766e2b70d3b86716f2c4517a9f8ba172fc469c Mon Sep 17 00:00:00 2001
From: Gaetan Boismal <gaetan.boismal@forgerock.com>
Date: Fri, 28 Nov 2014 14:52:21 +0000
Subject: [PATCH] OPENDJ-1607 Revert changes to revision 11339

---
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java             |  181 ++
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java                |    6 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java                                |  160 -
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java             |   27 
 opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java                   |   18 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java                 |    8 
 opendj-sdk/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties                   |    8 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java           |    8 
 opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java                     |   70 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java                               |  123 +
 opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java            |    6 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java                    |    6 
 opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java                   |   12 
 opendj-sdk/opendj-core/clirr-ignored-api-changes.xml                                                           |   65 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java                  |    7 
 opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java         |  294 ++
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java                    |    6 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java                |    8 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java                      |   20 
 opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java        |   18 
 opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java                    |   60 
 opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java                        |  144 
 opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java               |  298 +++
 opendj-sdk/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java   |   41 
 opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties                            |   14 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java         |    9 
 opendj-sdk/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java   |   41 
 opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java                           |   16 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java                     |   33 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java                    |    6 
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java         |  455 +++++
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java                   |   12 
 opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java          |   96 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java                                |  316 +--
 opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                        |   71 
 opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java            |  133 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java        |    8 
 opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java                |  755 +++++++
 opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java |   17 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java                  |    8 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java        |   14 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java                |   16 
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java                              |    8 
 /dev/null                                                                                                      |  563 ------
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java                 |  998 +++++++++++
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java               |    3 
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java                                 |    5 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java               |    8 
 opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java  |    7 
 49 files changed, 3,579 insertions(+), 1,627 deletions(-)

diff --git a/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
new file mode 100644
index 0000000..db64344
--- /dev/null
+++ b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
@@ -0,0 +1,298 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS.
+ */
+package com.forgerock.opendj.cli;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.opendj.ldap.AbstractConnectionWrapper;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.util.Reject;
+import org.forgerock.util.promise.AsyncFunction;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.Function;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.SuccessHandler;
+
+import static org.forgerock.util.Utils.*;
+/**
+ * An authenticated connection factory can be used to create pre-authenticated
+ * connections to a Directory Server.
+ * <p>
+ * The connections returned by an authenticated connection factory support all
+ * operations with the exception of Bind requests. Attempts to perform a Bind
+ * will result in an {@code UnsupportedOperationException}.
+ * <p>
+ * In addition, the returned connections support retrieval of the
+ * {@code BindResult} returned from the initial Bind request, or last rebind.
+ * <p>
+ * Support for connection re-authentication is provided through the
+ * {@link #setRebindAllowed} method which, if set to {@code true}, causes
+ * subsequent connections created using the factory to support the
+ * {@code rebind} method.
+ * <p>
+ * If the Bind request fails for some reason (e.g. invalid credentials), then
+ * the connection attempt will fail and an {@link LdapException} will be thrown.
+ */
+public final class AuthenticatedConnectionFactory implements ConnectionFactory {
+
+    /**
+     * An authenticated connection supports all operations except Bind
+     * operations.
+     */
+    public static final class AuthenticatedConnection extends AbstractConnectionWrapper<Connection> {
+
+        private final BindRequest request;
+        private volatile BindResult result;
+
+        private AuthenticatedConnection(final Connection connection, final BindRequest request,
+                final BindResult result) {
+            super(connection);
+            this.request = request;
+            this.result = result;
+        }
+
+        /*
+         * Bind operations are not supported by pre-authenticated connections.
+         * These methods will always throw {@code UnsupportedOperationException}.
+         */
+        /** {@inheritDoc} */
+        @Override
+        public BindResult bind(BindRequest request) throws LdapException {
+            throw new UnsupportedOperationException();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public BindResult bind(String name, char[] password) throws LdapException {
+            throw new UnsupportedOperationException();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LdapPromise<BindResult> bindAsync(BindRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Returns an unmodifiable view of the Bind result which was returned
+         * from the server after authentication.
+         *
+         * @return The Bind result which was returned from the server after
+         *         authentication.
+         */
+        public BindResult getAuthenticatedBindResult() {
+            return result;
+        }
+
+        /**
+         * Re-authenticates to the Directory Server using the bind request
+         * associated with this connection. If re-authentication fails for some
+         * reason then this connection will be automatically closed.
+         *
+         * @return A promise representing the result of the operation.
+         * @throws UnsupportedOperationException
+         *             If this connection does not support rebind operations.
+         * @throws IllegalStateException
+         *             If this connection has already been closed, i.e. if
+         *             {@code isClosed() == true}.
+         */
+        public LdapPromise<BindResult> rebindAsync() {
+            if (request == null) {
+                throw new UnsupportedOperationException();
+            }
+
+            return (LdapPromise<BindResult>) connection.bindAsync(request)
+                    .onSuccess(new SuccessHandler<BindResult>() {
+                        @Override
+                        public void handleResult(final BindResult result) {
+                            // Save the result.
+                            AuthenticatedConnection.this.result = result;
+                        }
+                    }).onFailure(new FailureHandler<LdapException>() {
+                        @Override
+                        public void handleError(final LdapException error) {
+                            /*
+                             * This connection is now unauthenticated so prevent further use.
+                             */
+                            connection.close();
+                        }
+                    });
+        }
+
+        /**
+         * Returns the string representation of this authenticated connection.
+         *
+         * @return The string representation of this authenticated connection factory.
+         */
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("AuthenticatedConnection(");
+            builder.append(connection);
+            builder.append(')');
+            return builder.toString();
+        }
+
+    }
+
+    private final BindRequest request;
+    private final ConnectionFactory parentFactory;
+    private boolean allowRebinds;
+
+    /**
+     * Creates a new authenticated connection factory which will obtain
+     * connections using the provided connection factory and immediately perform
+     * the provided Bind request.
+     *
+     * @param factory
+     *            The connection factory to use for connecting to the Directory
+     *            Server.
+     * @param request
+     *            The Bind request to use for authentication.
+     * @throws NullPointerException
+     *             If {@code factory} or {@code request} was {@code null}.
+     */
+    public AuthenticatedConnectionFactory(final ConnectionFactory factory, final BindRequest request) {
+        Reject.ifNull(factory, request);
+        this.parentFactory = factory;
+
+        // FIXME: should do a defensive copy.
+        this.request = request;
+    }
+
+    @Override
+    public void close() {
+        parentFactory.close();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Connection getConnection() throws LdapException {
+        final Connection connection = parentFactory.getConnection();
+        BindResult bindResult = null;
+        try {
+            bindResult = connection.bind(request);
+        } finally {
+            if (bindResult == null) {
+                connection.close();
+            }
+        }
+
+        /*
+         * If the bind didn't succeed then an exception will have been thrown
+         * and this line will not be reached.
+         */
+        return new AuthenticatedConnection(connection, request, bindResult);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Promise<Connection, LdapException> getConnectionAsync() {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
+        return parentFactory.getConnectionAsync()
+            .thenAsync(
+                    new AsyncFunction<Connection, BindResult, LdapException>() {
+                        @Override
+                        public Promise<BindResult, LdapException> apply(final Connection connection)
+                                throws LdapException {
+                            connectionHolder.set(connection);
+                            return connection.bindAsync(request);
+                        }
+                    }
+            ).then(
+                    new Function<BindResult, Connection, LdapException>() {
+                        @Override
+                        public Connection apply(BindResult result) throws LdapException {
+                            // FIXME: should make the result unmodifiable.
+                            return new AuthenticatedConnection(connectionHolder.get(), request, result);
+                        }
+                    },
+                    new Function<LdapException, Connection, LdapException>() {
+                        @Override
+                        public Connection apply(LdapException errorResult) throws LdapException {
+                            closeSilently(connectionHolder.get());
+                            throw errorResult;
+                        }
+                    }
+            );
+    }
+
+    /**
+     * Indicates whether or not rebind requests are to be supported by
+     * connections created by this authenticated connection factory.
+     * <p>
+     * Rebind requests are invoked using the connection's {@code rebind} method
+     * which will throw an {@code UnsupportedOperationException} if rebinds are
+     * not supported (the default).
+     *
+     * @return allowRebinds {@code true} if the {@code rebind} operation is to
+     *         be supported, otherwise {@code false}.
+     */
+    boolean isRebindAllowed() {
+        return allowRebinds;
+    }
+
+    /**
+     * Specifies whether or not rebind requests are to be supported by
+     * connections created by this authenticated connection factory.
+     * <p>
+     * Rebind requests are invoked using the connection's {@code rebind} method
+     * which will throw an {@code UnsupportedOperationException} if rebinds are
+     * not supported (the default).
+     *
+     * @param allowRebinds
+     *            {@code true} if the {@code rebind} operation is to be
+     *            supported, otherwise {@code false}.
+     * @return A reference to this connection factory.
+     */
+    AuthenticatedConnectionFactory setRebindAllowed(final boolean allowRebinds) {
+        this.allowRebinds = allowRebinds;
+        return this;
+    }
+
+    /**
+     * Returns the string representation of this authenticated connection factory.
+     *
+     * @return The string representation of this authenticated connection factory.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("AuthenticatedConnectionFactory(");
+        builder.append(parentFactory);
+        builder.append(')');
+        return builder.toString();
+    }
+
+}
diff --git a/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
index 267dfa6..fb21e1b 100644
--- a/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
+++ b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
@@ -26,6 +26,11 @@
  */
 package com.forgerock.opendj.cli;
 
+import static com.forgerock.opendj.cli.ArgumentConstants.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.cli.CliConstants.DEFAULT_LDAP_PORT;
+import static com.forgerock.opendj.cli.Utils.getHostNameForLdapUrl;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -46,6 +51,7 @@
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.KeyManagers;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.TrustManagers;
@@ -59,15 +65,6 @@
 import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.forgerock.opendj.ldap.Connections.*;
-
-import static com.forgerock.opendj.cli.ArgumentConstants.*;
-import static com.forgerock.opendj.cli.CliConstants.*;
-import static com.forgerock.opendj.cli.CliMessages.*;
-import static com.forgerock.opendj.cli.Utils.*;
-
 /**
  * A connection factory designed for use with command line tools.
  */
@@ -75,61 +72,59 @@
     /** The Logger. */
     static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
-    private static final long DEFAULT_TIMEOUT_SECONDS = 3;
-
     /** The 'hostName' global argument. */
-    private final StringArgument hostNameArg;
+    private StringArgument hostNameArg;
 
     /** The 'port' global argument. */
-    private final IntegerArgument portArg;
+    private IntegerArgument portArg;
 
     /** The 'bindDN' global argument. */
-    private final StringArgument bindNameArg;
+    private StringArgument bindNameArg;
 
     /** The 'bindPasswordFile' global argument. */
-    private final FileBasedArgument bindPasswordFileArg;
+    private FileBasedArgument bindPasswordFileArg;
 
     /** The 'password' value. */
     private char[] password;
 
     /** The 'bindPassword' global argument. */
-    private final StringArgument bindPasswordArg;
+    private StringArgument bindPasswordArg;
 
     /** The 'connectTimeOut' global argument. */
-    private final IntegerArgument connectTimeOut;
+    private IntegerArgument connectTimeOut;
 
     /** The 'trustAllArg' global argument. */
-    private final BooleanArgument trustAllArg;
+    private BooleanArgument trustAllArg;
 
     /** The 'trustStore' global argument. */
-    private final StringArgument trustStorePathArg;
+    private StringArgument trustStorePathArg;
 
     /** The 'trustStorePassword' global argument. */
-    private final StringArgument trustStorePasswordArg;
+    private StringArgument trustStorePasswordArg;
 
     /** The 'trustStorePasswordFile' global argument. */
-    private final FileBasedArgument trustStorePasswordFileArg;
+    private FileBasedArgument trustStorePasswordFileArg;
 
     /** The 'keyStore' global argument. */
-    private final StringArgument keyStorePathArg;
+    private StringArgument keyStorePathArg;
 
     /** The 'keyStorePassword' global argument. */
-    private final StringArgument keyStorePasswordArg;
+    private StringArgument keyStorePasswordArg;
 
     /** The 'keyStorePasswordFile' global argument. */
-    private final FileBasedArgument keyStorePasswordFileArg;
+    private FileBasedArgument keyStorePasswordFileArg;
 
     /** The 'certNicknameArg' global argument. */
-    private final StringArgument certNicknameArg;
+    private StringArgument certNicknameArg;
 
     /** The 'useSSLArg' global argument. */
-    private final BooleanArgument useSSLArg;
+    private BooleanArgument useSSLArg;
 
     /** The 'useStartTLSArg' global argument. */
-    private final BooleanArgument useStartTLSArg;
+    private BooleanArgument useStartTLSArg;
 
     /** Argument indicating a SASL option. */
-    private final StringArgument saslOptionArg;
+    private StringArgument saslOptionArg;
 
     /**
      * Whether to request that the server return the authorization ID in the
@@ -427,7 +422,8 @@
                     }
                 }
             } catch (final Exception e) {
-                throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(e.toString()), e);
+                throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(e.toString()),
+                        e);
             }
 
             LDAPOptions options = new LDAPOptions();
@@ -436,7 +432,7 @@
                 options.setSSLContext(sslContext).setUseStartTLS(useStartTLSArg.isPresent());
             }
             options.setConnectTimeout(getConnectTimeout(), TimeUnit.MILLISECONDS);
-            connFactory = newLDAPConnectionFactory(hostNameArg.getValue(), port, options);
+            connFactory = new LDAPConnectionFactory(hostNameArg.getValue(), port, options);
         }
         return connFactory;
     }
@@ -528,9 +524,7 @@
             authenticatedConnFactory = getConnectionFactory();
             final BindRequest bindRequest = getBindRequest();
             if (bindRequest != null) {
-                app.setBindRequest(bindRequest);
-                authenticatedConnFactory = newLDAPConnectionFactory(hostNameArg.getValue(), port,
-                        new LDAPOptions().setBindRequest(bindRequest).setTimeout(DEFAULT_TIMEOUT_SECONDS, SECONDS));
+                authenticatedConnFactory = new AuthenticatedConnectionFactory(authenticatedConnFactory, bindRequest);
             }
         }
         return authenticatedConnFactory;
diff --git a/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
index 7bb5284..e17c6bc 100755
--- a/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
+++ b/opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
@@ -45,7 +45,6 @@
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.ldap.requests.BindRequest;
 
 /**
  * This class provides an abstract base class which can be used as the basis of a console-based application.
@@ -66,8 +65,6 @@
 
     private boolean isProgressSuite;
 
-    private BindRequest bindRequest = null;
-
     /**
      * Defines the different line styles for output.
      */
@@ -119,15 +116,6 @@
     }
 
     /**
-     * Returns the bind request used by this application.
-     *
-     * @return The bind request used by this application.
-     */
-    public BindRequest getBindRequest() {
-        return bindRequest;
-    }
-
-    /**
      * Returns the application error stream.
      *
      * @return The application error stream.
@@ -686,10 +674,6 @@
         throw new ClientException(ReturnCode.ERROR_USER_DATA, ERR_TRIES_LIMIT_REACHED.get(maxTries));
     }
 
-    void setBindRequest(final BindRequest bindRequest) {
-        this.bindRequest = bindRequest;
-    }
-
     /**
      * Inserts line breaks into the provided buffer to wrap text at no more than the specified column width (80).
      *
diff --git a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
index 620688a..ac802e6 100644
--- a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
@@ -77,8 +77,8 @@
     <className>org/forgerock/opendj/ldap/Connections</className>
     <differenceType>7005</differenceType>
     <method>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(org\.forgerock\.opendj\.ldap\.ConnectionFactory, long, java\.util\.concurrent\.TimeUnit, org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(, java\.util\.concurrent\.ScheduledExecutorService)?\)]</method>
-    <to>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(java\.lang\.String,\s*int(,\s*long,\s*long,\s*java\.util\.concurrent\.TimeUnit(,\s*org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(,\s*java\.util\.concurrent\.ScheduledExecutorService)?)?)?\)]</to>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+    <to>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(org\.forgerock\.opendj\.ldap\.ConnectionFactory,\s*long,\s*long,\s*java\.util\.concurrent\.TimeUnit(,\s*org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(,\s*java\.util\.concurrent\.ScheduledExecutorService)?)?\)]</to>
+    <justification>OPENDJ-1058: Added a timeout parameter to actively shutdown dead connections</justification>
   </difference>
   <difference>
     <className>org/forgerock/opendj/ldap/Connections</className>
@@ -114,6 +114,14 @@
     <justification>OPENDJ-1270: Changed constructors to only accept InetSocketAddresses instead of more generic SocketAddress</justification>
   </difference>
   <difference>
+    <className>org/forgerock/opendj/ldap/LDAPListener</className>
+    <differenceType>7005</differenceType>
+    <method>%regex[LDAPListener\(java\.net\.SocketAddress, org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
+    <to>%regex[LDAPListener\(java\.net\.InetSocketAddress,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</to>
+    <justification>OPENDJ-1270: Changed constructors to only accept InetSocketAddresses instead of more generic SocketAddress</justification>
+  </difference>
+
+  <difference>
     <className>%regex[org/forgerock/opendj/ldap/(LDAPOptions|LDAPListenerOptions)]</className>
     <differenceType>7002</differenceType>
     <method>%regex[org\.glassfish\.grizzly\.nio\.transport\.TCPNIOTransport getTCPNIOTransport\(\)]</method>
@@ -381,59 +389,6 @@
     <method>*toNormalizedString()</method>
     <to>*toIrreversibleNormalizedByteString()</to>
     <justification>OPENDJ-1585 Function has been renamed to avoid abuse</justification>
-  </difference>  
-  <difference>
-    <className>%regex[org/forgerock/opendj/ldap/(Authenticated|HeartBeat)ConnectionFactory]</className>
-    <differenceType>8001</differenceType>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/AuthenticatedConnectionFactory$AuthenticatedConnection</className>
-    <differenceType>8001</differenceType>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/Connections</className>
-    <differenceType>7002</differenceType>
-    <method>%regex[org.forgerock.opendj.ldap.ConnectionFactory new(HeartBeat|Authenticated)ConnectionFactory\(org.forgerock.opendj.ldap.ConnectionFactory(.)*\)]</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPConnectionFactory</className>
-    <differenceType>7002</differenceType>
-    <method>LDAPConnectionFactory(java.lang.String, int)</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPListener</className>
-    <differenceType>7002</differenceType>
-    <method>%regex[LDAPListener\((int|java\.net\.SocketAddress),\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPListener</className>
-    <differenceType>7002</differenceType>
-    <method>%regex[LDAPListener\(java.lang.String,\s*int,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPListener</className>
-    <differenceType>7009</differenceType>
-    <method>LDAPListener(int, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.opendj.ldap.LDAPListenerOptions)</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPListener</className>
-    <differenceType>7005</differenceType>
-    <method>LDAPListener(int, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.opendj.ldap.LDAPListenerOptions)</method>
-    <to>LDAPListener(*)</to>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
-  </difference>
-  <difference>
-    <className>org/forgerock/opendj/ldap/LDAPConnectionFactory</className>
-    <differenceType>7009</differenceType>
-    <method>LDAPConnectionFactory(java.lang.String, int, org.forgerock.opendj.ldap.LDAPOptions)</method>
-    <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
   </difference>
   <difference>
     <className>%regex[org/forgerock/opendj/ldap/schema/Schema(Builder)?]</className>
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
new file mode 100644
index 0000000..d15fb31
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
@@ -0,0 +1,181 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.util.promise.AsyncFunction;
+import org.forgerock.util.promise.Function;
+import org.forgerock.util.promise.Promise;
+
+import static org.forgerock.util.Utils.*;
+
+/**
+ * An authenticated connection factory can be used to create pre-authenticated
+ * connections to a Directory Server.
+ * <p>
+ * The connections returned by an authenticated connection factory support all
+ * operations with the exception of Bind requests. Attempts to perform a Bind
+ * will result in an {@code UnsupportedOperationException}.
+ * <p>
+ * If the Bind request fails for some reason (e.g. invalid credentials), then
+ * the connection attempt will fail and an {@link LdapException} will be thrown.
+ */
+final class AuthenticatedConnectionFactory implements ConnectionFactory {
+
+    /**
+     * An authenticated connection supports all operations except Bind
+     * operations.
+     */
+    public static final class AuthenticatedConnection extends AbstractConnectionWrapper<Connection> {
+
+        private AuthenticatedConnection(final Connection connection) {
+            super(connection);
+        }
+
+        /**
+         * Bind operations are not supported by pre-authenticated connections.
+         * These methods will always throw {@code UnsupportedOperationException}.
+         */
+        public LdapPromise<BindResult> bindAsync(final BindRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler,
+                final ResultHandler<? super BindResult> resultHandler) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public BindResult bind(BindRequest request) throws LdapException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public BindResult bind(String name, char[] password) throws LdapException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("AuthenticatedConnection(");
+            builder.append(connection);
+            builder.append(')');
+            return builder.toString();
+        }
+
+    }
+
+    private final BindRequest request;
+    private final ConnectionFactory parentFactory;
+
+    /**
+     * Creates a new authenticated connection factory which will obtain
+     * connections using the provided connection factory and immediately perform
+     * the provided Bind request.
+     *
+     * @param factory
+     *            The connection factory to use for connecting to the Directory
+     *            Server.
+     * @param request
+     *            The Bind request to use for authentication.
+     */
+    AuthenticatedConnectionFactory(final ConnectionFactory factory, final BindRequest request) {
+        this.parentFactory = factory;
+
+        // FIXME: should do a defensive copy.
+        this.request = request;
+    }
+
+    @Override
+    public void close() {
+        // Delegate.
+        parentFactory.close();
+    }
+
+    @Override
+    public Connection getConnection() throws LdapException {
+        final Connection connection = parentFactory.getConnection();
+        boolean bindSucceeded = false;
+        try {
+            connection.bind(request);
+            bindSucceeded = true;
+        } finally {
+            if (!bindSucceeded) {
+                connection.close();
+            }
+        }
+
+        /*
+         * If the bind didn't succeed then an exception will have been thrown
+         * and this line will not be reached.
+         */
+        return new AuthenticatedConnection(connection);
+    }
+
+    @Override
+    public Promise<Connection, LdapException> getConnectionAsync() {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
+        return parentFactory.getConnectionAsync()
+            .thenAsync(
+                    new AsyncFunction<Connection, BindResult, LdapException>() {
+                        @Override
+                        public Promise<BindResult, LdapException> apply(final Connection connection)
+                                throws LdapException {
+                            connectionHolder.set(connection);
+                            return connection.bindAsync(request);
+                        }
+                    }
+            ).then(
+                    new Function<BindResult, Connection, LdapException>() {
+                        @Override
+                        public Connection apply(BindResult result) throws LdapException {
+                            return new AuthenticatedConnection(connectionHolder.get());
+                        }
+                    },
+                    new Function<LdapException, Connection, LdapException>() {
+                        @Override
+                        public Connection apply(LdapException error) throws LdapException {
+                            closeSilently(connectionHolder.get());
+                            throw error;
+                        }
+                    }
+            );
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("AuthenticatedConnectionFactory(");
+        builder.append(parentFactory);
+        builder.append(", ");
+        builder.append(request);
+        builder.append(')');
+        return builder.toString();
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
index e472302..43fc6f1 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
@@ -27,23 +27,51 @@
 
 package org.forgerock.opendj.ldap;
 
-import java.io.IOException;
+import static org.forgerock.opendj.ldap.RequestHandlerFactoryAdapter.adaptRequestHandler;
+
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.util.Reject;
 import org.forgerock.util.promise.Promise;
 
-import static org.forgerock.opendj.ldap.RequestHandlerFactoryAdapter.*;
-
 /**
  * This class contains methods for creating and manipulating connection
  * factories and connections.
  */
 public final class Connections {
+    /**
+     * Creates a new authenticated connection factory which will obtain
+     * connections using the provided connection factory and immediately perform
+     * the provided Bind request.
+     * <p>
+     * The connections returned by an authenticated connection factory support
+     * all operations with the exception of Bind requests. Attempts to perform a
+     * Bind will result in an {@code UnsupportedOperationException}.
+     * <p>
+     * If the Bind request fails for some reason (e.g. invalid credentials),
+     * then the connection attempt will fail and an {@link LdapException}
+     * will be thrown.
+     *
+     * @param factory
+     *            The connection factory to use for connecting to the Directory
+     *            Server.
+     * @param request
+     *            The Bind request to use for authentication.
+     * @return The new connection pool.
+     * @throws NullPointerException
+     *             If {@code factory} or {@code request} was {@code null}.
+     */
+    public static ConnectionFactory newAuthenticatedConnectionFactory(
+            final ConnectionFactory factory, final BindRequest request) {
+        Reject.ifNull(factory, request);
 
+        return new AuthenticatedConnectionFactory(factory, request);
+    }
 
     /**
      * Creates a new connection pool which creates new connections as needed
@@ -215,6 +243,113 @@
     }
 
     /**
+     * Creates a new heart-beat connection factory which will create connections
+     * using the provided connection factory and periodically ping any created
+     * connections in order to detect that they are still alive every 10 seconds
+     * using the default scheduler. Connections will be marked as having failed
+     * if a heart-beat takes longer than 3 seconds.
+     *
+     * @param factory
+     *            The connection factory to use for creating connections.
+     * @return The new heart-beat connection factory.
+     * @throws NullPointerException
+     *             If {@code factory} was {@code null}.
+     */
+    public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory) {
+        return new HeartBeatConnectionFactory(factory, 10, 3, TimeUnit.SECONDS, null, null);
+    }
+
+    /**
+     * Creates a new heart-beat connection factory which will create connections
+     * using the provided connection factory and periodically ping any created
+     * connections in order to detect that they are still alive using the
+     * specified frequency and the default scheduler.
+     *
+     * @param factory
+     *            The connection factory to use for creating connections.
+     * @param interval
+     *            The interval between keepalive pings.
+     * @param timeout
+     *            The heart-beat timeout after which a connection will be marked
+     *            as failed.
+     * @param unit
+     *            The time unit for the interval between keepalive pings.
+     * @return The new heart-beat connection factory.
+     * @throws IllegalArgumentException
+     *             If {@code interval} was negative.
+     * @throws NullPointerException
+     *             If {@code factory} or {@code unit} was {@code null}.
+     */
+    public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
+            final long interval, final long timeout, final TimeUnit unit) {
+        return new HeartBeatConnectionFactory(factory, interval, timeout, unit, null, null);
+    }
+
+    /**
+     * Creates a new heart-beat connection factory which will create connections
+     * using the provided connection factory and periodically ping any created
+     * connections using the specified search request in order to detect that
+     * they are still alive.
+     *
+     * @param factory
+     *            The connection factory to use for creating connections.
+     * @param interval
+     *            The interval between keepalive pings.
+     * @param timeout
+     *            The heart-beat timeout after which a connection will be marked
+     *            as failed.
+     * @param unit
+     *            The time unit for the interval between keepalive pings.
+     * @param heartBeat
+     *            The search request to use for keepalive pings.
+     * @return The new heart-beat connection factory.
+     * @throws IllegalArgumentException
+     *             If {@code interval} was negative.
+     * @throws NullPointerException
+     *             If {@code factory}, {@code unit}, or {@code heartBeat} was
+     *             {@code null}.
+     */
+    public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
+            final long interval, final long timeout, final TimeUnit unit,
+            final SearchRequest heartBeat) {
+        return new HeartBeatConnectionFactory(factory, interval, timeout, unit, heartBeat, null);
+    }
+
+    /**
+     * Creates a new heart-beat connection factory which will create connections
+     * using the provided connection factory and periodically ping any created
+     * connections using the specified search request in order to detect that
+     * they are still alive.
+     *
+     * @param factory
+     *            The connection factory to use for creating connections.
+     * @param interval
+     *            The interval between keepalive pings.
+     * @param timeout
+     *            The heart-beat timeout after which a connection will be marked
+     *            as failed.
+     * @param unit
+     *            The time unit for the interval between keepalive pings.
+     * @param heartBeat
+     *            The search request to use for keepalive pings.
+     * @param scheduler
+     *            The scheduler which should for periodically sending keepalive
+     *            pings.
+     * @return The new heart-beat connection factory.
+     * @throws IllegalArgumentException
+     *             If {@code interval} was negative.
+     * @throws NullPointerException
+     *             If {@code factory}, {@code unit}, or {@code heartBeat} was
+     *             {@code null}.
+     */
+    public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
+            final long interval, final long timeout, final TimeUnit unit,
+            final SearchRequest heartBeat, final ScheduledExecutorService scheduler) {
+        return new HeartBeatConnectionFactory(factory, interval, timeout, unit, heartBeat,
+                scheduler);
+    }
+
+    /**
      * Creates a new internal client connection which will route requests to the
      * provided {@code RequestHandler}.
      * <p>
@@ -366,181 +501,6 @@
     }
 
     /**
-     * Creates a new LDAP connection factory which can be used to create LDAP
-     * connections to the Directory Server at the provided host and port
-     * number.
-     *
-     * @param host
-     *            The host name.
-     * @param port
-     *            The port number.
-     * @return The connection factory to use for creating connections.
-     */
-    public static LDAPConnectionFactory newLDAPConnectionFactory(final String host, final int port) {
-        return new LDAPConnectionFactory(host, port, new LDAPOptions());
-    }
-
-    /**
-     * Creates a new LDAP connection factory which can be used to create LDAP
-     * connections to the Directory Server at the provided host and port
-     * number.
-     *
-     * @param host
-     *            The host name.
-     * @param port
-     *            The port number.
-     * @param options
-     *            The LDAP options to use when creating connections.
-     * @return The connection factory to use for creating connections.
-     */
-    public static LDAPConnectionFactory newLDAPConnectionFactory(final String host, final int port,
-            final LDAPOptions options) {
-        return new LDAPConnectionFactory(host, port, options);
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param port
-     *            The port to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {code factory} was {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final int port,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
-        return newLDAPListener(null, port, factory, new LDAPListenerOptions());
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param port
-     *            The port to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @param options
-     *            The LDAP listener options.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {code factory} or {@code options} was {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final int port,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
-            final LDAPListenerOptions options) throws IOException {
-        return newLDAPListener(null, port, factory, options);
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param host
-     *            The address to listen on.
-     * @param port
-     *            The port to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {@code host} or {code factory} was {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final String host, final int port,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
-        return newLDAPListener(host, port, factory, new LDAPListenerOptions());
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param host
-     *            The address to listen on.
-     * @param port
-     *            The port to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @param options
-     *            The LDAP listener options.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {@code host}, {code factory}, or {@code options} was
-     *             {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final String host, final int port,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
-            final LDAPListenerOptions options) throws IOException {
-        return newLDAPListener(host != null ? new InetSocketAddress(host, port) : new InetSocketAddress(port),
-                factory, options);
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param address
-     *            The address to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {@code address} or {code factory} was {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final InetSocketAddress address,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
-        return newLDAPListener(address, factory, new LDAPListenerOptions());
-    }
-
-    /**
-     * Creates a new LDAP listener implementation which will listen for LDAP
-     * client connections at the provided address.
-     *
-     * @param address
-     *            The address to listen on.
-     * @param factory
-     *            The server connection factory which will be used to create
-     *            server connections.
-     * @param options
-     *            The LDAP listener options.
-     * @throws IOException
-     *             If an error occurred while trying to listen on the provided
-     *             address.
-     * @throws NullPointerException
-     *             If {@code address}, {code factory}, or {@code options} was
-     *             {@code null}.
-     * @return The LDAP listener implementation which will listen for LDAP client connections.
-     */
-    public static LDAPListener newLDAPListener(final InetSocketAddress address,
-            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
-            final LDAPListenerOptions options) throws IOException {
-        return new LDAPListener(address, factory, options);
-    }
-
-    /**
      * Creates a new load balancer which will obtain connections using the
      * provided load balancing algorithm.
      *
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
new file mode 100644
index 0000000..ea590dd
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
@@ -0,0 +1,998 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+import org.forgerock.opendj.ldap.spi.ConnectionState;
+import org.forgerock.opendj.ldap.spi.LdapPromiseImpl;
+import org.forgerock.util.Reject;
+import org.forgerock.util.promise.AsyncFunction;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.PromiseImpl;
+import org.forgerock.util.promise.SuccessHandler;
+
+import com.forgerock.opendj.util.ReferenceCountedObject;
+import org.forgerock.util.time.TimeService;
+
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
+import static org.forgerock.util.Utils.*;
+
+import static com.forgerock.opendj.ldap.CoreMessages.*;
+import static com.forgerock.opendj.util.StaticUtils.*;
+
+/**
+ * An heart beat connection factory can be used to create connections that sends
+ * a periodic search request to a Directory Server.
+ * <p>
+ * Before returning new connections to the application this factory will first
+ * send an initial heart beat in order to determine that the remote server is
+ * responsive. If the heart beat fails or times out then the connection is
+ * closed immediately and an error returned to the client.
+ * <p>
+ * Once a connection has been established successfully (including the initial
+ * heart beat), this factory will periodically send heart beats on the
+ * connection based on the configured heart beat interval. If the heart beat
+ * times out then the server is assumed to be down and an appropriate
+ * {@link ConnectionException} generated and published to any registered
+ * {@link ConnectionEventListener}s. Note however, that heart beats will only be
+ * sent when the connection is determined to be reasonably idle: there is no
+ * point in sending heart beats if the connection has recently received a
+ * response. A connection is deemed to be idle if no response has been received
+ * during a period equivalent to half the heart beat interval.
+ * <p>
+ * The LDAP protocol specifically precludes clients from performing operations
+ * while bind or startTLS requests are being performed. Likewise, a bind or
+ * startTLS request will cause active operations to be aborted. This factory
+ * coordinates heart beats with bind or startTLS requests, ensuring that they
+ * are not performed concurrently. Specifically, bind and startTLS requests are
+ * queued up while a heart beat is pending, and heart beats are not sent at all
+ * while there are pending bind or startTLS requests.
+ */
+final class HeartBeatConnectionFactory implements ConnectionFactory {
+    /**
+     * A connection that sends heart beats and supports all operations.
+     */
+    private final class ConnectionImpl extends AbstractAsynchronousConnection implements ConnectionEventListener {
+
+        private class LdapPromiseImplWrapper<R> extends LdapPromiseImpl<R> {
+            protected LdapPromiseImplWrapper(final LdapPromise<R> wrappedPromise) {
+                super(new PromiseImpl<R, LdapException>() {
+                    @Override
+                    protected LdapException tryCancel(boolean mayInterruptIfRunning) {
+                        /*
+                         * FIXME: if the inner cancel succeeds then this promise will be
+                         * completed and we can never indicate that this cancel request
+                         * has succeeded.
+                         */
+                        wrappedPromise.cancel(mayInterruptIfRunning);
+                        return null;
+                    }
+                }, wrappedPromise.getRequestID());
+            }
+        }
+
+        /** The wrapped connection. */
+        private final Connection connection;
+
+        /**
+         * List of pending Bind or StartTLS requests which must be invoked once
+         * the current heart beat completes.
+         */
+        private final Queue<Runnable> pendingBindOrStartTLSRequests =
+                new ConcurrentLinkedQueue<Runnable>();
+
+        /**
+         * List of pending responses for all active operations. These will be
+         * signalled if no heart beat is detected within the permitted timeout
+         * period.
+         */
+        private final Queue<ResultHandler<?>> pendingResults =
+                new ConcurrentLinkedQueue<ResultHandler<?>>();
+
+        /** Internal connection state. */
+        private final ConnectionState state = new ConnectionState();
+
+        /** Coordinates heart-beats with Bind and StartTLS requests. */
+        private final Sync sync = new Sync();
+
+        /**
+         * Timestamp of last response received (any response, not just heart
+         * beats).
+         */
+        private volatile long lastResponseTimestamp = timeService.now(); // Assume valid at creation.
+
+        private ConnectionImpl(final Connection connection) {
+            this.connection = connection;
+            connection.addConnectionEventListener(this);
+        }
+
+        @Override
+        public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
+            return connection.abandonAsync(request);
+        }
+
+        @Override
+        public LdapPromise<Result> addAsync(final AddRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                return timestampPromise(connection.addAsync(request, intermediateResponseHandler));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public void addConnectionEventListener(final ConnectionEventListener listener) {
+            state.addConnectionEventListener(listener);
+        }
+
+        @Override
+        public LdapPromise<BindResult> bindAsync(final BindRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                if (sync.tryLockShared()) {
+                    // Fast path
+                    return timestampBindOrStartTLSPromise(connection.bindAsync(request, intermediateResponseHandler));
+                } else {
+                    return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() {
+                        @Override
+                        public Promise<BindResult, LdapException> apply(Void value) throws LdapException {
+                            return timestampBindOrStartTLSPromise(connection.bindAsync(request,
+                                    intermediateResponseHandler));
+                        }
+                    });
+                }
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public void close() {
+            handleConnectionClosed();
+            connection.close();
+        }
+
+        @Override
+        public void close(final UnbindRequest request, final String reason) {
+            handleConnectionClosed();
+            connection.close(request, reason);
+        }
+
+        @Override
+        public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                return timestampPromise(connection.compareAsync(request, intermediateResponseHandler));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public LdapPromise<Result> deleteAsync(final DeleteRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                return timestampPromise(connection.deleteAsync(request, intermediateResponseHandler));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                if (isStartTLSRequest(request)) {
+                    if (sync.tryLockShared()) {
+                        // Fast path
+                        return timestampBindOrStartTLSPromise(connection.extendedRequestAsync(request,
+                                intermediateResponseHandler));
+                    } else {
+                        return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() {
+                            @Override
+                            public Promise<R, LdapException> apply(Void value) throws LdapException {
+                                return timestampBindOrStartTLSPromise(connection.extendedRequestAsync(
+                                        request, intermediateResponseHandler));
+                            }
+                        });
+                    }
+                } else {
+                    return timestampPromise(connection.extendedRequestAsync(request, intermediateResponseHandler));
+                }
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public void handleConnectionClosed() {
+            if (state.notifyConnectionClosed()) {
+                failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED,
+                        HBCF_CONNECTION_CLOSED_BY_CLIENT.get()));
+                synchronized (validConnections) {
+                    connection.removeConnectionEventListener(this);
+                    validConnections.remove(this);
+                    if (validConnections.isEmpty()) {
+                        // This is the last active connection, so stop the heartbeat.
+                        heartBeatFuture.cancel(false);
+                    }
+                }
+                releaseScheduler();
+            }
+        }
+
+        @Override
+        public void handleConnectionError(final boolean isDisconnectNotification, final LdapException error) {
+            if (state.notifyConnectionError(isDisconnectNotification, error)) {
+                failPendingResults(error);
+            }
+        }
+
+        @Override
+        public void handleUnsolicitedNotification(final ExtendedResult notification) {
+            timestamp(notification);
+            state.notifyUnsolicitedNotification(notification);
+        }
+
+        @Override
+        public boolean isClosed() {
+            return state.isClosed();
+        }
+
+        @Override
+        public boolean isValid() {
+            return state.isValid() && connection.isValid();
+        }
+
+        @Override
+        public LdapPromise<Result> modifyAsync(final ModifyRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                return timestampPromise(connection.modifyAsync(request, intermediateResponseHandler));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            if (checkState()) {
+                return timestampPromise(connection.modifyDNAsync(request, intermediateResponseHandler));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public void removeConnectionEventListener(final ConnectionEventListener listener) {
+            state.removeConnectionEventListener(listener);
+        }
+
+        @Override
+        public LdapPromise<Result> searchAsync(final SearchRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler,
+                final SearchResultHandler searchHandler) {
+            if (checkState()) {
+                final AtomicBoolean searchDone = new AtomicBoolean();
+                final SearchResultHandler entryHandler = new SearchResultHandler() {
+                    @Override
+                    public synchronized boolean handleEntry(SearchResultEntry entry) {
+                        if (!searchDone.get()) {
+                            timestamp(entry);
+                            if (searchHandler != null) {
+                                searchHandler.handleEntry(entry);
+                            }
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public synchronized boolean handleReference(SearchResultReference reference) {
+                        if (!searchDone.get()) {
+                            timestamp(reference);
+                            if (searchHandler != null) {
+                                searchHandler.handleReference(reference);
+                            }
+                        }
+                        return true;
+                    }
+                };
+
+                return timestampPromise(connection.searchAsync(request,
+                        intermediateResponseHandler, entryHandler).onSuccessOrFailure(new Runnable() {
+                            @Override
+                            public void run() {
+                                searchDone.getAndSet(true);
+                            }
+                        }));
+            } else {
+                return newConnectionErrorPromise();
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("HeartBeatConnection(");
+            builder.append(connection);
+            builder.append(')');
+            return builder.toString();
+        }
+
+        private void checkForHeartBeat() {
+            if (sync.isHeld()) {
+                /*
+                 * A heart beat or bind/startTLS is still in progress, but it
+                 * should have completed by now. Let's avoid aggressively
+                 * terminating the connection, because the heart beat may simply
+                 * have been delayed by a sudden surge of activity. Therefore,
+                 * only flag the connection as failed if no activity has been
+                 * seen on the connection since the heart beat was sent.
+                 */
+                final long currentTimeMillis = timeService.now();
+                if (lastResponseTimestamp < (currentTimeMillis - timeoutMS)) {
+                    logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", connection));
+                    handleConnectionError(false, newHeartBeatTimeoutError());
+                }
+            }
+        }
+
+        private boolean checkState() {
+            return state.getConnectionError() == null;
+        }
+
+        private void failPendingResults(final LdapException error) {
+            /*
+             * Peek instead of pool because notification is responsible for
+             * removing the element from the queue.
+             */
+            ResultHandler<?> pendingResult;
+            while ((pendingResult = pendingResults.peek()) != null) {
+                pendingResult.handleError(error);
+            }
+        }
+
+        private void flushPendingBindOrStartTLSRequests() {
+            if (!pendingBindOrStartTLSRequests.isEmpty()) {
+                /*
+                 * The pending requests will acquire the shared lock, but we take
+                 * it here anyway to ensure that pending requests do not get blocked.
+                 */
+                if (sync.tryLockShared()) {
+                    try {
+                        Runnable pendingRequest;
+                        while ((pendingRequest = pendingBindOrStartTLSRequests.poll()) != null) {
+                            // Dispatch the waiting request. This will not block.
+                            pendingRequest.run();
+                        }
+                    } finally {
+                        sync.unlockShared();
+                    }
+                }
+            }
+        }
+
+        private boolean isStartTLSRequest(final ExtendedRequest<?> request) {
+            return request.getOID().equals(StartTLSExtendedRequest.OID);
+        }
+
+        private <R> LdapPromise<R> newConnectionErrorPromise() {
+            return newFailedLdapPromise(state.getConnectionError());
+        }
+
+        private void releaseBindOrStartTLSLock() {
+            sync.unlockShared();
+        }
+
+        private void releaseHeartBeatLock() {
+            sync.unlockExclusively();
+            flushPendingBindOrStartTLSRequests();
+        }
+
+        /**
+         * Sends a heart beat on this connection if required to do so.
+         *
+         * @return {@code true} if a heart beat was sent, otherwise {@code false}.
+         */
+        private boolean sendHeartBeat() {
+            /*
+             * Don't attempt to send a heart beat if the connection has already
+             * failed.
+             */
+            if (!state.isValid()) {
+                return false;
+            }
+
+            /*
+             * Only send the heart beat if the connection has been idle for some
+             * time.
+             */
+            final long currentTimeMillis = timeService.now();
+            if (currentTimeMillis < (lastResponseTimestamp + minDelayMS)) {
+                return false;
+            }
+
+            /*
+             * Don't send a heart beat if there is already a heart beat, bind,
+             * or startTLS in progress. Note that the bind/startTLS response
+             * will update the lastResponseTimestamp as if it were a heart beat.
+             */
+            if (sync.tryLockExclusively()) {
+                try {
+                    connection.searchAsync(heartBeatRequest, new SearchResultHandler() {
+                        @Override
+                        public boolean handleEntry(final SearchResultEntry entry) {
+                            timestamp(entry);
+                            return true;
+                        }
+
+                        @Override
+                        public boolean handleReference(final SearchResultReference reference) {
+                            timestamp(reference);
+                            return true;
+                        }
+                    }).onSuccess(new SuccessHandler<Result>() {
+                        @Override
+                        public void handleResult(Result result) {
+                            timestamp(result);
+                            releaseHeartBeatLock();
+                        }
+                    }).onFailure(new FailureHandler<LdapException>() {
+                        @Override
+                        public void handleError(LdapException error) {
+                            /*
+                             * Connection failure will be handled by connection
+                             * event listener. Ignore cancellation errors since
+                             * these indicate that the heart beat was aborted by
+                             * a client-side close.
+                             */
+                            if (!(error instanceof CancelledResultException)) {
+                                /*
+                                 * Log at debug level to avoid polluting the
+                                 * logs with benign password policy related
+                                 * errors. See OPENDJ-1168 and OPENDJ-1167.
+                                 */
+                                logger.debug(LocalizableMessage.raw("Heartbeat failed for connection factory '%s'",
+                                        factory, error));
+                                timestamp(error);
+                            }
+                            releaseHeartBeatLock();
+                        }
+                    });
+                } catch (final IllegalStateException e) {
+                    /*
+                     * This may happen when we attempt to send the heart beat
+                     * just after the connection is closed but before we are
+                     * notified.
+                     */
+
+                    /*
+                     * Release the lock because we're never going to get a
+                     * response.
+                     */
+                    releaseHeartBeatLock();
+                }
+            }
+            /*
+             * Indicate that a the heartbeat should be checked even if a
+             * bind/startTLS is in progress, since these operations will
+             * effectively act as the heartbeat.
+             */
+            return true;
+        }
+
+        private <R> R timestamp(final R response) {
+            if (!(response instanceof ConnectionException)) {
+                lastResponseTimestamp = timeService.now();
+            }
+            return response;
+        }
+
+        private <R extends Result> LdapPromise<R> enqueueBindOrStartTLSPromise(
+                AsyncFunction<Void, R, LdapException> doRequest) {
+            /*
+             * A heart beat must be in progress so create a runnable task which
+             * will be executed when the heart beat completes.
+             */
+            final LdapPromiseImpl<Void> promise = newLdapPromiseImpl();
+            final LdapPromise<R> result = promise.thenAsync(doRequest);
+
+            // Enqueue and flush if the heart beat has completed in the mean
+            // time.
+            pendingBindOrStartTLSRequests.offer(new Runnable() {
+                @Override
+                public void run() {
+                    // FIXME: Handle cancel chaining.
+                    if (!result.isCancelled()) {
+                        sync.lockShared(); // Will not block.
+                        promise.handleResult((Void) null);
+                    }
+                }
+            });
+            flushPendingBindOrStartTLSRequests();
+            return result;
+        }
+
+        private <R extends Result> LdapPromise<R> timestampPromise(LdapPromise<R> wrappedPromise) {
+            final LdapPromiseImpl<R> outerPromise = new LdapPromiseImplWrapper<R>(wrappedPromise);
+            pendingResults.add(outerPromise);
+            wrappedPromise.onSuccess(new SuccessHandler<R>() {
+                @Override
+                public void handleResult(R result) {
+                    outerPromise.handleResult(result);
+                    timestamp(result);
+                }
+            }).onFailure(new FailureHandler<LdapException>() {
+                @Override
+                public void handleError(LdapException error) {
+                    outerPromise.handleError(error);
+                    timestamp(error);
+                }
+            });
+            outerPromise.onSuccessOrFailure(new Runnable() {
+                @Override
+                public void run() {
+                    pendingResults.remove(outerPromise);
+                }
+            });
+            if (!checkState()) {
+                outerPromise.handleError(state.getConnectionError());
+            }
+            return outerPromise;
+        }
+
+        private <R extends Result> LdapPromise<R> timestampBindOrStartTLSPromise(LdapPromise<R> wrappedPromise) {
+            return timestampPromise(wrappedPromise).onSuccessOrFailure(new Runnable() {
+                @Override
+                public void run() {
+                    releaseBindOrStartTLSLock();
+                }
+            });
+        }
+    }
+
+    /**
+     * This synchronizer prevents Bind or StartTLS operations from being
+     * processed concurrently with heart-beats. This is required because the
+     * LDAP protocol specifically states that servers receiving a Bind operation
+     * should either wait for existing operations to complete or abandon them.
+     * The same presumably applies to StartTLS operations. Note that concurrent
+     * bind/StartTLS operations are not permitted.
+     * <p>
+     * This connection factory only coordinates Bind and StartTLS requests with
+     * heart-beats. It does not attempt to prevent or control attempts to send
+     * multiple concurrent Bind or StartTLS operations, etc.
+     * <p>
+     * This synchronizer can be thought of as cross between a read-write lock
+     * and a semaphore. Unlike a read-write lock there is no requirement that a
+     * thread releasing a lock must hold it. In addition, this synchronizer does
+     * not support reentrancy. A thread attempting to acquire exclusively more
+     * than once will deadlock, and a thread attempting to acquire shared more
+     * than once will succeed and be required to release an equivalent number of
+     * times.
+     * <p>
+     * The synchronizer has three states:
+     * <ul>
+     * <li>UNLOCKED(0) - the synchronizer may be acquired shared or exclusively
+     * <li>LOCKED_EXCLUSIVELY(-1) - the synchronizer is held exclusively and
+     * cannot be acquired shared or exclusively. An exclusive lock is held while
+     * a heart beat is in progress
+     * <li>LOCKED_SHARED(>0) - the synchronizer is held shared and cannot be
+     * acquired exclusively. N shared locks are held while N Bind or StartTLS
+     * operations are in progress.
+     * </ul>
+     */
+    private static final class Sync extends AbstractQueuedSynchronizer {
+        private static final int LOCKED_EXCLUSIVELY = -1;
+        /** Keep compiler quiet. */
+        private static final long serialVersionUID = -3590428415442668336L;
+
+        /** Lock states. Positive values indicate that the shared lock is taken. */
+        private static final int UNLOCKED = 0; // initial state
+
+        @Override
+        protected boolean isHeldExclusively() {
+            return getState() == LOCKED_EXCLUSIVELY;
+        }
+
+        boolean isHeld() {
+            return getState() != 0;
+        }
+
+        @Override
+        protected boolean tryAcquire(final int ignored) {
+            if (compareAndSetState(UNLOCKED, LOCKED_EXCLUSIVELY)) {
+                setExclusiveOwnerThread(Thread.currentThread());
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected int tryAcquireShared(final int readers) {
+            for (;;) {
+                final int state = getState();
+                if (state == LOCKED_EXCLUSIVELY) {
+                    return LOCKED_EXCLUSIVELY; // failed
+                }
+                final int newState = state + readers;
+                if (compareAndSetState(state, newState)) {
+                    return newState; // succeeded + more readers allowed
+                }
+            }
+        }
+
+        @Override
+        protected boolean tryRelease(final int ignored) {
+            if (getState() != LOCKED_EXCLUSIVELY) {
+                throw new IllegalMonitorStateException();
+            }
+            setExclusiveOwnerThread(null);
+            setState(UNLOCKED);
+            return true;
+        }
+
+        @Override
+        protected boolean tryReleaseShared(final int ignored) {
+            for (;;) {
+                final int state = getState();
+                if (state == UNLOCKED || state == LOCKED_EXCLUSIVELY) {
+                    throw new IllegalMonitorStateException();
+                }
+                final int newState = state - 1;
+                if (compareAndSetState(state, newState)) {
+                    /*
+                     * We could always return true here, but since there cannot
+                     * be waiting readers we can specialize for waiting writers.
+                     */
+                    return newState == UNLOCKED;
+                }
+            }
+        }
+
+        void lockShared() {
+            acquireShared(1);
+        }
+
+        boolean tryLockExclusively() {
+            return tryAcquire(0 /* unused */);
+        }
+
+        boolean tryLockShared() {
+            return tryAcquireShared(1) > 0;
+        }
+
+        void unlockExclusively() {
+            release(0 /* unused */);
+        }
+
+        void unlockShared() {
+            releaseShared(0 /* unused */);
+        }
+
+    }
+
+    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+    /**
+     * Default heart beat which will target the root DSE but not return any
+     * results.
+     */
+    private static final SearchRequest DEFAULT_SEARCH = Requests.newSearchRequest("",
+            SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1");
+
+    /**
+     * This is package private in order to allow unit tests to inject fake time
+     * stamps.
+     */
+    TimeService timeService = TimeService.SYSTEM;
+
+    /**
+     * Scheduled task which checks that all heart beats have been received
+     * within the timeout period.
+     */
+    private final Runnable checkHeartBeatRunnable = new Runnable() {
+        @Override
+        public void run() {
+            for (final ConnectionImpl connection : getValidConnections()) {
+                connection.checkForHeartBeat();
+            }
+        }
+    };
+
+    /**
+     * Underlying connection factory.
+     */
+    private final ConnectionFactory factory;
+
+    /**
+     * The heartbeat scheduled future - which may be null if heartbeats are not
+     * being sent (no valid connections).
+     */
+    private ScheduledFuture<?> heartBeatFuture;
+
+    /**
+     * The heartbeat search request.
+     */
+    private final SearchRequest heartBeatRequest;
+
+    /**
+     * The interval between successive heartbeats.
+     */
+    private final long interval;
+    private final TimeUnit intervalUnit;
+
+    /**
+     * Flag which indicates whether this factory has been closed.
+     */
+    private final AtomicBoolean isClosed = new AtomicBoolean();
+
+    /**
+     * The minimum amount of time the connection should remain idle (no
+     * responses) before starting to send heartbeats.
+     */
+    private final long minDelayMS;
+
+    /**
+     * Prevents the scheduler being released when there are remaining references
+     * (this factory or any connections). It is initially set to 1 because this
+     * factory has a reference.
+     */
+    private final AtomicInteger referenceCount = new AtomicInteger(1);
+
+    /**
+     * The heartbeat scheduler.
+     */
+    private final ReferenceCountedObject<ScheduledExecutorService>.Reference scheduler;
+
+
+    /**
+     * Scheduled task which sends heart beats for all valid idle connections.
+     */
+    private final Runnable sendHeartBeatRunnable = new Runnable() {
+        @Override
+        public void run() {
+            boolean heartBeatSent = false;
+            for (final ConnectionImpl connection : getValidConnections()) {
+                heartBeatSent |= connection.sendHeartBeat();
+            }
+            if (heartBeatSent) {
+                scheduler.get().schedule(checkHeartBeatRunnable, timeoutMS, TimeUnit.MILLISECONDS);
+            }
+        }
+    };
+
+    /**
+     * The heartbeat timeout in milli-seconds. The connection will be marked as
+     * failed if no heartbeat response is received within the timeout.
+     */
+    private final long timeoutMS;
+
+    /**
+     * List of valid connections to which heartbeats will be sent.
+     */
+    private final List<ConnectionImpl> validConnections = new LinkedList<ConnectionImpl>();
+
+    HeartBeatConnectionFactory(final ConnectionFactory factory, final long interval,
+            final long timeout, final TimeUnit unit, final SearchRequest heartBeat,
+            final ScheduledExecutorService scheduler) {
+        Reject.ifNull(factory, unit);
+        Reject.ifFalse(interval >= 0, "negative interval");
+        Reject.ifFalse(timeout >= 0, "negative timeout");
+
+        this.heartBeatRequest = heartBeat != null ? heartBeat : DEFAULT_SEARCH;
+        this.interval = interval;
+        this.intervalUnit = unit;
+        this.factory = factory;
+        this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(scheduler);
+        this.timeoutMS = unit.toMillis(timeout);
+        this.minDelayMS = unit.toMillis(interval) / 2;
+    }
+
+    @Override
+    public void close() {
+        if (isClosed.compareAndSet(false, true)) {
+            synchronized (validConnections) {
+                if (!validConnections.isEmpty()) {
+                    logger.debug(LocalizableMessage.raw(
+                            "HeartbeatConnectionFactory '%s' is closing while %d active connections remain",
+                            this, validConnections.size()));
+                }
+            }
+            releaseScheduler();
+            factory.close();
+        }
+    }
+
+    private void releaseScheduler() {
+        if (referenceCount.decrementAndGet() == 0) {
+            scheduler.release();
+        }
+    }
+
+    private void acquireScheduler() {
+        /*
+         * If the factory is not closed then we need to prevent the scheduler
+         * from being released while the connection attempt is in progress.
+         */
+        referenceCount.incrementAndGet();
+        if (isClosed.get()) {
+            releaseScheduler();
+            throw new IllegalStateException("Attempted to get a connection after factory close");
+        }
+    }
+
+    @Override
+    public Connection getConnection() throws LdapException {
+        /*
+         * Immediately send a heart beat in order to determine if the connected
+         * server is responding.
+         */
+        acquireScheduler(); // Protect scheduler.
+        Connection connection = null;
+        try {
+            connection = factory.getConnection();
+            connection.searchAsync(heartBeatRequest, null).getOrThrow(timeoutMS, TimeUnit.MILLISECONDS);
+            return registerConnection(new ConnectionImpl(connection));
+        } catch (final Exception e) {
+            closeSilently(connection);
+            releaseScheduler();
+            throw adaptHeartBeatError(e);
+        }
+    }
+
+    @Override
+    public Promise<Connection, LdapException> getConnectionAsync() {
+        acquireScheduler(); // Protect scheduler.
+
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
+        final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
+
+        // Request a connection and return the promise representing the heartbeat.
+        factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
+            @Override
+            public Promise<Result, LdapException> apply(final Connection connection) {
+                // Save the connection for later once the heart beat completes.
+                connectionHolder.set(connection);
+                scheduler.get().schedule(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (promise.tryHandleError(newHeartBeatTimeoutError())) {
+                            closeSilently(connectionHolder.get());
+                            releaseScheduler();
+                        }
+                    }
+                }, timeoutMS, TimeUnit.MILLISECONDS);
+                return connection.searchAsync(heartBeatRequest, null);
+            }
+        }).onSuccess(new SuccessHandler<Result>() {
+            @Override
+            public void handleResult(Result result) {
+                final Connection connection = connectionHolder.get();
+                final ConnectionImpl connectionImpl = new ConnectionImpl(connection);
+                if (!promise.tryHandleResult(registerConnection(connectionImpl))) {
+                    connectionImpl.close();
+                }
+            }
+        }).onFailure(new FailureHandler<LdapException>() {
+            @Override
+            public void handleError(LdapException error) {
+                if (promise.tryHandleError(adaptHeartBeatError(error))) {
+                    closeSilently(connectionHolder.get());
+                    releaseScheduler();
+                }
+            }
+        });
+
+        return promise;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("HeartBeatConnectionFactory(");
+        builder.append(factory);
+        builder.append(')');
+        return builder.toString();
+    }
+
+    private Connection registerConnection(final ConnectionImpl heartBeatConnection) {
+        synchronized (validConnections) {
+            if (validConnections.isEmpty()) {
+                /* This is the first active connection, so start the heart beat. */
+                heartBeatFuture =
+                        scheduler.get().scheduleWithFixedDelay(sendHeartBeatRunnable, 0, interval, intervalUnit);
+            }
+            validConnections.add(heartBeatConnection);
+            return heartBeatConnection;
+        }
+    }
+
+    private LdapException adaptHeartBeatError(final Exception error) {
+        if (error instanceof ConnectionException) {
+            return (LdapException) error;
+        } else if (error instanceof TimeoutResultException || error instanceof TimeoutException) {
+            return newHeartBeatTimeoutError();
+        } else if (error instanceof InterruptedException) {
+            return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, error);
+        } else {
+            return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_FAILED.get(), error);
+        }
+    }
+
+    private ConnectionImpl[] getValidConnections() {
+        final ConnectionImpl[] tmp;
+        synchronized (validConnections) {
+            tmp = validConnections.toArray(new ConnectionImpl[0]);
+        }
+        return tmp;
+    }
+
+    private LdapException newHeartBeatTimeoutError() {
+        return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_TIMEOUT
+                .get(timeoutMS));
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
index 427723a..2c9ce2b 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -59,6 +59,24 @@
      *            The host name.
      * @param port
      *            The port number.
+     * @throws NullPointerException
+     *             If {@code host} was {@code null}.
+     * @throws ProviderNotFoundException if no provider is available or if the
+     *             provider requested using options is not found.
+     */
+    public LDAPConnectionFactory(final String host, final int port) {
+        this(host, port, new LDAPOptions());
+    }
+
+    /**
+     * Creates a new LDAP connection factory which can be used to create LDAP
+     * connections to the Directory Server at the provided host and port
+     * number.
+     *
+     * @param host
+     *            The host name.
+     * @param port
+     *            The port number.
      * @param options
      *            The LDAP options to use when creating connections.
      * @throws NullPointerException
@@ -66,7 +84,7 @@
      * @throws ProviderNotFoundException if no provider is available or if the
      *             provider requested using options is not found.
      */
-    LDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
+    public LDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
         Reject.ifNull(host, options);
         this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
                 options.getProviderClassLoader());
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
index 8e1748d..736de1d 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
@@ -103,7 +103,74 @@
     /**
      * Transport provider that provides the implementation of this listener.
      */
-    private final TransportProvider provider;
+    private TransportProvider provider;
+
+    /**
+     * Creates a new LDAP listener implementation which will listen for LDAP
+     * client connections at the provided address.
+     *
+     * @param port
+     *            The port to listen on.
+     * @param factory
+     *            The server connection factory which will be used to create
+     *            server connections.
+     * @throws IOException
+     *             If an error occurred while trying to listen on the provided
+     *             address.
+     * @throws NullPointerException
+     *             If {code factory} was {@code null}.
+     */
+    public LDAPListener(final int port,
+            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+        this(port, factory, new LDAPListenerOptions());
+    }
+
+    /**
+     * Creates a new LDAP listener implementation which will listen for LDAP
+     * client connections at the provided address.
+     *
+     * @param port
+     *            The port to listen on.
+     * @param factory
+     *            The server connection factory which will be used to create
+     *            server connections.
+     * @param options
+     *            The LDAP listener options.
+     * @throws IOException
+     *             If an error occurred while trying to listen on the provided
+     *             address.
+     * @throws NullPointerException
+     *             If {code factory} or {@code options} was {@code null}.
+     */
+    public LDAPListener(final int port,
+            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+            final LDAPListenerOptions options) throws IOException {
+        Reject.ifNull(factory, options);
+        final InetSocketAddress address = new InetSocketAddress(port);
+        this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
+                options.getProviderClassLoader());
+        this.impl = provider.getLDAPListener(address, factory, options);
+    }
+
+    /**
+     * Creates a new LDAP listener implementation which will listen for LDAP
+     * client connections at the provided address.
+     *
+     * @param address
+     *            The address to listen on.
+     * @param factory
+     *            The server connection factory which will be used to create
+     *            server connections.
+     * @throws IOException
+     *             If an error occurred while trying to listen on the provided
+     *             address.
+     * @throws NullPointerException
+     *             If {@code address} or {code factory} was {@code null}.
+     */
+    public LDAPListener(final InetSocketAddress address,
+            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+        this(address, factory, new LDAPListenerOptions());
+    }
 
     /**
      * Creates a new LDAP listener implementation which will listen for LDAP
@@ -123,7 +190,7 @@
      *             If {@code address}, {code factory}, or {@code options} was
      *             {@code null}.
      */
-    LDAPListener(final InetSocketAddress address,
+    public LDAPListener(final InetSocketAddress address,
             final ServerConnectionFactory<LDAPClientContext, Integer> factory,
             final LDAPListenerOptions options) throws IOException {
         Reject.ifNull(address, factory, options);
@@ -133,6 +200,58 @@
     }
 
     /**
+     * Creates a new LDAP listener implementation which will listen for LDAP
+     * client connections at the provided address.
+     *
+     * @param host
+     *            The address to listen on.
+     * @param port
+     *            The port to listen on.
+     * @param factory
+     *            The server connection factory which will be used to create
+     *            server connections.
+     * @throws IOException
+     *             If an error occurred while trying to listen on the provided
+     *             address.
+     * @throws NullPointerException
+     *             If {@code host} or {code factory} was {@code null}.
+     */
+    public LDAPListener(final String host, final int port,
+            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+        this(host, port, factory, new LDAPListenerOptions());
+    }
+
+    /**
+     * Creates a new LDAP listener implementation which will listen for LDAP
+     * client connections at the provided address.
+     *
+     * @param host
+     *            The address to listen on.
+     * @param port
+     *            The port to listen on.
+     * @param factory
+     *            The server connection factory which will be used to create
+     *            server connections.
+     * @param options
+     *            The LDAP listener options.
+     * @throws IOException
+     *             If an error occurred while trying to listen on the provided
+     *             address.
+     * @throws NullPointerException
+     *             If {@code host}, {code factory}, or {@code options} was
+     *             {@code null}.
+     */
+    public LDAPListener(final String host, final int port,
+            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+            final LDAPListenerOptions options) throws IOException {
+        Reject.ifNull(host, factory, options);
+        final InetSocketAddress address = new InetSocketAddress(host, port);
+        this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
+                options.getProviderClassLoader());
+        this.impl = provider.getLDAPListener(address, factory, options);
+    }
+
+    /**
      * Closes this LDAP connection listener.
      */
     @Override
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
index 5fd488c..a3c5737 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
@@ -22,26 +22,19 @@
  *
  *
  *      Copyright 2010 Sun Microsystems, Inc.
- *      Portions copyright 2012-2014 ForgeRock AS.
+ *      Portions copyright 2012-2013 ForgeRock AS.
  */
 
 package org.forgerock.opendj.ldap;
 
 import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import javax.net.ssl.SSLContext;
 
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.util.Reject;
 
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-
 /**
  * Common options for LDAP client connections.
  * <p>
@@ -70,28 +63,6 @@
         DEFAULT_CONNECT_TIMEOUT = getIntProperty("org.forgerock.opendj.io.connectTimeout", 5000);
     }
 
-    /** Default heart beat search request. */
-    private static final SearchRequest DEFAULT_HEART_BEAT_SEARCH_REQUEST =
-        unmodifiableSearchRequest(newSearchRequest("", SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1"));
-
-    /** The heartbeat search request. Default request will target the root DSE but not return any results. */
-    private SearchRequest heartBeatSearchRequest = DEFAULT_HEART_BEAT_SEARCH_REQUEST;
-
-    /** The heartbeat bind request, if any. Default value is {@code null}. */
-    private BindRequest bindRequest;
-
-    /** The interval between successive heartbeats (milliseconds). Default value is 0 (means heartbeat disabled). */
-    private long heartBeatIntervalInMillis = 0;
-
-    /**
-     *  The heartbeat scheduler.
-     *  Default value is {@code null}.
-     *  If no scheduler is provided while creating new {@link HeartBeatConnectionFactory},
-     *  the factory will use his default one.
-     */
-    private ScheduledExecutorService heartBeatScheduler;
-
-    /** General Options. */
     private SSLContext sslContext;
     private boolean useStartTLS;
     private long timeoutInMillis = DEFAULT_TIMEOUT;
@@ -122,10 +93,6 @@
         this.enabledCipherSuites.addAll(options.getEnabledCipherSuites());
         this.enabledProtocols.addAll(options.getEnabledProtocols());
         this.connectTimeoutInMillis = options.connectTimeoutInMillis;
-        this.bindRequest = options.bindRequest;
-        this.heartBeatIntervalInMillis = options.heartBeatIntervalInMillis;
-        this.heartBeatSearchRequest = options.heartBeatSearchRequest;
-        this.heartBeatScheduler = options.heartBeatScheduler;
     }
 
     /**
@@ -169,21 +136,6 @@
     }
 
     /**
-     * Returns the bind request which will be sent as soon as the connection
-     * attempt succeeds, or null if unauthenticated connections should be
-     * returned.
-     * <p>
-     * By default, the bind request is {@code null}.
-     *
-     * @return The bind request which will be sent as soon as the connection
-     *         attempt succeeds, or null if unauthenticated connections should
-     *         be returned.
-     */
-    public BindRequest getBindRequest() {
-        return bindRequest;
-    }
-
-    /**
      * Returns the connect timeout in the specified unit. If a connection is not
      * established within the timeout period, then a
      * {@link TimeoutResultException} error result will be returned. A timeout
@@ -198,7 +150,7 @@
      *         timeout.
      */
     public long getConnectTimeout(final TimeUnit unit) {
-        return unit.convert(connectTimeoutInMillis, MILLISECONDS);
+        return unit.convert(connectTimeoutInMillis, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -224,45 +176,6 @@
     }
 
     /**
-     * Returns the interval between successive heart beat in the specified unit.
-     * <p>
-     * By default, heart beat interval value is 0, indicating that heart beat
-     * support will be disabled in the {@link LDAPConnectionFactory}.
-     *
-     * @param unit
-     *            The time unit.
-     * @return The interval between keepalive pings in the specified time unit.
-     */
-    public long getHeartBeatInterval(final TimeUnit unit) {
-        return unit.convert(heartBeatIntervalInMillis, MILLISECONDS);
-    }
-
-    /**
-     * Returns the scheduler which should periodically send keepalive pings.
-     * <p>
-     * By default, the scheduler is {@code null}, indicating that the associated
-     * {@link LDAPConnectionFactory} will use the default one.
-     *
-     * @return Returns the {@link ScheduledExecutorService} instance which
-     *         should periodically send keepalive pings.
-     */
-    public ScheduledExecutorService getHeartBeatScheduler() {
-        return heartBeatScheduler;
-    }
-
-    /**
-     * Returns the search request to use for keepalive pings.
-     * <p>
-     * The default {@link SearchRequest} will target the root DSE but not return
-     * any results.
-     *
-     * @return the {@link SearchRequest} to use for keepalive pings.
-     */
-    public SearchRequest getHeartBeatSearchRequest() {
-        return heartBeatSearchRequest;
-    }
-
-    /**
      * Returns the SSL context which will be used when initiating connections
      * with the Directory Server.
      * <p>
@@ -295,25 +208,7 @@
      *         timeout.
      */
     public long getTimeout(final TimeUnit unit) {
-        return unit.convert(timeoutInMillis, MILLISECONDS);
-    }
-
-    /**
-     * Sets the bind request which will be sent as soon as the
-     * connection attempt succeeds.
-     * <p>
-     * By default, the bind request is {@code null}, indicating that
-     * unauthenticated connections should be returned by the associated
-     * {@link LDAPConnectionFactory}
-     *
-     * @param bindRequest
-     *            The bind request which will be sent as soon as the connection
-     *            attempt succeeds.
-     * @return A reference to this set of options.
-     */
-    public LDAPOptions setBindRequest(final BindRequest bindRequest) {
-        this.bindRequest = bindRequest;
-        return this;
+        return unit.convert(timeoutInMillis, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -338,55 +233,6 @@
     }
 
     /**
-     * Sets the interval between successive heartbeats in the specified unit.
-     * <p>
-     * By default, heart beat interval value is 0, indicating that heart beat
-     * will be disabled in the {@link LDAPConnectionFactory}.
-     *
-     * @param interval
-     *            The interval between keepalive pings in the specified time unit.
-     * @param unit
-     *            The time unit of the given interval.
-     * @return A reference to this set of options.
-     */
-    public LDAPOptions setHeartBeatInterval(final long interval, final TimeUnit unit) {
-        Reject.ifTrue(interval < 0, "negative timeout");
-        this.heartBeatIntervalInMillis = MILLISECONDS.convert(interval, unit);
-        return this;
-    }
-
-    /**
-     * The scheduler which should for periodically sending keepalive pings.
-     * <p>
-     * By default, the scheduler is {@code null}, indicating that the associated
-     * {@link LDAPConnectionFactory} will use the default one.
-     *
-     * @param scheduler
-     *            The scheduler which should for periodically sending keepalive
-     *            pings.
-     * @return A reference to this set of options.
-     */
-    public LDAPOptions setHeartBeatScheduler(final ScheduledExecutorService scheduler) {
-        this.heartBeatScheduler = scheduler;
-        return this;
-    }
-
-    /**
-     * Sets the search request to use for keepalive pings.
-     * <p>
-     * The default {@link SearchRequest} will target the root DSE but not return
-     * any results.
-     *
-     * @param heartBeatRequest
-     *            The search request to use for keepalive pings.
-     * @return A reference to this set of options.
-     */
-    public LDAPOptions setHeartBeatSearchRequest(final SearchRequest heartBeatRequest) {
-        this.heartBeatSearchRequest = heartBeatRequest;
-        return this;
-    }
-
-    /**
      * Sets the SSL context which will be used when initiating connections with
      * the Directory Server.
      * <p>
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java
deleted file mode 100644
index def8c35..0000000
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2014 ForgeRock AS
- */
-
-package org.forgerock.opendj.ldap.spi;
-
-import java.net.InetSocketAddress;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionEventListener;
-import org.forgerock.opendj.ldap.ConnectionException;
-import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.TimeoutResultException;
-import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.util.Reject;
-import org.forgerock.util.promise.AsyncFunction;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.Function;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.SuccessHandler;
-import org.forgerock.util.time.TimeService;
-
-import com.forgerock.opendj.util.ReferenceCountedObject;
-
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-import static org.forgerock.util.Utils.*;
-import static org.forgerock.util.promise.Promises.*;
-
-import static com.forgerock.opendj.ldap.CoreMessages.*;
-import static com.forgerock.opendj.util.StaticUtils.*;
-
-/**
- * A abstract implementation of a LDAP connection factory.
- * This factory can be used to create connections that send a
- * periodic search request to a Directory Server, or to create
- * pre-authenticated secure connections to a Directory Server.
- * <p>
- * Before returning new connections to the application this factory will first
- * send an initial startTLS, bind or search (according to options) request in order to
- * determine that the remote server is responsive. If the request fails or times
- * out then the connection is closed immediately and an error returned to the client.
- * <p>
- * Once a connection has been established successfully (including the initial request)
- * and if heart beat is activated (this is done by specifying a interval
- * greater than 0 in the {@link LDAPOptions} constructor parameter), this
- * factory will periodically send heart beats on the connection based on the
- * configured heart beat interval. If the heart beat times out then the server
- * is assumed to be down and an appropriate {@link ConnectionException}
- * generated and published to any registered {@link ConnectionEventListener}s.
- * Note however, that heart beats will only be sent when the connection is
- * determined to be reasonably idle: there is no point in sending heart beats if
- * the connection has recently received a response. A connection is deemed to be
- * idle if no response has been received during a period equivalent to half the
- * heart beat interval.
- * <p>
- * The LDAP protocol specifically precludes clients from performing operations
- * while bind or startTLS requests are being performed. Likewise, a bind or
- * startTLS request will cause active operations to be aborted. This factory
- * coordinates heart beats with bind or startTLS requests, ensuring that they
- * are not performed concurrently. Specifically, bind and startTLS requests are
- * queued up while a heart beat is pending, and heart beats are not sent at all
- * while there are pending bind or startTLS requests. If one bind or startTLS
- * request has timed out, an error will be thrown after the interval time period.
- */
-public abstract class AbstractLdapConnectionFactoryImpl implements LDAPConnectionFactoryImpl {
-    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
-    /** This is package private in order to allow unit tests to inject fake timestamps. */
-    TimeService timeService = TimeService.SYSTEM;
-
-    // @Checkstyle:ignore
-    private static <VOUT, VIN extends VOUT, E extends Exception> Function<VIN, VOUT, E> castFunction() {
-        return new Function<VIN, VOUT, E>() {
-            @Override
-            public VOUT apply(VIN value) throws E {
-                return value;
-            }
-        };
-    }
-
-    private final String host;
-
-    private final int port;
-
-    /** The LDAP connection options to use when creating connections. */
-    protected final LDAPOptions options;
-
-    /** Flag which indicates whether this factory has been closed. */
-    private final AtomicBoolean isClosed = new AtomicBoolean();
-
-    /**
-     * Prevents the scheduler being released when there are remaining references (this factory or any connections).
-     * It is initially set to 1 because this factory has a reference.
-     */
-    private final AtomicInteger referenceCount = new AtomicInteger(1);
-
-    /** List of valid connections to which heartbeats will be sent. */
-    private final List<AbstractLdapConnectionImpl<?>> validConnections =
-        new LinkedList<AbstractLdapConnectionImpl<?>>();
-
-    /** The heart beat scheduler. */
-    private final ReferenceCountedObject<ScheduledExecutorService>.Reference scheduler;
-
-    /** The heart beat scheduled future - which may be null if heart beats are not being sent (no valid connections). */
-    private ScheduledFuture<?> heartBeatFuture;
-
-    /** Scheduled task which sends heart beats for all valid idle connections. */
-    private final Runnable sendHeartBeatRunnable = new Runnable() {
-        @Override
-        public void run() {
-            boolean heartBeatSent = false;
-            for (final AbstractLdapConnectionImpl<?> connection : getValidConnections()) {
-                heartBeatSent |= connection.sendHeartBeat();
-            }
-            if (heartBeatSent) {
-                scheduler.get().schedule(checkHeartBeatRunnable, options.getTimeout(MILLISECONDS), MILLISECONDS);
-            }
-        }
-    };
-
-    /** Scheduled task which checks that all heart beats have been received within the timeout period. */
-    private final Runnable checkHeartBeatRunnable = new Runnable() {
-        @Override
-        public void run() {
-            for (final AbstractLdapConnectionImpl<?> connection : getValidConnections()) {
-                connection.checkForHeartBeat();
-            }
-        }
-    };
-
-    /**
-     * Initializes this {@link AbstractLdapConnectionFactoryImpl}.
-     *
-     * @param host
-     *            The host name.
-     * @param port
-     *            The port number.
-     * @param options
-     *            The LDAP options to use when creating connections.
-     */
-    public AbstractLdapConnectionFactoryImpl(final String host, final int port, final LDAPOptions options) {
-        Reject.ifNull(options);
-
-        this.host = host;
-        this.port = port;
-        this.options = new LDAPOptions(options);
-        this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(options.getHeartBeatScheduler());
-    }
-
-    /**
-     * Returns a promise which result is a {@link AbstractLdapConnectionImpl}.
-     *
-     * @return A promise which result is a {@link AbstractLdapConnectionImpl}.
-     */
-    protected abstract Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0();
-
-    /**
-     * Install a secure layer on the given connection. Does nothing by default.
-     * Factories which wants to use secure layer like SSL/TLS should override this method.
-     *
-     * @param connection
-     *      The {@link Connection} to secure.
-     * @return A promise which should be completed once the operation is done.
-     */
-    protected Promise<Void, LdapException> installSecureLayer(final Connection connection) {
-        return newFailedPromise(newLdapException(ResultCode.OTHER,
-            LocalizableMessage.raw("The connection should install a secure layer.")));
-    }
-
-    /**
-     * Release all implementation resources.
-     */
-    protected void releaseImplResources() {
-        //does nothing by default.
-    }
-
-    @Override
-    public Connection getConnection() throws LdapException {
-        try {
-            return getConnectionAsync().getOrThrow();
-        } catch (final InterruptedException e) {
-            throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
-        }
-    }
-
-    @Override
-    public Promise<Connection, LdapException> getConnectionAsync() {
-        acquireResource(); // Protect resources.
-
-        final AtomicReference<AbstractLdapConnectionImpl<?>> connectionHolder =
-                new AtomicReference<AbstractLdapConnectionImpl<?>>();
-        final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
-
-        getConnectionAsync0().thenAsync(new AsyncFunction<AbstractLdapConnectionImpl<?>, Void, LdapException>() {
-            @Override
-            public Promise<Void, LdapException> apply(AbstractLdapConnectionImpl<?> connection) throws LdapException {
-                connectionHolder.set(connection);
-                return startSchedulerAndSecureLayerIfNeeded(connection, promise);
-            }
-        }).thenAsync(new AsyncFunction<Void, Result, LdapException>() {
-            @Override
-            public Promise<Result, LdapException> apply(Void v) throws LdapException {
-                return sendInitialRequest(connectionHolder.get());
-            }
-        }).onSuccess(new SuccessHandler<Result>() {
-            @Override
-            public void handleResult(Result voidResult) {
-                final AbstractLdapConnectionImpl<?> connectionImpl = connectionHolder.get();
-                if (!promise.tryHandleResult(registerConnection(connectionImpl))) {
-                    connectionImpl.close();
-                }
-            }
-        }).onFailure(new FailureHandler<LdapException>() {
-            @Override
-            public void handleError(LdapException error) {
-                if (promise.tryHandleError(error)) {
-                    closeSilently(connectionHolder.get());
-                    releaseResources();
-                }
-            }
-        });
-
-        return promise;
-    }
-
-    private Promise<Void, LdapException> startSchedulerAndSecureLayerIfNeeded(
-        final AbstractLdapConnectionImpl<?> connection, final PromiseImpl<Connection, LdapException> promise) {
-        // Start the scheduler to prevents initial request timed out.
-        if (initialRequestEnabled()) {
-            scheduler.get().schedule(new Runnable() {
-                @Override
-                public void run() {
-                    if (promise.tryHandleError(newHeartBeatTimeoutError())) {
-                        closeSilently(connection);
-                        releaseResources();
-                    }
-                }
-            }, options.getTimeout(MILLISECONDS), MILLISECONDS);
-        }
-
-        if (!options.useStartTLS() && options.getSSLContext() != null) {
-            return installSecureLayer(connection);
-        }
-
-        return newSuccessfulPromise(null);
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private Promise<Result, LdapException> sendInitialRequest(final AbstractLdapConnectionImpl connection) {
-        // Sends the request which will acts as initial heartbeat.
-        if (options.useStartTLS()) {
-            // StartTLS extended request which will act as initial heartbeat.
-            final StartTLSExtendedRequest startTLS = newStartTLSExtendedRequest(options.getSSLContext());
-            startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites().toArray(
-                    new String[options.getEnabledCipherSuites().size()]));
-            startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray(
-                    new String[options.getEnabledProtocols().size()]));
-
-            return (Promise) connection.extendedRequestAsync(startTLS);
-        } else if (options.getBindRequest() != null) {
-            // FIXME: support multi-stage SASL binds?
-            // The bind request will act as the initial hearbeat.
-            return (Promise) connection.bindAsync(options.getBindRequest());
-        } else if (isHeartBeatEnabled()) {
-            return connection.searchAsync(options.getHeartBeatSearchRequest(), null, null).then(
-                AbstractLdapConnectionFactoryImpl.<Result, Result, LdapException> castFunction(),
-                new Function<LdapException, Result, LdapException>() {
-                    @Override
-                    public Result apply(LdapException error) throws LdapException {
-                        throw adaptHeartBeatError(error);
-                    }
-                });
-        }
-
-        return newSuccessfulPromise(null);
-    }
-
-    private boolean initialRequestEnabled() {
-        return isHeartBeatEnabled() || options.useStartTLS()
-                || options.getSSLContext() != null || options.getBindRequest() != null;
-    }
-
-    private void acquireResource() {
-        /*
-         * If the factory is not closed then we need to prevent the scheduler
-         * from being released while the connection attempt is in progress.
-         */
-        referenceCount.incrementAndGet();
-        if (isClosed.get()) {
-            releaseResources();
-            throw new IllegalStateException("Attempted to get a connection after factory close");
-        }
-    }
-
-    private Connection registerConnection(final AbstractLdapConnectionImpl<?> connection) {
-        if (isHeartBeatEnabled()) {
-            synchronized (validConnections) {
-                if (validConnections.isEmpty()) {
-                    /* This is the first active connection, so start the heart beat. */
-                    heartBeatFuture = scheduler.get().scheduleWithFixedDelay(sendHeartBeatRunnable, 0,
-                            options.getHeartBeatInterval(MILLISECONDS), MILLISECONDS);
-                }
-                validConnections.add(connection);
-            }
-        }
-
-        return connection;
-    }
-
-    @Override
-    public void close() {
-        if (isClosed.compareAndSet(false, true)) {
-            releaseResources();
-            if (isHeartBeatEnabled()) {
-                synchronized (validConnections) {
-                    if (!validConnections.isEmpty()) {
-                        logger.debug(LocalizableMessage.raw(
-                                "HeartbeatConnectionFactory '%s' is closing while %d active connections remain", this,
-                                validConnections.size()));
-                    }
-                }
-            }
-        }
-    }
-
-    void releaseResources() {
-        if (referenceCount.decrementAndGet() == 0) {
-            scheduler.release();
-            releaseImplResources();
-        }
-    }
-
-    /**
-     * Returns {@link LDAPOptions} of this factory.
-     *
-     * @return {@link LDAPOptions} of this factory.
-     */
-    public LDAPOptions getLdapOptions() {
-        return options;
-    }
-
-    @Override
-    public InetSocketAddress getSocketAddress() {
-        return new InetSocketAddress(host, port);
-    }
-
-    @Override
-    public String getHostName() {
-        return host;
-    }
-
-    @Override
-    public int getPort() {
-        return port;
-    }
-
-    boolean isHeartBeatEnabled() {
-        return options.getHeartBeatInterval(MILLISECONDS) > 0L;
-    }
-
-    private AbstractLdapConnectionImpl<?>[] getValidConnections() {
-        synchronized (validConnections) {
-            return validConnections.toArray(new AbstractLdapConnectionImpl<?>[0]);
-        }
-    }
-
-    void deregisterConnection(AbstractLdapConnectionImpl<?> connection) {
-        if (isHeartBeatEnabled()) {
-            synchronized (validConnections) {
-                if (validConnections.remove(connection) && validConnections.isEmpty()) {
-                    // This is the last active connection, so stop the heartbeat.
-                    heartBeatFuture.cancel(false);
-                }
-            }
-        }
-    }
-
-    private LdapException adaptHeartBeatError(final Exception error) {
-        if (error instanceof ConnectionException) {
-            return (LdapException) error;
-        } else if (error instanceof TimeoutResultException || error instanceof TimeoutException) {
-            return newHeartBeatTimeoutError();
-        } else if (error instanceof InterruptedException) {
-            return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, error);
-        } else {
-            return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_FAILED.get(), error);
-        }
-    }
-
-    LdapException newHeartBeatTimeoutError() {
-        return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
-                HBCF_HEARTBEAT_TIMEOUT.get(options.getTimeout(MILLISECONDS)));
-    }
-
-    TimeService getTimeService() {
-        return timeService;
-    }
-
-    /**
-     * Returns the minimum amount of time the connection should remain idle (no responses)
-     * before starting to send heartbeats.
-     *
-     * @param unit
-     *      The {@link TimeUnit} of the response
-     * @return
-     *      A long which represents the minimum amount of time the connection should remain idle (no responses)
-     *      before starting to send heartbeats.
-     */
-    long getMinimumDelay(TimeUnit unit) {
-        return MILLISECONDS.convert(options.getHeartBeatInterval(MILLISECONDS) / 2, unit);
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append("(").append(getClass().getSimpleName())
-               .append("(").append(host).append(":").append(port).append("), ")
-               .append("HeartBeat: ").append(isHeartBeatEnabled() ? "Enabled" : "Disabled").append(", ");
-
-        if (isHeartBeatEnabled()) {
-            builder.append("HeartBeatSearchRequest: ").append(options.getHeartBeatSearchRequest()).append(", ");
-        }
-
-        final boolean isSecured = options.getSSLContext() != null || options.useStartTLS();
-        builder.append("IsSecured: ").append(isSecured ? "Yes" : "No").append(", ")
-               .append("InitialRequest: ").append(initialRequestEnabled() ? "Enabled" : "Disabled");
-        if (options.useStartTLS() || options.getBindRequest() != null || options.getHeartBeatSearchRequest() != null) {
-            builder.append(", ");
-            if (options.useStartTLS()) {
-                builder.append("startTLS");
-            } else if (options.getBindRequest() != null) {
-                builder.append(options.getBindRequest());
-            } else if (options.getHeartBeatSearchRequest() != null) {
-                builder.append(options.getHeartBeatSearchRequest());
-            }
-        }
-        builder.append(")");
-        return builder.toString();
-    }
-
-}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java
deleted file mode 100644
index 7402dde..0000000
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java
+++ /dev/null
@@ -1,1124 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2014 ForgeRock AS
- */
-package org.forgerock.opendj.ldap.spi;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.AbstractQueuedSynchronizer;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.io.LDAPWriter;
-import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
-import org.forgerock.opendj.ldap.CancelledResultException;
-import org.forgerock.opendj.ldap.ConnectionEventListener;
-import org.forgerock.opendj.ldap.ConnectionException;
-import org.forgerock.opendj.ldap.IntermediateResponseHandler;
-import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.TimeoutEventListener;
-import org.forgerock.opendj.ldap.requests.AbandonRequest;
-import org.forgerock.opendj.ldap.requests.AddRequest;
-import org.forgerock.opendj.ldap.requests.BindClient;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.CompareRequest;
-import org.forgerock.opendj.ldap.requests.DeleteRequest;
-import org.forgerock.opendj.ldap.requests.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-import org.forgerock.opendj.ldap.requests.Request;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
-import org.forgerock.opendj.ldap.requests.UnbindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.CompareResult;
-import org.forgerock.opendj.ldap.responses.ExtendedResult;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.util.promise.AsyncFunction;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.SuccessHandler;
-
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.forgerock.opendj.ldap.Connections.*;
-import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.opendj.ldap.ResultCode.*;
-import static org.forgerock.opendj.ldap.responses.Responses.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
-
-import static com.forgerock.opendj.ldap.CoreMessages.*;
-
-
-
-/**
- * An abstract implementation of a LDAP {@link AbstractAsynchronousConnection}.
- *
- * @param <F>
- *            The associated connection factory.
- */
-public abstract class AbstractLdapConnectionImpl<F extends AbstractLdapConnectionFactoryImpl>
-    extends AbstractAsynchronousConnection implements TimeoutEventListener {
-    /**
-     * This synchronizer prevents Bind or StartTLS operations from being
-     * processed concurrently with heart-beats. This is required because the
-     * LDAP protocol specifically states that servers receiving a Bind operation
-     * should either wait for existing operations to complete or abandon them.
-     * The same presumably applies to StartTLS operations. Note that concurrent
-     * bind/StartTLS operations are not permitted.
-     * <p>
-     * This connection factory only coordinates Bind and StartTLS requests with
-     * heart-beats. It does not attempt to prevent or control attempts to send
-     * multiple concurrent Bind or StartTLS operations, etc.
-     * <p>
-     * This synchronizer can be thought of as cross between a read-write lock
-     * and a semaphore. Unlike a read-write lock there is no requirement that a
-     * thread releasing a lock must hold it. In addition, this synchronizer does
-     * not support reentrancy. A thread attempting to acquire exclusively more
-     * than once will deadlock, and a thread attempting to acquire shared more
-     * than once will succeed and be required to release an equivalent number of
-     * times.
-     * <p>
-     * The synchronizer has three states:
-     * <ul>
-     * <li>UNLOCKED(0) - the synchronizer may be acquired shared or exclusively
-     * <li>LOCKED_EXCLUSIVELY(-1) - the synchronizer is held exclusively and
-     * cannot be acquired shared or exclusively. An exclusive lock is held while
-     * a heart beat is in progress
-     * <li>LOCKED_SHARED(>0) - the synchronizer is held shared and cannot be
-     * acquired exclusively. N shared locks are held while N Bind or StartTLS
-     * operations are in progress.
-     * </ul>
-     */
-    private static final class HeartBeatSynchronizer extends AbstractQueuedSynchronizer {
-        /** Keep compiler quiet. */
-        private static final long serialVersionUID = -3590428415442668336L;
-
-        /** Lock states. Positive values indicate that the shared lock is taken. */
-        private static final int LOCKED_EXCLUSIVELY = -1;
-        private static final int UNLOCKED = 0; // initial state
-
-        @Override
-        protected boolean isHeldExclusively() {
-            return getState() == LOCKED_EXCLUSIVELY;
-        }
-
-        boolean isHeld() {
-            return getState() != 0;
-        }
-
-        @Override
-        protected boolean tryAcquire(final int ignored) {
-            if (compareAndSetState(UNLOCKED, LOCKED_EXCLUSIVELY)) {
-                setExclusiveOwnerThread(Thread.currentThread());
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        protected int tryAcquireShared(final int readers) {
-            for (;;) {
-                final int state = getState();
-                if (state == LOCKED_EXCLUSIVELY) {
-                    return LOCKED_EXCLUSIVELY; // failed
-                }
-                final int newState = state + readers;
-                if (compareAndSetState(state, newState)) {
-                    return newState; // succeeded + more readers allowed
-                }
-            }
-        }
-
-        @Override
-        protected boolean tryRelease(final int ignored) {
-            if (getState() != LOCKED_EXCLUSIVELY) {
-                throw new IllegalMonitorStateException();
-            }
-            setExclusiveOwnerThread(null);
-            setState(UNLOCKED);
-            return true;
-        }
-
-        @Override
-        protected boolean tryReleaseShared(final int ignored) {
-            for (;;) {
-                final int state = getState();
-                if (state == UNLOCKED || state == LOCKED_EXCLUSIVELY) {
-                    throw new IllegalMonitorStateException();
-                }
-                final int newState = state - 1;
-                if (compareAndSetState(state, newState)) {
-                    /*
-                     * We could always return true here, but since there cannot
-                     * be waiting readers we can specialize for waiting writers.
-                     */
-                    return newState == UNLOCKED;
-                }
-            }
-        }
-
-        void lockShared() {
-            acquireShared(1);
-        }
-
-        boolean tryLockExclusively() {
-            return tryAcquire(0 /* unused */);
-        }
-
-        boolean tryLockShared() {
-            return tryAcquireShared(1) > 0;
-        }
-
-        void unlockExclusively() {
-            release(0 /* unused */);
-        }
-
-        void unlockShared() {
-            releaseShared(0 /* unused */);
-        }
-
-    }
-
-    /**
-     * A request writer which is use to write different kind of {@link Request}.
-     *
-     * @param <R> The {@link Request} to write.
-     */
-    protected static abstract interface RequestWriter<R extends Request> {
-        /**
-         * Use the given {@link LDAPWriter} to write the given {@link Request}.
-         *
-         * @param messageID
-         *            Identifier of the request.
-         * @param writer
-         *            {@link LDAPWriter} associated to the request.
-         * @param request
-         *            The request to send to the server.
-         * @throws IOException
-         *             If any writes problems occurs.
-         */
-        void writeRequest(int messageID, LDAPWriter<?> writer, R request) throws IOException;
-    };
-
-    private static final RequestWriter<AddRequest> ADD_WRITER = new RequestWriter<AddRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, AddRequest request)  throws IOException {
-            writer.writeAddRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<CompareRequest> COMPARE_WRITER = new RequestWriter<CompareRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, CompareRequest request)  throws IOException {
-            writer.writeCompareRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<DeleteRequest> DELETE_WRITER = new RequestWriter<DeleteRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, DeleteRequest request)  throws IOException {
-            writer.writeDeleteRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<ExtendedRequest<?>> EXTENDED_WRITER = new RequestWriter<ExtendedRequest<?>>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, ExtendedRequest<?> request)  throws IOException {
-            writer.writeExtendedRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<ModifyRequest> MODIFY_WRITER = new RequestWriter<ModifyRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, ModifyRequest request)  throws IOException {
-            writer.writeModifyRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<ModifyDNRequest> MODIFY_DN_WRITER = new RequestWriter<ModifyDNRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, ModifyDNRequest request)  throws IOException {
-            writer.writeModifyDNRequest(messageID, request);
-        };
-    };
-
-    private static final RequestWriter<SearchRequest> SEARCH_WRITER = new RequestWriter<SearchRequest>() {
-        @Override
-        public void writeRequest(int messageID, LDAPWriter<?> writer, SearchRequest request)  throws IOException {
-            writer.writeSearchRequest(messageID, request);
-        };
-    };
-
-    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
-    /** List of pending Bind or StartTLS requests which must be invoked once the current heart beat completes. */
-    private final Queue<Runnable> pendingBindOrStartTLSRequests = new ConcurrentLinkedQueue<Runnable>();
-
-    /** Coordinates heart-beats with Bind and StartTLS requests. */
-    private final HeartBeatSynchronizer sync = new HeartBeatSynchronizer();
-
-    /** Timestamp of last response received (any response, not just heart beats). */
-    private volatile long lastResponseTimestamp;
-
-    private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
-
-    private final AtomicInteger msgIDGenerator = new AtomicInteger(1);
-
-    /** Subclasses are responsible for adding and removing pending results. */
-    private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingResults =
-            new ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>>();
-
-    private final F factory;
-
-    /** Internal connection state. */
-    protected final ConnectionState state = new ConnectionState();
-
-    /**
-     * Creates a connection which was produced by the given factory.
-     *
-     * @param attachedFactory
-     *            The factory of this connection.
-     */
-    protected AbstractLdapConnectionImpl(F attachedFactory) {
-        this.factory = attachedFactory;
-        this.lastResponseTimestamp = attachedFactory.getTimeService().now(); // Assume valid at creation.
-    }
-
-    /**
-     * Returns {@link LDAPOptions} associated to the factory which has produced
-     * this connection.
-     *
-     * @return {@link LDAPOptions} associated to the factory which has produced
-     *         this connection.
-     */
-    public LDAPOptions getLdapOptions() {
-        return factory.getLdapOptions();
-    }
-
-    @Override
-    public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
-        /*
-         * Need to be careful here since both abandonAsync and Promise.cancel can
-         * be called separately by the client application. Therefore
-         * promise.cancel() should abandon the request, and abandonAsync should
-         * cancel the promise. In addition, bind or StartTLS requests cannot be
-         * abandoned.
-         */
-        try {
-            checkConnectionIsValid();
-            /*
-             * If there is a bind or startTLS in progress then it must be
-             * this request which is being abandoned. The following check
-             * will prevent it from happening.
-             */
-            checkBindOrStartTLSInProgress();
-        } catch (final LdapException e) {
-            return newFailedLdapPromise(e);
-        }
-
-        // Remove the promise associated with the request to be abandoned.
-        final ResultLdapPromiseImpl<?, ?> pendingRequest = removePendingResult(request.getRequestID());
-        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.
-             */
-            return newSuccessfulLdapPromise((Void) null);
-        }
-
-        /*
-         * This will cancel the promise, but will also recursively invoke this
-         * method. Since the pending request has been removed, there is no risk
-         * of an infinite loop.
-         */
-        pendingRequest.cancel(false);
-
-        /*
-         * FIXME: there's a potential race condition here if a bind or startTLS
-         * is initiated just after we removed the pending request.
-         */
-        return abandonAsync0(newMessageID(), request);
-    }
-
-    @Override
-    public LdapPromise<Result> addAsync(final AddRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        return sendRequest(request, intermediateResponseHandler, ADD_WRITER);
-    }
-
-    @Override
-    public LdapPromise<BindResult> bindAsync(final BindRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        try {
-            checkConnectionIsValid();
-            if (!isHeartBeatConnection() || sync.tryLockShared()) {
-                // Fast path
-                return performBindRequest(request, intermediateResponseHandler);
-            } else {
-                return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() {
-                    @Override
-                    public Promise<BindResult, LdapException> apply(Void value) throws LdapException {
-                        return performBindRequest(request, intermediateResponseHandler);
-                    }
-                });
-            }
-        } catch (final LdapException e) {
-            return newFailedLdapPromise(e);
-        }
-    }
-
-    @Override
-    public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        return sendRequest(request, intermediateResponseHandler, COMPARE_WRITER);
-    }
-
-    @Override
-    public LdapPromise<Result> deleteAsync(final DeleteRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        return sendRequest(request, intermediateResponseHandler, DELETE_WRITER);
-    }
-
-    @Override
-    public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        try {
-            checkConnectionIsValid();
-            if (isStartTLSRequest(request)) {
-                if (!isHeartBeatConnection() && sync.tryLockShared()) {
-                    // Fast path
-                    return performStartTLSRequest(request, intermediateResponseHandler);
-                } else {
-                    return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() {
-                        @Override
-                        public Promise<R, LdapException> apply(Void value) throws LdapException {
-                            startBindOrStartTLS();
-                            return performStartTLSRequest(request, intermediateResponseHandler);
-                        }
-                    });
-                }
-            } else {
-                return sendExtendedRequest(request, intermediateResponseHandler);
-            }
-        } catch (LdapException e) {
-            return newFailedLdapPromise(e);
-        }
-    }
-
-    @Override
-    public LdapPromise<Result> modifyAsync(final ModifyRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        return sendRequest(request, intermediateResponseHandler, MODIFY_WRITER);
-    }
-
-    @Override
-    public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) {
-        return sendRequest(request, intermediateResponseHandler, MODIFY_DN_WRITER);
-    }
-
-    @Override
-    public LdapPromise<Result> searchAsync(final SearchRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
-        return sendRequest(request, intermediateResponseHandler, entryHandler, SEARCH_WRITER);
-    }
-
-    @Override
-    public long handleTimeout(final long currentTime) {
-        final long timeout = getLdapOptions().getTimeout(MILLISECONDS);
-        if (timeout <= 0) {
-            return 0;
-        }
-
-        long delay = timeout;
-        for (final ResultLdapPromiseImpl<?, ?> promise : pendingResults.values()) {
-            if (promise == null || !promise.checkForTimeout()) {
-                continue;
-            }
-            final long diff = (promise.getTimestamp() + timeout) - currentTime;
-            if (diff > 0) {
-                // Will expire in diff milliseconds.
-                delay = Math.min(delay, diff);
-            } else if (removePendingResult(promise.getRequestID()) == null) {
-                // Result arrived at the same time.
-                continue;
-            } else if (promise.isBindOrStartTLS()) {
-                //TODO: set diagnostic messages for timeout (from opendj-grizzly)
-                /*
-                 * No other operations can be performed while a bind or StartTLS
-                 * request is active, so we cannot time out the request. We
-                 * therefore have a choice: either ignore timeouts for these
-                 * operations, or enforce them but doing so requires
-                 * invalidating the connection. We'll do the latter, since
-                 * ignoring timeouts could cause the application to hang.
-                 */
-                logger.debug(LocalizableMessage.raw("Failing bind or StartTLS request due to timeout %s"
-                        + "(connection will be invalidated): ", promise));
-                final Result result = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
-                    LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout).toString());
-                promise.adaptErrorResult(result);
-
-                // Fail the connection.
-                final Result errorResult = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
-                    LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout).toString());
-                connectionErrorOccurred(false, errorResult);
-            } else {
-                logger.debug(LocalizableMessage.raw("Failing request due to timeout: %s", promise));
-                final Result result = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
-                    LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
-
-                promise.adaptErrorResult(result);
-
-                /*
-                 * FIXME: there's a potential race condition here if a bind or
-                 * startTLS is initiated just after we check the boolean. It
-                 * seems potentially even more dangerous to send the abandon
-                 * request while holding the state lock, since a blocking write
-                 * could hang the application.
-                 */
-                // if (!bindOrStartTLSInProgress.get()) {
-                // sendAbandonRequest(newAbandonRequest(promise.getRequestID()));
-                // }
-            }
-        }
-        return delay;
-    }
-
-    @Override
-    public long getTimeout() {
-        return getLdapOptions().getTimeout(MILLISECONDS);
-    }
-
-    @Override
-    public void addConnectionEventListener(final ConnectionEventListener listener) {
-        state.addConnectionEventListener(listener);
-    }
-
-    @Override
-    public void removeConnectionEventListener(final ConnectionEventListener listener) {
-        state.removeConnectionEventListener(listener);
-    }
-
-    @Override
-    public boolean isClosed() {
-        return state.isClosed();
-    }
-
-    @Override
-    public boolean isValid() {
-        return state.isValid();
-    }
-
-    /**
-     * Set this connection state as performing a bind or a startTLS request
-     * according to the given boolean.
-     *
-     * @param state
-     *            true if this connection is performing a bind or a startTLS
-     *            request.
-     */
-    public void setBindOrStartTLSInProgress(final boolean state) {
-        bindOrStartTLSInProgress.set(state);
-    }
-
-    @Override
-    public void close(final UnbindRequest request, final String reason) {
-        if (state.notifyConnectionClosed()) {
-            failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED,
-                                HBCF_CONNECTION_CLOSED_BY_CLIENT.get()));
-            factory.deregisterConnection(this);
-            close0(newMessageID(), request, reason);
-            factory.releaseResources();
-        }
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append("(").append(getClass().getSimpleName()).append(", ")
-               .append("attachedFactory: ").append(factory).append(")");
-        return builder.toString();
-    };
-
-    /**
-     * Returns the factory which has produced this connection.
-     *
-     * @return The factory which has produced this connection.
-     */
-    protected F getFactory() {
-        return  factory;
-    }
-
-    /**
-     * Sends the given {@link AbandonRequest} to the server.
-     *
-     * @param messageID
-     *            Identifier of the request.
-     * @param request
-     *            The request identifying the operation to be abandoned.
-     * @return A promise whose result is Void.
-     */
-    protected abstract LdapPromise<Void> abandonAsync0(final int messageID, final AbandonRequest request);
-
-    /**
-     * Sends the given {@link BindRequest} to the server.
-     *
-     * @param messageID
-     *            Identifier of the request.
-     * @param request
-     *            The bind request.
-     * @param bindClient
-     *            Bind client which can be used to perform the authentication
-     *            process.
-     * @param intermediateResponseHandler
-     *            An intermediate response handler which can be used to process
-     *            any intermediate responses as they are received, may be
-     *            {@code null}.
-     * @throws LdapException
-     *             If a LDAP error occurred.
-     */
-    protected abstract void bindAsync0(final int messageID, final BindRequest request, final BindClient bindClient,
-            final IntermediateResponseHandler intermediateResponseHandler) throws LdapException;
-
-    /**
-     * Sends the given {@link UnbindRequest} and closes the underlying socket.
-     *
-     * @param messageID
-     *            Identifier of the request.
-     * @param request
-     *            The unbind request to use in the case where a physical
-     *            connection is closed.
-     * @param reason
-     *            A reason describing why the connection was closed.
-     */
-    protected abstract void close0(final int messageID, UnbindRequest request, String reason);
-
-    /**
-     * Indicates whether or not TLS is enabled on this connection.
-     *
-     * @return {@code true} if TLS is enabled on this connection, otherwise {@code false}.
-     */
-    protected abstract boolean isTLSEnabled();
-
-    /**
-     * Sends the given request to the server.
-     *
-     * @param <R> The request type.
-     *
-     * @param messageID
-     *            Identifier of the request.
-     * @param request
-     *            The request to send.
-     * @param intermediateResponseHandler
-     *            An intermediate response handler which can be used to process
-     *            any intermediate responses as they are received, may be
-     *            {@code null}.
-     * @param requestWriter
-     *            The writer associated to the request.
-     * @throws LdapException
-     *             If a LDAP error occurred.
-     */
-    protected abstract <R extends Request> void writeRequest(final int messageID, final R request,
-            final IntermediateResponseHandler intermediateResponseHandler, final RequestWriter<R> requestWriter)
-            throws LdapException;
-
-    /**
-     * Mark this connection as failed, invoking event listeners as needed.
-     *
-     * @param isDisconnectNotification
-     *            {@code true} if this is a connection failure signaled by a
-     *            server disconnect notification.
-     * @param reason
-     *            The result indicating why the connection has failed.
-     */
-    protected void connectionErrorOccurred(final boolean isDisconnectNotification, final Result reason) {
-        LdapException error = newLdapException(reason);
-        if (state.notifyConnectionError(isDisconnectNotification, error)) {
-            failPendingResults(error);
-        }
-    }
-
-    /**
-     * Checks the connection state. If the connection is no longer valid, throws
-     * the connection {@link LdapException}.
-     *
-     * @throws LdapException
-     *             The connection error.
-     */
-    protected void checkConnectionIsValid() throws LdapException {
-        if (!state.isValid()) {
-            /*
-             * Connection termination was triggered remotely. We don't want to
-             * blindly pass on the result code to requests since it could be
-             * confused for a genuine response. For example, if the disconnect
-             * contained the invalidCredentials result code then this could be
-             * misinterpreted as a genuine authentication failure for subsequent
-             * bind requests.
-             */
-            throw state.getConnectionError() != null ? state.getConnectionError() : newLdapException(
-                    ResultCode.CLIENT_SIDE_SERVER_DOWN, "Connection closed by server");
-        }
-    }
-
-    /**
-     * Returns true of the given {@link ExtendedRequest} is a startTLS request.
-     *
-     * @param request
-     *            The extended request to test.
-     * @return true of the given {@link ExtendedRequest} is a startTLS request.
-     */
-    protected boolean isStartTLSRequest(final ExtendedRequest<?> request) {
-        return request.getOID().equals(StartTLSExtendedRequest.OID);
-    }
-
-    /**
-     * Returns true if heart beat is enabled on this connection.
-     *
-     * @return true if heart beat is enabled on this connection.
-     */
-    protected boolean isHeartBeatConnection() {
-        return factory.isHeartBeatEnabled();
-    }
-
-    /**
-     * Returns the next pending result identifier available for this connection.
-     *
-     * @return The next pending result identifier available for this connection.
-     */
-    protected int newMessageID() {
-        return msgIDGenerator.getAndIncrement();
-    }
-
-    /**
-     * Adds the given pending result to the pending results of this
-     * connection.
-     *
-     * @param messageID
-     *            The pending result identifier.
-     * @param promise
-     *            The pending results.
-     */
-    protected void addPendingResult(final int messageID, final ResultLdapPromiseImpl<?, ?> promise) {
-        if (pendingResults.put(messageID, promise) != null)  {
-            throw new IllegalStateException("There is already a pending request with ID: " + messageID);
-        }
-
-        promise.onSuccessOrFailure(new Runnable() {
-            @Override
-            public void run() {
-                removePendingResult(messageID);
-            }
-        });
-    }
-
-    /**
-     * Returns the pending results of this connection.
-     *
-     * @return A {@link Map} which represents the pending results of this connection.
-     */
-    protected Map<Integer, ResultLdapPromiseImpl<?, ?>> getPendingResults() {
-        return pendingResults;
-    }
-
-    /**
-     * Returns the pending result associated to the given identifier, or
-     * {@code null} if there is no pending result mapped for the given
-     * identifier.
-     *
-     * @param messageID
-     *            The request identifier.
-     * @return The pending result associated to the given identifier, or
-     *         {@code null} if there is no pending result mapped for the given
-     *         identifier.
-     */
-    protected ResultLdapPromiseImpl<?, ?> getPendingResult(int messageID) {
-        return pendingResults.get(messageID);
-    }
-
-    /**
-     * Removes the pending result for the given identifier, if present.
-     *
-     * @param messageID
-     *            The pending result identifier.
-     * @param <R>
-     *            The associated request type.
-     * @param <S>
-     *            The pending result type.
-     * @return The pending result associated with the specified identifier, or
-     *         {@code null} if absent.
-     */
-    @SuppressWarnings("unchecked")
-    protected <R extends Request, S extends Result> ResultLdapPromiseImpl<R, S> removePendingResult(
-            final Integer messageID) {
-        return (ResultLdapPromiseImpl<R, S>) pendingResults.remove(messageID);
-    }
-
-    /**
-     * Notifies connection event listeners that this connection has just
-     * received the provided unsolicited notification from the server.
-     *
-     * @param notification
-     *            The unsolicited notification.
-     */
-    protected void handleUnsolicitedNotification(ExtendedResult notification) {
-        if (isHeartBeatConnection()) {
-            timestamp(notification);
-        }
-        state.notifyUnsolicitedNotification(notification);
-    }
-
-    /**
-     * Sends a heart beat on this connection if required to do so.
-     *
-     * @return {@code true} if a heart beat was sent, otherwise {@code false}.
-     */
-    boolean sendHeartBeat() {
-        // Don't attempt to send a heart beat if the connection has already failed.
-        if (!state.isValid()) {
-            return false;
-        }
-
-        // Only send the heart beat if the connection has been idle for some time.
-        final long currentTimeMillis = factory.getTimeService().now();
-        if (currentTimeMillis < (lastResponseTimestamp + factory.getMinimumDelay(MILLISECONDS))) {
-            return false;
-        }
-
-        /*
-         * Don't send a heart beat if there is already a heart beat, bind,
-         * or startTLS in progress. Note that the bind/startTLS response
-         * will update the lastResponseTimestamp as if it were a heart beat.
-         */
-        if (sync.tryLockExclusively()) {
-            try {
-                searchAsync(factory.getLdapOptions().getHeartBeatSearchRequest(), null,
-                    new SearchResultHandler() {
-                        @Override
-                        public boolean handleEntry(final SearchResultEntry entry) {
-                            timestamp(entry);
-                            return true;
-                        }
-
-                        @Override
-                        public boolean handleReference(final SearchResultReference reference) {
-                            timestamp(reference);
-                            return true;
-                        }
-                    }
-                ).onSuccess(new SuccessHandler<Result>() {
-                    @Override
-                    public void handleResult(final Result result) {
-                        timestamp(result);
-                        releaseHeartBeatLock();
-                    }
-                }).onFailure(new FailureHandler<LdapException>() {
-                    @Override
-                    public void handleError(final LdapException error) {
-                        /*
-                         * Connection failure will be handled by connection
-                         * event listener. Ignore cancellation errors since
-                         * these indicate that the heart beat was aborted by a
-                         * client-side close.
-                         */
-                        if (!(error instanceof CancelledResultException)) {
-                            /*
-                             * Log at debug level to avoid polluting the logs
-                             * with benign password policy related errors. See
-                             * OPENDJ-1168 and OPENDJ-1167.
-                             */
-                            logger.debug(LocalizableMessage.raw(
-                                    "Heartbeat failed for connection factory '%s'", factory, error));
-                            timestamp(error);
-                        }
-                        releaseHeartBeatLock();
-                    }
-                });
-            } catch (final IllegalStateException e) {
-                /*
-                 * This may happen when we attempt to send the heart beat
-                 * just after the connection is closed but before we are
-                 * notified.
-                 */
-
-                /*
-                 * Release the lock because we're never going to get a
-                 * response.
-                 */
-                releaseHeartBeatLock();
-            }
-        }
-        /*
-         * Indicate that a the heartbeat should be checked even if a
-         * bind/startTLS is in progress, since these operations will
-         * effectively act as the heartbeat.
-         */
-        return true;
-    }
-
-    void checkForHeartBeat() {
-        if (sync.isHeld()) {
-            /*
-             * A heart beat or bind/startTLS is still in progress, but it
-             * should have completed by now. Let's avoid aggressively
-             * terminating the connection, because the heart beat may simply
-             * have been delayed by a sudden surge of activity. Therefore,
-             * only flag the connection as failed if no activity has been
-             * seen on the connection since the heart beat was sent.
-             */
-            final long currentTimeMillis = factory.getTimeService().now();
-            final long timeoutMS = factory.getLdapOptions().getTimeout(MILLISECONDS);
-            if (lastResponseTimestamp < (currentTimeMillis - timeoutMS)) {
-                logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", this));
-                connectionErrorOccurred(false, factory.newHeartBeatTimeoutError().getResult());
-            }
-        }
-    }
-
-    private void releaseHeartBeatLock() {
-        sync.unlockExclusively();
-        flushPendingBindOrStartTLSRequests();
-    }
-
-    private LdapPromise<BindResult> performBindRequest(final BindRequest request,
-            final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
-        startBindOrStartTLS();
-        int messageID = newMessageID();
-        BindClient context = null;
-        try {
-            InetSocketAddress socketAddress = factory.getSocketAddress();
-            if (socketAddress != null) {
-                context = request.createBindClient(getHostString(socketAddress));
-            }
-        } catch (final LdapException e) {
-            releaseBindOrStartTLSLock();
-            setBindOrStartTLSInProgress(false);
-            return newFailedLdapPromise(e, messageID);
-        }
-
-        final BindResultLdapPromiseImpl bindPromise =
-                newBindLdapPromise(messageID, request, context, intermediateResponseHandler, this);
-        addPendingResult(messageID, bindPromise);
-        try {
-            bindAsync0(messageID, request, context, intermediateResponseHandler);
-            return timestampBindOrStartTLSPromise(bindPromise);
-        } catch (LdapException e) {
-            removePendingResult(messageID).setResultOrError(e.getResult());
-            setBindOrStartTLSInProgress(false);
-            releaseBindOrStartTLSLock();
-            return bindPromise;
-        }
-    }
-
-    private <R extends ExtendedResult> LdapPromise<R> performStartTLSRequest(final ExtendedRequest<R> request,
-            final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
-        if (isTLSEnabled()) {
-            return newFailedLdapPromise(newLdapException(request.getResultDecoder().newExtendedErrorResult(
-                    ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled")));
-        }
-        startBindOrStartTLS();
-        return sendExtendedRequest(request, intermediateResponseHandler);
-    }
-
-    private <R extends ExtendedResult> LdapPromise<R> sendExtendedRequest(final ExtendedRequest<R> request,
-            final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
-        final int messageID = msgIDGenerator.getAndIncrement();
-        final ExtendedResultLdapPromiseImpl<R> promise =
-                newExtendedLdapPromise(messageID, request, intermediateResponseHandler, this);
-        addPendingResult(messageID, promise);
-        writeRequest(messageID, request, intermediateResponseHandler, EXTENDED_WRITER);
-        return isStartTLSRequest(request) ? timestampBindOrStartTLSPromise(promise) : timestampPromise(promise);
-    }
-
-    private <R extends Result, Q extends Request> LdapPromise<R> sendRequest(Q request,
-            IntermediateResponseHandler intermediateResponseHandler, RequestWriter<Q> requestWriter) {
-        return sendRequest(request, intermediateResponseHandler, null, requestWriter);
-    }
-
-    @SuppressWarnings("unchecked")
-    private <R extends Result, Q extends Request> LdapPromise<R> sendRequest(Q request,
-            IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler,
-            RequestWriter<Q> requestWriter) {
-        final int messageID = newMessageID();
-        final ResultLdapPromiseImpl<?, ?> promise =
-            createResultPromise(messageID, request, intermediateResponseHandler, entryHandler);
-        try {
-            checkConnectionIsValid();
-            checkBindOrStartTLSInProgress();
-            addPendingResult(messageID, promise);
-            // Will rollback if the pending request was missed during the close then terminate is here.
-            checkConnectionIsValid();
-
-            writeRequest(messageID, request, intermediateResponseHandler, requestWriter);
-            return (LdapPromise<R>) timestampPromise(promise);
-        } catch (final LdapException e) {
-            removePendingResult(messageID);
-            return newFailedLdapPromise(e);
-        }
-    }
-
-    private ResultLdapPromiseImpl<?, ?> createResultPromise(final int messageID, final Request request,
-            final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
-        if (request instanceof CompareRequest) {
-            return newCompareLdapPromise(messageID, (CompareRequest) request, intermediateResponseHandler, this);
-        } else if (request instanceof SearchRequest) {
-            return newSearchLdapPromise(messageID, (SearchRequest) request, entryHandler,
-                    intermediateResponseHandler, this);
-        }
-
-        return newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
-    }
-
-    private <R> R timestamp(final R response) {
-        if (!(response instanceof ConnectionException)) {
-            lastResponseTimestamp = factory.getTimeService().now();
-        }
-        return response;
-    }
-
-    private <R extends Result> LdapPromise<R> timestampPromise(final LdapPromise<R> promise) {
-        if (!isHeartBeatConnection()) {
-            //Fast path.
-            return promise;
-        }
-
-        return promise.onSuccess(new SuccessHandler<R>() {
-            @Override
-            public void handleResult(R result) {
-                if (!(result instanceof ConnectionException)) {
-                    lastResponseTimestamp = factory.getTimeService().now();
-                }
-            }
-        }).onFailure(new FailureHandler<LdapException>() {
-            @Override
-            public void handleError(LdapException error) {
-                if (!(error instanceof ConnectionException)) {
-                    lastResponseTimestamp = factory.getTimeService().now();
-                }
-            }
-        });
-    }
-
-    private <R extends Result> LdapPromise<R> timestampBindOrStartTLSPromise(final LdapPromise<R> wrappedPromise) {
-        return timestampPromise(wrappedPromise).onSuccessOrFailure(new Runnable() {
-            @Override
-            public void run() {
-                setBindOrStartTLSInProgress(false);
-                releaseBindOrStartTLSLock();
-            }
-        });
-    }
-
-    private void failPendingResults(final LdapException error) {
-        // Notification is responsible for removing the element from the map.
-        while (!pendingResults.isEmpty()) {
-            pendingResults.values().iterator().next().handleError(error);
-        }
-    }
-
-    private void startBindOrStartTLS() throws LdapException {
-        if (!pendingResults.isEmpty()) {
-            throw newLdapException(newBindResult(ResultCode.OPERATIONS_ERROR)
-                    .setDiagnosticMessage("There are other operations pending on this connection"));
-        }
-
-        if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
-            throw newLdapException(newBindResult(ResultCode.OPERATIONS_ERROR)
-                    .setDiagnosticMessage("Bind or Start TLS operation in progress"));
-        }
-    }
-
-    private void checkBindOrStartTLSInProgress() throws LdapException {
-        if (bindOrStartTLSInProgress.get()) {
-            throw newLdapException(ResultCode.OPERATIONS_ERROR, "Bind or Start TLS operation in progress");
-        }
-    }
-
-    private <R extends Result> LdapPromise<R> enqueueBindOrStartTLSPromise(
-            final AsyncFunction<Void, R, LdapException> doRequest) {
-        /*
-         * A heart beat must be in progress so create a runnable task which
-         * will be executed when the heart beat completes.
-         */
-        final LdapPromiseImpl<Void> promise = newLdapPromiseImpl();
-        final LdapPromise<R> result = promise.thenAsync(doRequest);
-
-        // Enqueue and flush if the heart beat has completed in the mean time.
-        pendingBindOrStartTLSRequests.offer(new Runnable() {
-            @Override
-            public void run() {
-                // FIXME: Handle cancel chaining.
-                if (!result.isCancelled()) {
-                    sync.lockShared(); // Will not block.
-                    promise.handleResult((Void) null);
-                }
-            }
-        });
-        flushPendingBindOrStartTLSRequests();
-
-        return result;
-    }
-
-    private void flushPendingBindOrStartTLSRequests() {
-        if (!pendingBindOrStartTLSRequests.isEmpty()) {
-            /*
-             * The pending requests will acquire the shared lock, but we take
-             * it here anyway to ensure that pending requests do not get blocked.
-             */
-            if (sync.tryLockShared()) {
-                try {
-                    Runnable pendingRequest;
-                    while ((pendingRequest = pendingBindOrStartTLSRequests.poll()) != null) {
-                        // Dispatch the waiting request. This will not block.
-                        pendingRequest.run();
-                    }
-                } finally {
-                    sync.unlockShared();
-                }
-            }
-        }
-    }
-
-    private void releaseBindOrStartTLSLock() {
-        if (isHeartBeatConnection()) {
-            sync.unlockShared();
-        }
-    }
-
-}
diff --git a/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 7f8fb15..835d19b 100755
--- a/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1641,16 +1641,4 @@
 WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT=The provided \
  value "%s" could not be parsed as a valid assertion value because the \
  character '%c' is not allowed. The acceptable values are s (second), \
- m (minute), h (hour), D (date), M (month) and Y (year)
-#
-# Connection Timeout Messages
-#
-LDAP_CONNECTION_REQUEST_TIMEOUT=The request has failed because no response \
- was received from the server within the %d ms timeout
-LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT=The bind or StartTLS request \
- has failed because no response was received from the server within the %d ms \
- timeout. The LDAP connection is now in an invalid state and can no longer be used
-LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT=The LDAP connection has \
- failed because no bind or StartTLS response was received from the server \
- within the %d ms timeout
- 
\ No newline at end of file
+ m (minute), h (hour), D (date), M (month) and Y (year)
\ No newline at end of file
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
new file mode 100644
index 0000000..e650e16
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
@@ -0,0 +1,455 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2013-2014 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.PromiseImpl;
+import org.forgerock.util.promise.SuccessHandler;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.fest.assertions.Assertions.*;
+import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.ResultCode.*;
+import static org.forgerock.opendj.ldap.SearchScope.*;
+import static org.forgerock.opendj.ldap.TestCaseUtils.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+import static org.forgerock.opendj.ldap.responses.Responses.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests the connection pool implementation..
+ */
+@SuppressWarnings("javadoc")
+public class HeartBeatConnectionFactoryTestCase extends SdkTestCase {
+
+    // @formatter:off
+
+    /*
+     * Key features which need testing:
+     *
+     * - lazy scheduler registration and deregistration
+     * - scheduled task only sends heart-beat when connection is open
+     * - connection remains valid when any response is received
+     * - connection remains valid when a heart beat response is received
+     * - connection becomes invalid if no response is received during timeout
+     * - heart beat only sent when connection is idle
+     * - slow bind / startTLS prevents heart beat
+     * - slow heart beat prevents bind / start TLS
+     * - support concurrent bind / start TLS
+     */
+
+    // @formatter:on
+
+    private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT,
+            "(objectclass=*)", "1.1");
+
+    private Connection connection;
+    private ConnectionFactory factory;
+    private Connection hbc;
+    private HeartBeatConnectionFactory hbcf;
+    private List<ConnectionEventListener> listeners;
+    private MockScheduler scheduler;
+
+    /**
+     * Disables logging before the tests.
+     */
+    @BeforeClass
+    public void disableLogging() {
+        TestCaseUtils.setDefaultLogLevel(Level.SEVERE);
+    }
+
+    /**
+     * Re-enable logging after the tests.
+     */
+    @AfterClass
+    public void enableLogging() {
+        TestCaseUtils.setDefaultLogLevel(Level.INFO);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() {
+        try {
+            if (hbc != null) {
+                hbc.close();
+                assertThat(hbc.isValid()).isFalse();
+                assertThat(hbc.isClosed()).isTrue();
+            }
+            scheduler.runAllTasks(); // Flush any remaining timeout tasks.
+            assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check.
+            hbcf.close();
+        } finally {
+            connection = null;
+            factory = null;
+            hbcf = null;
+            listeners = null;
+            scheduler = null;
+            hbc = null;
+        }
+    }
+
+    @Test
+    public void testBindWhileHeartBeatInProgress() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        mockBindAsyncResponse();
+        hbc = hbcf.getConnection();
+
+        /*
+         * Send a heartbeat, trapping the search call-back so that we can send
+         * the response once we have attempted a bind.
+         */
+        when(connection.searchAsync(any(SearchRequest.class), any(SearchResultHandler.class))).thenReturn(
+            newSuccessfulLdapPromise(newResult(SUCCESS)));
+        when(hbcf.timeService.now()).thenReturn(11000L);
+        scheduler.runAllTasks(); // Send the heartbeat.
+
+        // Capture the heartbeat search result handler.
+        verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
+
+        /*
+         * Now attempt a bind request, which should be held in a queue until the
+         * heart beat completes.
+         */
+        hbc.bindAsync(newSimpleBindRequest());
+        verify(connection, times(0)).bindAsync(any(BindRequest.class));
+
+        // Send fake heartbeat response, releasing the bind request.
+        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
+    }
+
+    @Test
+    public void testGetConnection() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        hbc = hbcf.getConnection();
+        assertThat(hbc).isNotNull();
+        assertThat(hbc.isValid()).isTrue();
+    }
+
+    @Test
+    public void testGetConnectionAsync() throws Exception {
+        @SuppressWarnings("unchecked")
+        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
+
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        hbc = hbcf.getConnectionAsync().onSuccess(mockSuccessHandler).getOrThrow();
+        assertThat(hbc).isNotNull();
+        assertThat(hbc.isValid()).isTrue();
+
+        verify(mockSuccessHandler).handleResult(any(Connection.class));
+        verifyNoMoreInteractions(mockSuccessHandler);
+    }
+
+    @Test
+    public void testGetConnectionAsyncWithInitialHeartBeatError() throws Exception {
+        @SuppressWarnings("unchecked")
+        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
+        final PromiseImpl<LdapException, NeverThrowsException> promisedError = PromiseImpl.create();
+
+        mockConnectionWithInitialHeartbeatResult(ResultCode.BUSY);
+        Promise<? extends Connection, LdapException> promise = hbcf.getConnectionAsync();
+        promise.onSuccess(mockSuccessHandler).onFailure(new FailureHandler<LdapException>() {
+            @Override
+            public void handleError(LdapException error) {
+                promisedError.handleResult(error);
+            }
+        });
+
+        checkInitialHeartBeatFailure(promisedError.getOrThrow());
+
+        try {
+            promise.getOrThrow();
+            fail("Unexpectedly obtained a connection");
+        } catch (final LdapException e) {
+            checkInitialHeartBeatFailure(e);
+        }
+
+        verifyNoMoreInteractions(mockSuccessHandler);
+    }
+
+    @Test
+    public void testGetConnectionWithInitialHeartBeatError() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.BUSY);
+        try {
+            hbcf.getConnection();
+            fail("Unexpectedly obtained a connection");
+        } catch (final LdapException e) {
+            checkInitialHeartBeatFailure(e);
+        }
+    }
+
+    @Test
+    public void testHeartBeatSucceedsThenFails() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+
+        // Get a connection and check that it was pinged.
+        hbc = hbcf.getConnection();
+
+        verifyHeartBeatSent(connection, 1);
+        assertThat(scheduler.isScheduled()).isTrue(); // heartbeater
+        assertThat(listeners).hasSize(1);
+        assertThat(hbc.isValid()).isTrue();
+
+        // Invoke heartbeat before the connection is considered idle.
+        scheduler.runAllTasks();
+        verifyHeartBeatSent(connection, 1); // No heartbeat sent - not idle yet.
+        assertThat(hbc.isValid()).isTrue();
+
+        // Invoke heartbeat after the connection is considered idle.
+        when(hbcf.timeService.now()).thenReturn(6000L);
+        scheduler.runAllTasks();
+        verifyHeartBeatSent(connection, 2); // Heartbeat sent.
+        assertThat(hbc.isValid()).isTrue();
+
+        // Now force the heartbeat to fail.
+        mockHeartBeatResponse(connection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN);
+        when(hbcf.timeService.now()).thenReturn(11000L);
+        scheduler.runAllTasks();
+        verifyHeartBeatSent(connection, 3);
+        assertThat(hbc.isValid()).isFalse();
+
+        // Flush redundant timeout tasks.
+        scheduler.runAllTasks();
+
+        // Attempt to send a new request: it should fail immediately.
+        @SuppressWarnings("unchecked")
+        final FailureHandler<LdapException> mockHandler = mock(FailureHandler.class);
+        hbc.modifyAsync(newModifyRequest(DN.rootDN())).onFailure(mockHandler);
+        final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class);
+        verify(mockHandler).handleError(arg.capture());
+        assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(
+                ResultCode.CLIENT_SIDE_SERVER_DOWN);
+
+        assertThat(hbc.isValid()).isFalse();
+        assertThat(hbc.isClosed()).isFalse();
+    }
+
+    @Test
+    public void testHeartBeatTimeout() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+
+        // Get a connection and check that it was pinged.
+        hbc = hbcf.getConnection();
+
+        // Now force the heartbeat to fail due to timeout.
+        mockHeartBeatResponse(connection, listeners, null /* no response */);
+        when(hbcf.timeService.now()).thenReturn(11000L);
+        scheduler.runAllTasks(); // Send the heartbeat.
+        verifyHeartBeatSent(connection, 2);
+        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
+        when(hbcf.timeService.now()).thenReturn(12000L);
+        scheduler.runAllTasks(); // Check for heartbeat.
+        assertThat(hbc.isValid()).isFalse(); // Now invalid.
+        assertThat(hbc.isClosed()).isFalse();
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test(description = "OPENDJ-1348")
+    public void testBindPreventsHeartBeatTimeout() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        BindResultLdapPromiseImpl promise = mockBindAsyncResponse();
+        hbc = hbcf.getConnection();
+
+        /*
+         * Send a bind request, trapping the bind call-back so that we can send
+         * the response once we have attempted a heartbeat.
+         */
+        when(hbcf.timeService.now()).thenReturn(11000L);
+        hbc.bindAsync(newSimpleBindRequest());
+
+        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
+
+        // Verify no heartbeat is sent because there is a bind in progress.
+        when(hbcf.timeService.now()).thenReturn(11001L);
+        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
+        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+
+        // Send fake bind response, releasing the heartbeat.
+        when(hbcf.timeService.now()).thenReturn(11099L);
+        ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
+
+        // Check that bind response acts as heartbeat.
+        assertThat(hbc.isValid()).isTrue();
+        when(hbcf.timeService.now()).thenReturn(11100L);
+        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
+        assertThat(hbc.isValid()).isTrue();
+    }
+
+    @Test(description = "OPENDJ-1348")
+    public void testBindTriggersHeartBeatTimeoutWhenTooSlow() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        mockBindAsyncResponse();
+        hbc = hbcf.getConnection();
+
+        // Send another bind request which will timeout.
+        when(hbcf.timeService.now()).thenReturn(20000L);
+        hbc.bindAsync(newSimpleBindRequest());
+        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
+
+        // Verify no heartbeat is sent because there is a bind in progress.
+        when(hbcf.timeService.now()).thenReturn(20001L);
+        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
+        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+
+        // Check that lack of bind response acts as heartbeat timeout.
+        assertThat(hbc.isValid()).isTrue();
+        when(hbcf.timeService.now()).thenReturn(20100L);
+        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
+        assertThat(hbc.isValid()).isFalse();
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testHeartBeatWhileBindInProgress() throws Exception {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        //Trapping the callback of mockedConnection.bindAsync
+        BindResultLdapPromiseImpl promise = mockBindAsyncResponse();
+        hbc = hbcf.getConnection();
+        hbc.bindAsync(newSimpleBindRequest());
+
+        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
+
+        /*
+         * Now attempt the heartbeat which should not happen because there is a
+         * bind in progress.
+         */
+        when(hbcf.timeService.now()).thenReturn(11000L);
+        // Attempt to send the heartbeat.
+        scheduler.runAllTasks();
+        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+
+        // Send fake bind response, releasing the heartbeat.
+        ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
+
+        // Attempt to send a heartbeat again.
+        when(hbcf.timeService.now()).thenReturn(16000L);
+        // Attempt to send the heartbeat.
+        scheduler.runAllTasks();
+        verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+    }
+
+    @Test
+    public void testToString() {
+        mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
+        assertThat(hbcf.toString()).isNotNull();
+    }
+
+    private void checkInitialHeartBeatFailure(final LdapException e) {
+        /*
+         * Initial heartbeat failure should trigger connection exception with
+         * heartbeat cause.
+         */
+        assertThat(e).isInstanceOf(ConnectionException.class);
+        assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
+        assertThat(e.getCause()).isInstanceOf(LdapException.class);
+        assertThat(((LdapException) e.getCause()).getResult().getResultCode()).isEqualTo(ResultCode.BUSY);
+    }
+
+    private void mockConnectionWithInitialHeartbeatResult(final ResultCode initialHeartBeatResult) {
+        listeners = new LinkedList<ConnectionEventListener>();
+        connection = mockConnection(listeners);
+        when(connection.isValid()).thenReturn(true);
+        mockHeartBeatResponse(connection, listeners, initialHeartBeatResult);
+
+        // Underlying connection factory.
+        factory = mockConnectionFactory(connection);
+
+        // Create heart beat connection factory.
+        scheduler = new MockScheduler();
+        hbcf = new HeartBeatConnectionFactory(factory, 10000, 100, TimeUnit.MILLISECONDS, HEARTBEAT, scheduler);
+
+        // Set initial time stamp.
+        hbcf.timeService = mockTimeService(0);
+    }
+
+    private BindResultLdapPromiseImpl mockBindAsyncResponse() {
+        final BindResultLdapPromiseImpl bindPromise = newBindLdapPromise(-1, null, null, null, connection);
+        doAnswer(new Answer<LdapPromise<BindResult>>() {
+            @Override
+            public LdapPromise<BindResult> answer(final InvocationOnMock invocation) throws Throwable {
+                return bindPromise;
+            }
+        }).when(connection).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
+
+        return bindPromise;
+    }
+
+    private Connection mockHeartBeatResponse(final Connection mockConnection,
+            final List<ConnectionEventListener> listeners, final ResultCode resultCode) {
+        Answer<LdapPromise<Result>> answer = new Answer<LdapPromise<Result>>() {
+            @Override
+            public LdapPromise<Result> answer(final InvocationOnMock invocation) throws Throwable {
+                if (resultCode == null) {
+                    return newLdapPromiseImpl();
+                }
+
+                if (resultCode.isExceptional()) {
+                    final LdapException error = newLdapException(resultCode);
+                    if (error instanceof ConnectionException) {
+                        for (final ConnectionEventListener listener : listeners) {
+                            listener.handleConnectionError(false, error);
+                        }
+                    }
+                    return newFailedLdapPromise(error);
+                } else {
+                    return newSuccessfulLdapPromise(newResult(resultCode));
+                }
+            }
+        };
+
+        doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class), any(SearchResultHandler.class));
+        doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class),
+            any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
+
+        return mockConnection;
+    }
+
+    private void verifyHeartBeatSent(final Connection connection, final int times) {
+        verify(connection, times(times)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
index de84880..a52dfdb 100644
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -76,7 +76,6 @@
 import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
 import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
 
-import static org.forgerock.opendj.ldap.Connections.*;
 import static org.forgerock.opendj.ldap.LdapException.*;
 import static org.forgerock.opendj.ldap.TestCaseUtils.*;
 
@@ -549,7 +548,9 @@
             return;
         }
         sslContext = new SSLContextBuilder().getSSLContext();
-        listener = newLDAPListener(findFreeSocketAddress(), getInstance(), new LDAPListenerOptions().setBacklog(4096));
+        listener =
+                new LDAPListener(findFreeSocketAddress(), getInstance(),
+                        new LDAPListenerOptions().setBacklog(4096));
         isRunning = true;
     }
 
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
index ebf8b1e..7f7cbea 100644
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
@@ -45,7 +45,7 @@
  * A mock scheduled executor which allows unit tests to directly invoke
  * scheduled tasks without needing to wait.
  */
-public final class MockScheduler implements ScheduledExecutorService {
+final class MockScheduler implements ScheduledExecutorService {
 
     private final class ScheduledCallableFuture<T> implements ScheduledFuture<T>, Callable<T> {
         private final Callable<T> callable;
@@ -116,7 +116,7 @@
     /** Saved scheduled tasks. */
     private final List<Callable<?>> tasks = new CopyOnWriteArrayList<Callable<?>>();
 
-    public MockScheduler() {
+    MockScheduler() {
         // Nothing to do.
     }
 
@@ -230,11 +230,11 @@
         return tasks.get(0);
     }
 
-    public boolean isScheduled() {
+    boolean isScheduled() {
         return !tasks.isEmpty();
     }
 
-    public void runAllTasks() {
+    void runAllTasks() {
         for (final Callable<?> task : tasks) {
             runTask0(task);
         }
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java
deleted file mode 100644
index f061b80..0000000
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2013-2014 ForgeRock AS.
- */
-
-package org.forgerock.opendj.ldap.spi;
-
-import java.util.logging.Level;
-
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionException;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.IntermediateResponseHandler;
-import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
-import org.forgerock.opendj.ldap.MockScheduler;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.SdkTestCase;
-import org.forgerock.opendj.ldap.TestCaseUtils;
-import org.forgerock.opendj.ldap.requests.AbandonRequest;
-import org.forgerock.opendj.ldap.requests.BindClient;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.Request;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.requests.UnbindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.SuccessHandler;
-import org.mockito.ArgumentCaptor;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.fest.assertions.Assertions.*;
-import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.opendj.ldap.ResultCode.*;
-import static org.forgerock.opendj.ldap.SearchScope.*;
-import static org.forgerock.opendj.ldap.TestCaseUtils.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-import static org.forgerock.opendj.ldap.responses.Responses.*;
-import static org.mockito.Matchers.*;
-import static org.mockito.Mockito.*;
-
-/**
- * Tests the connection pool implementation..
- */
-@SuppressWarnings("javadoc")
-public class HeartBeatConnectionFactoryTestCase extends SdkTestCase {
-    /** Test timeout in ms for tests which need to wait for simulated network events. */
-    private static final long TEST_TIMEOUT_MS = 30L * 1000L;
-
-    // @formatter:off
-
-    /*
-     * Key features which need testing:
-     *
-     * - lazy scheduler registration and deregistration
-     * - scheduled task only sends heart-beat when connection is open
-     * - connection remains valid when any response is received
-     * - connection remains valid when a heart beat response is received
-     * - connection becomes invalid if no response is received during timeout
-     * - heart beat only sent when connection is idle
-     * - slow bind / startTLS prevents heart beat
-     * - slow heart beat prevents bind / start TLS
-     * - support concurrent bind / start TLS
-     */
-
-    // @formatter:on
-
-    private final class TestHeartBeatConnectionFactory extends AbstractLdapConnectionFactoryImpl {
-
-        public TestHeartBeatConnectionFactory(String host, int port, LDAPOptions options) {
-            super(host, port, options);
-        }
-
-        @Override
-        protected Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0() {
-            PromiseImpl<AbstractLdapConnectionImpl<?>, LdapException> result = PromiseImpl.create();
-            result.handleResult(connection);
-            return result;
-        }
-
-    }
-
-    /**
-     * This class defines a test connection which simulate interactions with
-     * LDAP server by returning promises for asynchronous bind and search
-     * requests.
-     * The promises state depends on the associated ResultCode object.
-     * If a result code is not null, a completed promise is returned.
-     * If a result code is null, the returned promise is pending and could be completed
-     * by a call to the complete promise methods.
-     */
-    private final class MockHeartBeatConnectionImpl extends AbstractLdapConnectionImpl<TestHeartBeatConnectionFactory> {
-
-        private ResultCode heartBeatRC;
-        private ResultCode bindRC;
-        private ResultLdapPromiseImpl<BindRequest, BindResult> bindPromise;
-        private ResultLdapPromiseImpl<SearchRequest, Result> searchPromise;
-
-        private int bindAsyncCalls;
-        private int searchAsyncCalls;
-
-        protected MockHeartBeatConnectionImpl(TestHeartBeatConnectionFactory attachedFactory,
-                ResultCode initialHeartBeatResult) {
-            super(attachedFactory);
-            heartBeatRC = initialHeartBeatResult;
-        }
-
-        @Override
-        protected LdapPromise<Void> abandonAsync0(int messageID, AbandonRequest request) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        protected void bindAsync0(int messageID, BindRequest request, BindClient bindClient,
-                IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
-            bindAsyncCalls++;
-            if (bindRC == null) {
-                bindPromise = (ResultLdapPromiseImpl<BindRequest, BindResult>) getPendingResult(messageID);
-            } else {
-                ResultLdapPromiseImpl<BindRequest, BindResult> promise = removePendingResult(messageID);
-                if (bindRC.isExceptional()) {
-                    final LdapException error = newLdapException(bindRC);
-                    if (error instanceof ConnectionException) {
-                        connectionErrorOccurred(false, error.getResult());
-                    }
-                }
-
-                promise.setResultOrError(newBindResult(bindRC));
-            }
-        }
-
-        private void completeBindPromise(ResultCode result) {
-            ResultLdapPromiseImpl<BindRequest, BindResult> promise = removePendingResult(bindPromise.getRequestID());
-            promise.setResultOrError(newBindResult(result));
-            bindPromise = null;
-        }
-
-        @Override
-        protected void close0(int messageID, UnbindRequest request, String reason) {
-            //noting to do.
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        protected <R extends Request> void writeRequest(int messageID, R request,
-                IntermediateResponseHandler intermediateResponseHandler, RequestWriter<R> requestWriter)
-                throws LdapException {
-            if (request instanceof SearchRequest) {
-                searchAsyncCalls++;
-                if (heartBeatRC == null) {
-                    searchPromise = (ResultLdapPromiseImpl<SearchRequest, Result>) getPendingResult(messageID);
-                } else {
-                    ResultLdapPromiseImpl<SearchRequest, Result> promise = removePendingResult(messageID);
-                    if (heartBeatRC.isExceptional()) {
-                        final LdapException error = newLdapException(heartBeatRC);
-                        if (error instanceof ConnectionException) {
-                            connectionErrorOccurred(false, error.getResult());
-                        }
-                    }
-
-                    promise.setResultOrError(newResult(heartBeatRC));
-                }
-            }
-        }
-
-        public void completeSearchPromise(ResultCode resultCode) {
-            ResultLdapPromiseImpl<SearchRequest, Result> promise = removePendingResult(searchPromise.getRequestID());
-            promise.setResultOrError(newResult(resultCode));
-            searchPromise = null;
-        }
-
-        @Override
-        protected boolean isTLSEnabled() {
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return getClass().getSimpleName();
-        }
-
-    }
-
-    private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT, "(objectclass=*)", "1.1");
-    private static final BindRequest BIND_REQUEST = newSimpleBindRequest();
-
-    private MockHeartBeatConnectionImpl connection;
-    private Connection hbc;
-    private TestHeartBeatConnectionFactory hbcf;
-    private MockScheduler scheduler;
-
-    /**
-     * Disables logging before the tests.
-     */
-    @BeforeClass
-    public void disableLogging() {
-        TestCaseUtils.setDefaultLogLevel(Level.SEVERE);
-    }
-
-    /**
-     * Re-enable logging after the tests.
-     */
-    @AfterClass
-    public void enableLogging() {
-        TestCaseUtils.setDefaultLogLevel(Level.INFO);
-    }
-
-    @AfterMethod(alwaysRun = true)
-    public void tearDown() {
-        try {
-            if (hbc != null) {
-                hbc.close();
-                assertThat(hbc.isValid()).isFalse();
-                assertThat(hbc.isClosed()).isTrue();
-            }
-            scheduler.runAllTasks(); // Flush any remaining timeout tasks.
-            assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check.
-            hbcf.close();
-        } finally {
-            connection = null;
-            hbcf = null;
-            scheduler = null;
-            hbc = null;
-        }
-    }
-
-    @Test
-    public void testBindWhileHeartBeatInProgress() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        hbc = hbcf.getConnection();
-
-        connection.heartBeatRC = null;
-        when(hbcf.timeService.now()).thenReturn(11000L);
-        scheduler.runAllTasks(); // Send the heartbeat.
-
-        // Check that connection was used for both initial request and first heart beat request.
-        verifySearchAsyncCalled(2);
-        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
-
-        // Now attempt a bind request, which should be held in a queue until the heart beat completes.
-        hbc.bindAsync(newSimpleBindRequest());
-        verifyBindAsyncCalled(0);
-
-        // Send fake heartbeat response, releasing the bind request.
-        connection.completeSearchPromise(ResultCode.SUCCESS);
-        verifyBindAsyncCalled(1);
-    }
-
-    @Test
-    public void testGetConnection() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        hbc = hbcf.getConnection();
-        assertThat(hbc).isNotNull();
-        assertThat(hbc.isValid()).isTrue();
-    }
-
-    @Test
-    public void testGetConnectionAsync() throws Exception {
-        @SuppressWarnings("unchecked")
-        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
-
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        hbc = hbcf.getConnectionAsync().onSuccess(mockSuccessHandler).getOrThrow();
-        assertThat(hbc).isNotNull();
-        assertThat(hbc.isValid()).isTrue();
-
-        verify(mockSuccessHandler).handleResult(any(Connection.class));
-        verifyNoMoreInteractions(mockSuccessHandler);
-    }
-
-    @Test
-    public void testGetConnectionAsyncWithInitialHeartBeatError() throws Exception {
-        @SuppressWarnings("unchecked")
-        final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
-        final PromiseImpl<LdapException, NeverThrowsException> promisedError = PromiseImpl.create();
-
-        mockConnectionWithInitialHeartBeatRC(ResultCode.BUSY);
-        Promise<? extends Connection, LdapException> promise = hbcf.getConnectionAsync();
-        promise.onSuccess(mockSuccessHandler).onFailure(new FailureHandler<LdapException>() {
-            @Override
-            public void handleError(LdapException error) {
-                promisedError.handleResult(error);
-            }
-        });
-
-        checkInitialHeartBeatFailure(promisedError.getOrThrow());
-
-        try {
-            promise.getOrThrow();
-            fail("Unexpectedly obtained a connection");
-        } catch (final LdapException e) {
-            checkInitialHeartBeatFailure(e);
-        }
-
-        verifyNoMoreInteractions(mockSuccessHandler);
-    }
-
-    @Test
-    public void testGetConnectionWithInitialHeartBeatError() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.BUSY);
-        try {
-            hbcf.getConnection();
-            fail("Unexpectedly obtained a connection");
-        } catch (final LdapException e) {
-            checkInitialHeartBeatFailure(e);
-        }
-    }
-
-    @Test
-    public void testHeartBeatSucceedsThenFails() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-
-        // Get a connection and check that it was pinged.
-        hbc = hbcf.getConnection();
-
-        verifySearchAsyncCalled(1);
-        assertThat(scheduler.isScheduled()).isTrue(); // heartbeater
-        assertThat(hbc.isValid()).isTrue();
-
-        // Invoke heartbeat before the connection is considered idle.
-        scheduler.runAllTasks();
-        verifySearchAsyncCalled(1); // No heartbeat sent - not idle yet.
-        assertThat(hbc.isValid()).isTrue();
-
-        // Invoke heartbeat after the connection is considered idle.
-        when(hbcf.timeService.now()).thenReturn(6000L);
-        scheduler.runAllTasks();
-        verifySearchAsyncCalled(2); // Heartbeat sent.
-        assertThat(hbc.isValid()).isTrue();
-
-        // Now force the heartbeat to fail.
-        connection.heartBeatRC = ResultCode.CLIENT_SIDE_SERVER_DOWN;
-        when(hbcf.timeService.now()).thenReturn(11000L);
-        scheduler.runAllTasks();
-        verifySearchAsyncCalled(3);
-        assertThat(hbc.isValid()).isFalse();
-
-        // Flush redundant timeout tasks.
-        scheduler.runAllTasks();
-
-        // Attempt to send a new request: it should fail immediately.
-        @SuppressWarnings("unchecked")
-        final FailureHandler<LdapException> mockHandler = mock(FailureHandler.class);
-        hbc.modifyAsync(newModifyRequest(DN.rootDN())).onFailure(mockHandler);
-        final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class);
-        verify(mockHandler).handleError(arg.capture());
-        assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
-
-        assertThat(hbc.isValid()).isFalse();
-        assertThat(hbc.isClosed()).isFalse();
-    }
-
-    @Test
-    public void testHeartBeatTimeout() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-
-        // Get a connection and check that it was pinged.
-        hbc = hbcf.getConnection();
-
-        // Now force the heartbeat to fail due to timeout.
-        connection.heartBeatRC = null; /* no response */
-        when(hbcf.timeService.now()).thenReturn(11000L);
-        scheduler.runAllTasks(); // Send the heartbeat.
-        verifySearchAsyncCalled(2);
-        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
-        when(hbcf.timeService.now()).thenReturn(12000L);
-        scheduler.runAllTasks(); // Check for heartbeat.
-        assertThat(hbc.isValid()).isFalse(); // Now invalid.
-        assertThat(hbc.isClosed()).isFalse();
-    }
-
-    @Test(description = "OPENDJ-1348")
-    public void testBindPreventsHeartBeatTimeout() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        mockNoBindAsyncResponse();
-        hbc = hbcf.getConnection();
-
-        /*
-         * Send a bind request, trapping the bind call-back so that we can send
-         * the response once we have attempted a heartbeat.
-         */
-        when(hbcf.timeService.now()).thenReturn(11000L);
-        hbc.bindAsync(newSimpleBindRequest());
-        verifyBindAsyncCalled(1);
-
-        // Verify no heartbeat is sent because there is a bind in progress.
-        when(hbcf.timeService.now()).thenReturn(11001L);
-        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
-        verifySearchAsyncCalled(1);
-
-        // Send fake bind response, releasing the heartbeat.
-        when(hbcf.timeService.now()).thenReturn(11099L);
-        connection.completeBindPromise(SUCCESS);
-
-        // Check that bind response acts as heartbeat.
-        assertThat(hbc.isValid()).isTrue();
-        when(hbcf.timeService.now()).thenReturn(11100L);
-        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
-        assertThat(hbc.isValid()).isTrue();
-    }
-
-    @Test(description = "OPENDJ-1348")
-    public void testBindTriggersHeartBeatTimeoutWhenTooSlow() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        mockNoBindAsyncResponse();
-        hbc = hbcf.getConnection();
-
-        // Send another bind request which will timeout.
-        when(hbcf.timeService.now()).thenReturn(20000L);
-        hbc.bindAsync(newSimpleBindRequest());
-        verifyBindAsyncCalled(1);
-
-        // Verify no heartbeat is sent because there is a bind in progress.
-        when(hbcf.timeService.now()).thenReturn(20001L);
-        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
-        verifySearchAsyncCalled(1);
-
-        // Check that lack of bind response acts as heartbeat timeout.
-        assertThat(hbc.isValid()).isTrue();
-        when(hbcf.timeService.now()).thenReturn(20100L);
-        scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
-        assertThat(hbc.isValid()).isFalse();
-    }
-
-    @Test
-    public void testHeartBeatWhileBindInProgress() throws Exception {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        //Trapping the callback of mockedConnection.bindAsync
-        mockNoBindAsyncResponse();
-        hbc = hbcf.getConnection();
-        hbc.bindAsync(newSimpleBindRequest());
-
-        verifyBindAsyncCalled(1);
-
-        //Now attempt the heartbeat which should not happen because there is a bind in progress.
-        when(hbcf.timeService.now()).thenReturn(11000L);
-        // Attempt to send the heartbeat.
-        scheduler.runAllTasks();
-        verifySearchAsyncCalled(1);
-
-        // Send fake bind response, releasing the heartbeat.
-        connection.completeBindPromise(SUCCESS);
-
-        // Attempt to send a heartbeat again.
-        when(hbcf.timeService.now()).thenReturn(16000L);
-        // Attempt to send the heartbeat.
-        scheduler.runAllTasks();
-        verifySearchAsyncCalled(2);
-    }
-
-    @Test(description = "OPENDJ-1607", timeOut = TEST_TIMEOUT_MS)
-    public void testAuthenticatedTimeout() throws Exception {
-        // Check that we detect timeout if server does not reply.
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS,
-                new LDAPOptions().setBindRequest(BIND_REQUEST));
-        mockNoBindAsyncResponse();
-        try {
-            // Attempt to send a bind request.
-            hbcf.getConnectionAsync().getOrThrow();
-            fail("Unexpectedly obtained a connection");
-        } catch (LdapException e) {
-            assertThat(e).isInstanceOf(ConnectionException.class);
-            assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
-        }
-    }
-
-    @Test(description = "OPENDJ-1607")
-    public void testHearBeatAgainstSecureServer() throws Exception {
-        // Check that the initial request is a bind request.
-        mockConnectionWithInitialHeartBeatRC(ResultCode.AUTHORIZATION_DENIED,
-                new LDAPOptions().setBindRequest(BIND_REQUEST));
-        mockBindAsyncResponse(ResultCode.SUCCESS);
-        hbc = hbcf.getConnection();
-        verifyBindAsyncCalled(1);
-    }
-
-    @Test
-    public void testToString() {
-        mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
-        assertThat(hbcf.toString()).isNotNull();
-    }
-
-    private void checkInitialHeartBeatFailure(final LdapException e) {
-        // Initial heartbeat failure should trigger connection exception with heartbeat cause.
-        assertThat(e).isInstanceOf(ConnectionException.class);
-        assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
-        assertThat(e.getCause()).isInstanceOf(LdapException.class);
-        assertThat(((LdapException) e.getCause()).getResult().getResultCode()).isEqualTo(ResultCode.BUSY);
-    }
-
-    private void mockConnectionWithInitialHeartBeatRC(final ResultCode initialHeartBeatRC) {
-        mockConnectionWithInitialHeartBeatRC(
-                initialHeartBeatRC,
-                new LDAPOptions().setHeartBeatInterval(10, SECONDS)
-                    .setHeartBeatSearchRequest(HEARTBEAT)
-                    .setTimeout(100, MILLISECONDS));
-    }
-
-    private void mockConnectionWithInitialHeartBeatRC(final ResultCode initialHeartBeatRC, LDAPOptions options) {
-        // Create heart beat connection factory.
-        scheduler = new MockScheduler();
-        if (options.getBindRequest() == null) {
-            options.setHeartBeatInterval(10000, MILLISECONDS).setHeartBeatSearchRequest(HEARTBEAT)
-                    .setHeartBeatScheduler(scheduler);
-        }
-
-        options.setTimeout(100, MILLISECONDS);
-        hbcf = new TestHeartBeatConnectionFactory("localhost", 1111, options);
-        // Set initial time stamp.
-        hbcf.timeService = mockTimeService(0);
-
-        connection = new MockHeartBeatConnectionImpl(hbcf, initialHeartBeatRC);
-    }
-
-    private void mockNoBindAsyncResponse() {
-        connection.bindRC = null;
-    }
-
-    private void mockBindAsyncResponse(final ResultCode resultCode) {
-        connection.bindRC = resultCode;
-    }
-
-    private void verifyBindAsyncCalled(int times) {
-        assertThat(connection.bindAsyncCalls).isEqualTo(times);
-    }
-
-    private void verifySearchAsyncCalled(final int times) {
-        assertThat(connection.searchAsyncCalls).isEqualTo(times);
-    }
-}
diff --git a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
index b59f093..e332639 100644
--- a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
+++ b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -21,37 +21,60 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2014 ForgeRock AS
+ *      Copyright 2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS
  */
-
 package org.forgerock.opendj.grizzly;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.io.LDAPWriter;
+import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LdapPromise;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.TimeoutEventListener;
 import org.forgerock.opendj.ldap.TrustManagers;
 import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
 import org.forgerock.opendj.ldap.requests.BindClient;
 import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
 import org.forgerock.opendj.ldap.requests.GenericBindRequest;
-import org.forgerock.opendj.ldap.requests.Request;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
 import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
 import org.forgerock.opendj.ldap.responses.ExtendedResult;
 import org.forgerock.opendj.ldap.responses.Responses;
 import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionImpl;
 import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl;
+import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl;
 import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl;
+import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl;
 import org.forgerock.util.Reject;
 import org.glassfish.grizzly.CompletionHandler;
 import org.glassfish.grizzly.filterchain.Filter;
@@ -62,7 +85,12 @@
 import static org.forgerock.opendj.ldap.LdapException.*;
 import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
 
-final class GrizzlyLDAPConnection extends AbstractLdapConnectionImpl<GrizzlyLDAPConnectionFactory> {
+import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
+
+/**
+ * LDAP connection implementation.
+ */
+final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements TimeoutEventListener {
     /**
      * A dummy SSL client engine configurator as SSLFilter only needs client
      * config. This prevents Grizzly from needlessly using JVM defaults which
@@ -71,87 +99,637 @@
     private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR;
     static {
         try {
-            DUMMY_SSL_ENGINE_CONFIGURATOR = new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager(
-                    TrustManagers.distrustAll()).getSSLContext());
+            DUMMY_SSL_ENGINE_CONFIGURATOR =
+                    new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager(
+                            TrustManagers.distrustAll()).getSSLContext());
         } catch (GeneralSecurityException e) {
             // This should never happen.
             throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e);
         }
     }
 
+    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+    private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
     private final org.glassfish.grizzly.Connection<?> connection;
+    private final AtomicInteger nextMsgID = new AtomicInteger(1);
+    private final GrizzlyLDAPConnectionFactory factory;
+    private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingRequests =
+            new ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>>();
+    private final Object stateLock = new Object();
+    /** Guarded by stateLock. */
+    private Result connectionInvalidReason;
+    private boolean failedDueToDisconnect;
+    private boolean isClosed;
+    private boolean isFailed;
+    private List<ConnectionEventListener> listeners;
 
-    @SuppressWarnings("rawtypes")
-    public GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection connection,
-            final GrizzlyLDAPConnectionFactory attachedFactory) {
-        super(attachedFactory);
+    /**
+     * Create a LDAP Connection with provided Grizzly connection and LDAP
+     * connection factory.
+     *
+     * @param connection
+     *            actual connection
+     * @param factory
+     *            factory that provides LDAP connections
+     */
+    GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection<?> connection,
+            final GrizzlyLDAPConnectionFactory factory) {
         this.connection = connection;
+        this.factory = factory;
     }
 
     @Override
-    protected LdapPromise<Void> abandonAsync0(final int messageID, final AbandonRequest request) {
+    public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
+        /*
+         * Need to be careful here since both abandonAsync and Promise.cancel can
+         * be called separately by the client application. Therefore
+         * promise.cancel() should abandon the request, and abandonAsync should
+         * cancel the promise. In addition, bind or StartTLS requests cannot be
+         * abandoned.
+         */
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                /*
+                 * If there is a bind or startTLS in progress then it must be
+                 * this request which is being abandoned. The following check
+                 * will prevent it from happening.
+                 */
+                checkBindOrStartTLSInProgress();
+            }
+        } catch (final LdapException e) {
+            return newFailedLdapPromise(e);
+        }
+
+        // Remove the promise associated with the request to be abandoned.
+        final ResultLdapPromiseImpl<?, ?> pendingRequest = pendingRequests.remove(request.getRequestID());
+        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.
+             */
+            return newSuccessfulLdapPromise((Void) null);
+        }
+
+        /*
+         * This will cancel the promise, but will also recursively invoke this
+         * method. Since the pending request has been removed, there is no risk
+         * of an infinite loop.
+         */
+        pendingRequest.cancel(false);
+
+        /*
+         * FIXME: there's a potential race condition here if a bind or startTLS
+         * is initiated just after we removed the pending request.
+         */
+        return sendAbandonRequest(request);
+    }
+
+    private LdapPromise<Void> sendAbandonRequest(final AbandonRequest request) {
         final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
         try {
+            final int messageID = nextMsgID.getAndIncrement();
             writer.writeAbandonRequest(messageID, request);
             connection.write(writer.getASN1Writer().getBuffer(), null);
             return newSuccessfulLdapPromise((Void) null, messageID);
         } catch (final IOException e) {
-            return newFailedLdapPromise(newLdapException(adaptRequestIOException(e)));
+            return newFailedLdapPromise(adaptRequestIOException(e));
         } finally {
             GrizzlyUtils.recycleWriter(writer);
         }
     }
 
     @Override
-    protected void bindAsync0(final int messageID, final BindRequest request, final BindClient bindClient,
-            final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
-        checkConnectionIsValid();
-        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+    public LdapPromise<Result> addAsync(final AddRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ResultLdapPromiseImpl<AddRequest, Result> promise =
+                newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
         try {
-            // Use the bind client to get the initial request instead of
-            // using the bind request passed to this method.
-            final GenericBindRequest initialRequest = bindClient.nextBindRequest();
-            writer.writeBindRequest(messageID, 3, initialRequest);
-            connection.write(writer.getASN1Writer().getBuffer(), null);
-        } catch (final IOException e) {
-            throw newLdapException(adaptRequestIOException(e));
-        } finally {
-            GrizzlyUtils.recycleWriter(writer);
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeAddRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public void addConnectionEventListener(final ConnectionEventListener listener) {
+        Reject.ifNull(listener);
+        final boolean notifyClose;
+        final boolean notifyErrorOccurred;
+        synchronized (stateLock) {
+            notifyClose = isClosed;
+            notifyErrorOccurred = isFailed;
+            if (!isClosed) {
+                if (listeners == null) {
+                    listeners = new CopyOnWriteArrayList<ConnectionEventListener>();
+                }
+                listeners.add(listener);
+            }
+        }
+        if (notifyErrorOccurred) {
+            // Use the reason provided in the disconnect notification.
+            listener.handleConnectionError(failedDueToDisconnect,
+                    newLdapException(connectionInvalidReason));
+        }
+        if (notifyClose) {
+            listener.handleConnectionClosed();
         }
     }
 
     @Override
-    protected void close0(final int messageID, UnbindRequest unbindRequest, String reason) {
-        Reject.ifNull(unbindRequest);
-        // This is the final client initiated close then release the connection
-        // and release resources.
-        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+    public LdapPromise<BindResult> bindAsync(final BindRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final BindClient context;
         try {
-            writer.writeUnbindRequest(messageID, unbindRequest);
-            connection.write(writer.getASN1Writer().getBuffer(), null);
-        } catch (final Exception ignore) {
-            /*
-             * Underlying channel probably blown up. Ignore all errors,
-             * including possibly runtime exceptions (see OPENDJ-672).
-             */
-        } finally {
-            GrizzlyUtils.recycleWriter(writer);
+            context = request.createBindClient(Connections.getHostString(factory.getSocketAddress()));
+        } catch (final LdapException e) {
+            return newFailedLdapPromise(e, messageID);
         }
-        getFactory().getTimeoutChecker().removeListener(this);
-        connection.closeSilently();
+
+        final BindResultLdapPromiseImpl promise =
+                newBindLdapPromise(messageID, request, context, intermediateResponseHandler, this);
+
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                if (!pendingRequests.isEmpty()) {
+                    promise.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                            "There are other operations pending on this connection"));
+                    return promise;
+                }
+                if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
+                    promise.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
+                            "Bind or Start TLS operation in progress"));
+                    return promise;
+                }
+                pendingRequests.put(messageID, promise);
+            }
+
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    // Use the bind client to get the initial request instead of
+                    // using the bind request passed to this method.
+                    final GenericBindRequest initialRequest = context.nextBindRequest();
+                    writer.writeBindRequest(messageID, 3, initialRequest);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                bindOrStartTLSInProgress.set(false);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+
+        return promise;
     }
 
     @Override
-    protected <R extends Request> void writeRequest(final int messageID, final R request,
-            final IntermediateResponseHandler intermediateResponseHandler, final RequestWriter<R> requestWriter) {
-        final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+    public void close(final UnbindRequest request, final String reason) {
+        // FIXME: I18N need to internationalize this message.
+        Reject.ifNull(request);
+        close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
+                .setDiagnosticMessage(reason != null ? reason : "Connection closed by client"));
+    }
+
+    @Override
+    public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ResultLdapPromiseImpl<CompareRequest, CompareResult> promise =
+                newCompareLdapPromise(messageID, request, intermediateResponseHandler, this);
         try {
-            requestWriter.writeRequest(messageID, writer, request);
-            connection.write(writer.getASN1Writer().getBuffer(), null);
-        } catch (final IOException e) {
-            removePendingResult(messageID).setResultOrError(adaptRequestIOException(e));
-        } finally {
-            GrizzlyUtils.recycleWriter(writer);
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeCompareRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public LdapPromise<Result> deleteAsync(final DeleteRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ResultLdapPromiseImpl<DeleteRequest, Result> promise =
+                newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeDeleteRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ExtendedResultLdapPromiseImpl<R> promise =
+                newExtendedLdapPromise(messageID, request, intermediateResponseHandler, this);
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                if (StartTLSExtendedRequest.OID.equals(request.getOID())) {
+                    if (!pendingRequests.isEmpty()) {
+                        promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                                ResultCode.OPERATIONS_ERROR, "", "There are pending operations on this connection"));
+                        return promise;
+                    } else if (isTLSEnabled()) {
+                        promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                                ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled"));
+                        return promise;
+                    } else if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
+                        promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
+                                ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress"));
+                        return promise;
+                    }
+                } else {
+                    checkBindOrStartTLSInProgress();
+                }
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeExtendedRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                bindOrStartTLSInProgress.set(false);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public boolean isClosed() {
+        synchronized (stateLock) {
+            return isClosed;
+        }
+    }
+
+    @Override
+    public boolean isValid() {
+        synchronized (stateLock) {
+            return isValid0();
+        }
+    }
+
+    @Override
+    public LdapPromise<Result> modifyAsync(final ModifyRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ResultLdapPromiseImpl<ModifyRequest, Result> promise =
+                newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeModifyRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
+            final IntermediateResponseHandler intermediateResponseHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final ResultLdapPromiseImpl<ModifyDNRequest, Result> promise =
+                newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeModifyDNRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public void removeConnectionEventListener(final ConnectionEventListener listener) {
+        Reject.ifNull(listener);
+        synchronized (stateLock) {
+            if (listeners != null) {
+                listeners.remove(listener);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public LdapPromise<Result> searchAsync(final SearchRequest request,
+        final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
+        final int messageID = nextMsgID.getAndIncrement();
+        final SearchResultLdapPromiseImpl promise =
+                newSearchLdapPromise(messageID, request, entryHandler, intermediateResponseHandler, this);
+        try {
+            synchronized (stateLock) {
+                checkConnectionIsValid();
+                checkBindOrStartTLSInProgress();
+                pendingRequests.put(messageID, promise);
+            }
+            try {
+                final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+                try {
+                    writer.writeSearchRequest(messageID, request);
+                    connection.write(writer.getASN1Writer().getBuffer(), null);
+                } finally {
+                    GrizzlyUtils.recycleWriter(writer);
+                }
+            } catch (final IOException e) {
+                pendingRequests.remove(messageID);
+                throw adaptRequestIOException(e);
+            }
+        } catch (final LdapException e) {
+            promise.adaptErrorResult(e.getResult());
+        }
+        return promise;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("LDAPConnection(");
+        builder.append(connection.getLocalAddress());
+        builder.append(',');
+        builder.append(connection.getPeerAddress());
+        builder.append(')');
+        return builder.toString();
+    }
+
+    @Override
+    public long handleTimeout(final long currentTime) {
+        final long timeout = factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
+        if (timeout <= 0) {
+            return 0;
+        }
+
+        long delay = timeout;
+        for (final ResultLdapPromiseImpl<?, ?> promise : pendingRequests.values()) {
+            if (promise == null || !promise.checkForTimeout()) {
+                continue;
+            }
+            final long diff = (promise.getTimestamp() + timeout) - currentTime;
+            if (diff > 0) {
+                // Will expire in diff milliseconds.
+                delay = Math.min(delay, diff);
+            } else if (pendingRequests.remove(promise.getRequestID()) == null) {
+                // Result arrived at the same time.
+                continue;
+            } else if (promise.isBindOrStartTLS()) {
+                /*
+                 * No other operations can be performed while a bind or StartTLS
+                 * request is active, so we cannot time out the request. We
+                 * therefore have a choice: either ignore timeouts for these
+                 * operations, or enforce them but doing so requires
+                 * invalidating the connection. We'll do the latter, since
+                 * ignoring timeouts could cause the application to hang.
+                 */
+                logger.debug(LocalizableMessage.raw("Failing bind or StartTLS request due to timeout %s"
+                        + "(connection will be invalidated): ", promise));
+                final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+                        LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout).toString());
+                promise.adaptErrorResult(result);
+
+                // Fail the connection.
+                final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+                        LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout).toString());
+                connectionErrorOccurred(errorResult);
+            } else {
+                logger.debug(LocalizableMessage.raw("Failing request due to timeout: %s", promise));
+                final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+                        LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
+                promise.adaptErrorResult(result);
+
+                /*
+                 * FIXME: there's a potential race condition here if a bind or
+                 * startTLS is initiated just after we check the boolean. It
+                 * seems potentially even more dangerous to send the abandon
+                 * request while holding the state lock, since a blocking write
+                 * could hang the application.
+                 */
+                // if (!bindOrStartTLSInProgress.get()) {
+                // sendAbandonRequest(newAbandonRequest(promise.getRequestID()));
+                // }
+            }
+        }
+        return delay;
+    }
+
+    @Override
+    public long getTimeout() {
+        return factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Closes this connection, invoking event listeners as needed.
+     *
+     * @param unbindRequest
+     *            The client provided unbind request if this is a client
+     *            initiated close, or {@code null} if the connection has failed.
+     * @param isDisconnectNotification
+     *            {@code true} if this is a connection failure signalled by a
+     *            server disconnect notification.
+     * @param reason
+     *            The result indicating why the connection was closed.
+     */
+    void close(final UnbindRequest unbindRequest, final boolean isDisconnectNotification,
+            final Result reason) {
+        final boolean notifyClose;
+        final boolean notifyErrorOccurred;
+        final List<ConnectionEventListener> tmpListeners;
+        synchronized (stateLock) {
+            if (isClosed) {
+                // Already closed locally.
+                return;
+            } else if (unbindRequest != null) {
+                // Local close.
+                notifyClose = true;
+                notifyErrorOccurred = false;
+                isClosed = true;
+                tmpListeners = listeners;
+                listeners = null; // Prevent future invocations.
+                if (connectionInvalidReason == null) {
+                    connectionInvalidReason = reason;
+                }
+            } else if (isFailed) {
+                // Already failed.
+                return;
+            } else {
+                // Connection has failed and this is the first indication.
+                notifyClose = false;
+                notifyErrorOccurred = true;
+                isFailed = true;
+                failedDueToDisconnect = isDisconnectNotification;
+                connectionInvalidReason = reason;
+                tmpListeners = listeners; // Keep list for client close.
+            }
+        }
+
+        // First abort all outstanding requests.
+        for (final int requestID : pendingRequests.keySet()) {
+            final ResultLdapPromiseImpl<?, ?> promise = pendingRequests.remove(requestID);
+            if (promise != null) {
+                promise.adaptErrorResult(connectionInvalidReason);
+            }
+        }
+
+        /*
+         * If this is the final client initiated close then release close the
+         * connection and release resources.
+         */
+        if (notifyClose) {
+            final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
+            try {
+                writer.writeUnbindRequest(nextMsgID.getAndIncrement(), unbindRequest);
+                connection.write(writer.getASN1Writer().getBuffer(), null);
+            } catch (final Exception ignore) {
+                /*
+                 * Underlying channel probably blown up. Ignore all errors,
+                 * including possibly runtime exceptions (see OPENDJ-672).
+                 */
+            } finally {
+                GrizzlyUtils.recycleWriter(writer);
+            }
+            factory.getTimeoutChecker().removeListener(this);
+            connection.closeSilently();
+            factory.releaseTransportAndTimeoutChecker();
+        }
+
+        // Notify listeners.
+        if (tmpListeners != null) {
+            if (notifyErrorOccurred) {
+                for (final ConnectionEventListener listener : tmpListeners) {
+                    // Use the reason provided in the disconnect notification.
+                    listener.handleConnectionError(isDisconnectNotification, newLdapException(reason));
+                }
+            }
+            if (notifyClose) {
+                for (final ConnectionEventListener listener : tmpListeners) {
+                    listener.handleConnectionClosed();
+                }
+            }
+        }
+    }
+
+    int continuePendingBindRequest(final BindResultLdapPromiseImpl promise) throws LdapException {
+        final int newMsgID = nextMsgID.getAndIncrement();
+        synchronized (stateLock) {
+            checkConnectionIsValid();
+            pendingRequests.put(newMsgID, promise);
+        }
+        return newMsgID;
+    }
+
+    LDAPOptions getLDAPOptions() {
+        return factory.getLDAPOptions();
+    }
+
+    ResultLdapPromiseImpl<?, ?> getPendingRequest(final Integer messageID) {
+        return pendingRequests.get(messageID);
+    }
+
+    void handleUnsolicitedNotification(final ExtendedResult result) {
+        final List<ConnectionEventListener> tmpListeners;
+        synchronized (stateLock) {
+            tmpListeners = listeners;
+        }
+        if (tmpListeners != null) {
+            for (final ConnectionEventListener listener : tmpListeners) {
+                listener.handleUnsolicitedNotification(result);
+            }
         }
     }
 
@@ -163,14 +741,19 @@
      *            The filter to be installed.
      */
     void installFilter(final Filter filter) {
-        synchronized (state) {
+        synchronized (stateLock) {
             GrizzlyUtils.addFilterToConnection(filter, connection);
         }
     }
 
-    @Override
-    protected boolean isTLSEnabled() {
-        synchronized (state) {
+    /**
+     * Indicates whether or not TLS is enabled on this connection.
+     *
+     * @return {@code true} if TLS is enabled on this connection, otherwise
+     *         {@code false}.
+     */
+    boolean isTLSEnabled() {
+        synchronized (stateLock) {
             final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
             for (final Filter filter : currentFilterChain) {
                 if (filter instanceof SSLFilter) {
@@ -181,9 +764,17 @@
         }
     }
 
+    ResultLdapPromiseImpl<?, ?> removePendingRequest(final Integer messageID) {
+        return pendingRequests.remove(messageID);
+    }
+
+    void setBindOrStartTLSInProgress(final boolean state) {
+        bindOrStartTLSInProgress.set(state);
+    }
+
     void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites,
             final CompletionHandler<SSLEngine> completionHandler) throws IOException {
-        synchronized (state) {
+        synchronized (stateLock) {
             if (isTLSEnabled()) {
                 throw new IllegalStateException("TLS already enabled");
             }
@@ -200,41 +791,43 @@
         }
     }
 
-    private Result adaptRequestIOException(final IOException e) {
+    private LdapException adaptRequestIOException(final IOException e) {
         // FIXME: what other sort of IOExceptions can be thrown?
         // FIXME: Is this the best result code?
         final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
-        connectionErrorOccurred(false, errorResult);
-        return errorResult;
+        connectionErrorOccurred(errorResult);
+        return newLdapException(errorResult);
     }
 
-    @Override
-    protected ResultLdapPromiseImpl<?, ?> getPendingResult(int messageID) {
-        return super.getPendingResult(messageID);
-    };
-
-    @Override
-    protected <R extends Request, S extends Result> ResultLdapPromiseImpl<R, S> removePendingResult(
-            final Integer messageID) {
-        return super.removePendingResult(messageID);
+    private void checkBindOrStartTLSInProgress() throws LdapException {
+        if (bindOrStartTLSInProgress.get()) {
+            throw newLdapException(ResultCode.OPERATIONS_ERROR, "Bind or Start TLS operation in progress");
+        }
     }
 
-    @Override
-    protected void connectionErrorOccurred(boolean isDisconnectNotification, Result reason) {
-        super.connectionErrorOccurred(isDisconnectNotification, reason);
+    private void checkConnectionIsValid() throws LdapException {
+        if (!isValid0()) {
+            if (failedDueToDisconnect) {
+                /*
+                 * Connection termination was triggered remotely. We don't want
+                 * to blindly pass on the result code to requests since it could
+                 * be confused for a genuine response. For example, if the
+                 * disconnect contained the invalidCredentials result code then
+                 * this could be misinterpreted as a genuine authentication
+                 * failure for subsequent bind requests.
+                 */
+                throw newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, "Connection closed by server");
+            } else {
+                throw newLdapException(connectionInvalidReason);
+            }
+        }
     }
 
-    @Override
-    protected void handleUnsolicitedNotification(final ExtendedResult notification) {
-        super.handleUnsolicitedNotification(notification);
+    private void connectionErrorOccurred(final Result reason) {
+        close(null, false, reason);
     }
 
-    int continuePendingBindRequest(BindResultLdapPromiseImpl promise)
-            throws LdapException {
-        final int newMsgID = newMessageID();
-        checkConnectionIsValid();
-        addPendingResult(newMsgID, promise);
-
-        return newMsgID;
+    private boolean isValid0() {
+        return !isFailed && !isClosed;
     }
 }
diff --git a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
index 9a1f278..8df6801 100644
--- a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
+++ b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -28,8 +28,11 @@
 package org.forgerock.opendj.grizzly;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLEngine;
 
@@ -40,12 +43,15 @@
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.TimeoutChecker;
 import org.forgerock.opendj.ldap.TimeoutEventListener;
-import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionFactoryImpl;
-import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionImpl;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
 import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl;
-import org.forgerock.util.promise.Function;
+import org.forgerock.util.promise.FailureHandler;
 import org.forgerock.util.promise.Promise;
 import org.forgerock.util.promise.PromiseImpl;
+import org.forgerock.util.promise.SuccessHandler;
+import org.glassfish.grizzly.CompletionHandler;
 import org.glassfish.grizzly.EmptyCompletionHandler;
 import org.glassfish.grizzly.SocketConnectorHandler;
 import org.glassfish.grizzly.filterchain.FilterChain;
@@ -64,21 +70,19 @@
 /**
  * LDAP connection factory implementation using Grizzly for transport.
  */
-public final class GrizzlyLDAPConnectionFactory extends AbstractLdapConnectionFactoryImpl implements
-        LDAPConnectionFactoryImpl {
+public final class GrizzlyLDAPConnectionFactory implements LDAPConnectionFactoryImpl {
     private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
     /**
-     * Adapts a Grizzly connection completion handler to an LDAP connection
-     * promise.
+     * Adapts a Grizzly connection completion handler to an LDAP connection promise.
      */
     @SuppressWarnings("rawtypes")
-    private final class CompletionHandlerAdapter extends EmptyCompletionHandler<org.glassfish.grizzly.Connection>
-            implements TimeoutEventListener {
-        private final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise;
+    private final class CompletionHandlerAdapter implements
+            CompletionHandler<org.glassfish.grizzly.Connection>, TimeoutEventListener {
+        private final PromiseImpl<Connection, LdapException> promise;
         private final long timeoutEndTime;
 
-        private CompletionHandlerAdapter(final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise) {
+        private CompletionHandlerAdapter(final PromiseImpl<Connection, LdapException> promise) {
             this.promise = promise;
             final long timeoutMS = getTimeout();
             this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0;
@@ -86,11 +90,68 @@
         }
 
         @Override
-        public void completed(final org.glassfish.grizzly.Connection connection) {
-            timeoutChecker.get().removeListener(this);
-            if (!promise.tryHandleResult(connection)) {
-                // The connection has been either cancelled or it has timed out.
+        public void cancelled() {
+            // Ignore this.
+        }
+
+        @Override
+        public void completed(final org.glassfish.grizzly.Connection result) {
+            // Adapt the connection.
+            final GrizzlyLDAPConnection connection = adaptConnection(result);
+
+            // Plain connection.
+            if (options.getSSLContext() == null) {
+                onSuccess(connection);
+                return;
+            }
+
+            // Start TLS or install SSL layer asynchronously.
+
+            // Give up immediately if the promise has been cancelled or timed out.
+            if (promise.isDone()) {
+                timeoutChecker.get().removeListener(this);
                 connection.close();
+                return;
+            }
+
+            if (options.useStartTLS()) {
+                // Chain StartTLS extended request.
+                final StartTLSExtendedRequest startTLS =
+                        Requests.newStartTLSExtendedRequest(options.getSSLContext());
+                startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites().toArray(
+                    new String[options.getEnabledCipherSuites().size()]));
+                startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray(
+                    new String[options.getEnabledProtocols().size()]));
+
+                connection.extendedRequestAsync(startTLS).onSuccess(new SuccessHandler<ExtendedResult>() {
+                    @Override
+                    public void handleResult(final ExtendedResult result) {
+                        onSuccess(connection);
+                    }
+                }).onFailure(new FailureHandler<LdapException>() {
+                    @Override
+                    public void handleError(final LdapException error) {
+                        onFailure(connection, error);
+                    }
+                });
+            } else {
+                // Install SSL/TLS layer.
+                try {
+                    connection.startTLS(options.getSSLContext(), options.getEnabledProtocols(),
+                        options.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
+                            @Override
+                            public void completed(final SSLEngine result) {
+                                onSuccess(connection);
+                            }
+
+                            @Override
+                            public void failed(final Throwable throwable) {
+                                onFailure(connection, throwable);
+                            }
+                        });
+                } catch (final IOException e) {
+                    onFailure(connection, e);
+                }
             }
         }
 
@@ -99,6 +160,51 @@
             // Adapt and forward.
             timeoutChecker.get().removeListener(this);
             promise.handleError(adaptConnectionException(throwable));
+            releaseTransportAndTimeoutChecker();
+        }
+
+        @Override
+        public void updated(final org.glassfish.grizzly.Connection result) {
+            // Ignore this.
+        }
+
+        private GrizzlyLDAPConnection adaptConnection(
+                final org.glassfish.grizzly.Connection<?> connection) {
+            configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(), options
+                    .isReuseAddress(), options.getLinger(), logger);
+
+            final GrizzlyLDAPConnection ldapConnection =
+                    new GrizzlyLDAPConnection(connection, GrizzlyLDAPConnectionFactory.this);
+            timeoutChecker.get().addListener(ldapConnection);
+            clientFilter.registerConnection(connection, ldapConnection);
+            return ldapConnection;
+        }
+
+        private LdapException adaptConnectionException(Throwable t) {
+            if (!(t instanceof LdapException) && t instanceof ExecutionException) {
+                t = t.getCause() != null ? t.getCause() : t;
+            }
+
+            if (t instanceof LdapException) {
+                return (LdapException) t;
+            } else {
+                return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), t);
+            }
+        }
+
+        private void onFailure(final GrizzlyLDAPConnection connection, final Throwable t) {
+            // Abort connection attempt due to error.
+            timeoutChecker.get().removeListener(this);
+            promise.handleError(adaptConnectionException(t));
+            connection.close();
+        }
+
+        private void onSuccess(final GrizzlyLDAPConnection connection) {
+            timeoutChecker.get().removeListener(this);
+            if (!promise.tryHandleResult(connection)) {
+                // The connection has been either cancelled or it has timed out.
+                connection.close();
+            }
         }
 
         @Override
@@ -122,24 +228,25 @@
 
     private final LDAPClientFilter clientFilter;
     private final FilterChain defaultFilterChain;
-    private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
-    private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER.acquire();
+    private final LDAPOptions options;
+    private final String host;
+    private final int port;
 
-    @SuppressWarnings("rawtypes")
-    private final Function<org.glassfish.grizzly.Connection, AbstractLdapConnectionImpl<?>, LdapException>
-    convertToLDAPConnection =
-        new Function<org.glassfish.grizzly.Connection, AbstractLdapConnectionImpl<?>, LdapException>() {
-            @Override
-            public GrizzlyLDAPConnection apply(org.glassfish.grizzly.Connection connection) throws LdapException {
-                configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(),
-                    options.isReuseAddress(), options.getLinger(), logger);
-                final GrizzlyLDAPConnection ldapConnection =
-                    new GrizzlyLDAPConnection(connection, GrizzlyLDAPConnectionFactory.this);
-                timeoutChecker.get().addListener(ldapConnection);
-                clientFilter.registerConnection(connection, ldapConnection);
-                return ldapConnection;
-            }
-        };
+    /**
+     * Prevents the transport and timeoutChecker being released when there are
+     * remaining references (this factory or any connections). It is initially
+     * set to 1 because this factory has a reference.
+     */
+    private final AtomicInteger referenceCount = new AtomicInteger(1);
+
+    /**
+     * Indicates whether this factory has been closed or not.
+     */
+    private final AtomicBoolean isClosed = new AtomicBoolean();
+
+    private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
+    private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER
+            .acquire();
 
     /**
      * Creates a new LDAP connection factory based on Grizzly which can be used
@@ -155,15 +262,6 @@
      */
     public GrizzlyLDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
         this(host, port, options, null);
-
-    }
-
-    private LdapException adaptConnectionException(Throwable t) {
-        if (t instanceof LdapException) {
-            return (LdapException) t;
-        }
-        t = t instanceof ExecutionException && t.getCause() != null ? t.getCause() : t;
-        return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), t);
     }
 
     /**
@@ -183,60 +281,88 @@
      *            connections. If {@code null}, default transport will be used.
      */
     public GrizzlyLDAPConnectionFactory(final String host, final int port, final LDAPOptions options,
-            final TCPNIOTransport transport) {
-        super(host, port, options);
+                                        TCPNIOTransport transport) {
         this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
-        this.clientFilter = new LDAPClientFilter(options.getDecodeOptions(), 0);
-        this.defaultFilterChain = buildFilterChain(this.transport.get().getProcessor(), clientFilter);
+        this.host = host;
+        this.port = port;
+        this.options = new LDAPOptions(options);
+        this.clientFilter = new LDAPClientFilter(this.options.getDecodeOptions(), 0);
+        this.defaultFilterChain =
+                buildFilterChain(this.transport.get().getProcessor(), clientFilter);
+    }
+
+    @Override
+    public void close() {
+        if (isClosed.compareAndSet(false, true)) {
+            releaseTransportAndTimeoutChecker();
+        }
+    }
+
+    @Override
+    public Connection getConnection() throws LdapException {
+        try {
+            return getConnectionAsync().getOrThrow();
+        } catch (final InterruptedException e) {
+            throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
+        }
+    }
+
+    @Override
+    public Promise<Connection, LdapException> getConnectionAsync() {
+        acquireTransportAndTimeoutChecker(); // Protect resources.
+        final SocketConnectorHandler connectorHandler =
+                TCPNIOConnectorHandler.builder(transport.get()).processor(defaultFilterChain)
+                        .build();
+        final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
+        connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise));
+        return promise;
+    }
+
+    @Override
+    public InetSocketAddress getSocketAddress() {
+        return new InetSocketAddress(host, port);
+    }
+
+    @Override
+    public String getHostName() {
+        return host;
+    }
+
+    @Override
+    public int getPort() {
+        return port;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "(" + host + ':' + port + ')';
     }
 
     TimeoutChecker getTimeoutChecker() {
         return timeoutChecker.get();
     }
 
-    @Override
-    @SuppressWarnings("rawtypes")
-    protected Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0() {
-        final SocketConnectorHandler connectorHandler = TCPNIOConnectorHandler.builder(transport.get())
-                .processor(defaultFilterChain).build();
-        final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise = PromiseImpl.create();
-        connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise));
-
-        return promise.then(convertToLDAPConnection);
+    LDAPOptions getLDAPOptions() {
+        return options;
     }
 
-    @Override
-    protected Promise<Void, LdapException> installSecureLayer(final Connection connection) {
-        final PromiseImpl<Void, LdapException> sslHandshakePromise = PromiseImpl.create();
-        try {
-            final GrizzlyLDAPConnection grizzlyConnection = (GrizzlyLDAPConnection) connection;
-            grizzlyConnection.startTLS(options.getSSLContext(), options.getEnabledProtocols(),
-                    options.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
-                        @Override
-                        public void completed(final SSLEngine result) {
-                            if (!sslHandshakePromise.tryHandleResult(null)) {
-                                // The connection has been either cancelled or
-                                // it has timed out.
-                                connection.close();
-                            }
-                        }
-
-                        @Override
-                        public void failed(final Throwable throwable) {
-                            sslHandshakePromise.handleError(adaptConnectionException(throwable));
-                        }
-                    });
-        } catch (final IOException e) {
-            sslHandshakePromise.handleError(adaptConnectionException(e));
+    void releaseTransportAndTimeoutChecker() {
+        if (referenceCount.decrementAndGet() == 0) {
+            transport.release();
+            timeoutChecker.release();
         }
-
-        return sslHandshakePromise;
     }
 
-    @Override
-    protected void releaseImplResources() {
-        transport.release();
-        timeoutChecker.release();
+    private void acquireTransportAndTimeoutChecker() {
+        /*
+         * If the factory is not closed then we need to prevent the resources
+         * (transport, timeout checker) from being released while the connection
+         * attempt is in progress.
+         */
+        referenceCount.incrementAndGet();
+        if (isClosed.get()) {
+            releaseTransportAndTimeoutChecker();
+            throw new IllegalStateException("Attempted to get a connection after factory close");
+        }
     }
-
 }
diff --git a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
index 251ec93..bf98a32 100644
--- a/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
+++ b/opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -79,7 +79,7 @@
  */
 final class LDAPClientFilter extends LDAPBaseFilter {
     private static final Attribute<GrizzlyLDAPConnection> LDAP_CONNECTION_ATTR =
-            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("GrizzlyLdapClientConnection");
+            Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPClientConnection");
 
     private static final Attribute<ClientResponseHandler> RESPONSE_HANDLER_ATTR =
             Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ClientResponseHandler");
@@ -109,7 +109,6 @@
          *
          * @return the reader to read incoming LDAP messages
          */
-        @Override
         public LDAPReader<ASN1BufferReader> getReader() {
             return this.reader;
         }
@@ -120,7 +119,7 @@
                 IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof AddRequest) {
                         pendingRequest.setResultOrError(result);
@@ -136,7 +135,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest instanceof BindResultLdapPromiseImpl) {
                         final BindResultLdapPromiseImpl promise = (BindResultLdapPromiseImpl) pendingRequest;
@@ -144,7 +143,8 @@
 
                         try {
                             if (!bindClient.evaluateResult(result)) {
-                                // The server is expecting a multi stage bind response.
+                                // The server is expecting a multi stage
+                                // bind response.
                                 final int msgID = ldapConnection.continuePendingBindRequest(promise);
 
                                 LDAPWriter<ASN1BufferWriter> ldapWriter = GrizzlyUtils.getWriter();
@@ -202,7 +202,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof CompareRequest) {
                         ((ResultLdapPromiseImpl<CompareRequest, CompareResult>) pendingRequest)
@@ -220,7 +220,7 @@
                 IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof DeleteRequest) {
                         ((ResultLdapPromiseImpl<DeleteRequest, Result>) pendingRequest).setResultOrError(result);
@@ -242,12 +242,12 @@
                         // Treat this as a connection error.
                         final Result errorResult = newResult(result.getResultCode()).setDiagnosticMessage(
                                         result.getDiagnosticMessage());
-                        ldapConnection.connectionErrorOccurred(true, errorResult);
+                        ldapConnection.close(null, true, errorResult);
                     } else {
                         ldapConnection.handleUnsolicitedNotification(result);
                     }
                 } else {
-                    final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                    final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                     if (pendingRequest != null) {
                         if (pendingRequest.getRequest() instanceof ExtendedRequest) {
                             final ExtendedResultLdapPromiseImpl<?> extendedPromise =
@@ -274,7 +274,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
                 if (pendingRequest != null) {
                     pendingRequest.handleIntermediateResponse(response);
                 }
@@ -287,7 +287,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof ModifyDNRequest) {
                         ((ResultLdapPromiseImpl<ModifyDNRequest, Result>) pendingRequest).setResultOrError(result);
@@ -303,7 +303,7 @@
         public void modifyResult(final int messageID, final Result result) throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof ModifyRequest) {
                         ((ResultLdapPromiseImpl<ModifyRequest, Result>) pendingRequest).setResultOrError(result);
@@ -320,7 +320,7 @@
                 IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest.getRequest() instanceof SearchRequest) {
                         ((ResultLdapPromiseImpl<SearchRequest, Result>) pendingRequest).setResultOrError(result);
@@ -336,7 +336,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest instanceof SearchResultLdapPromiseImpl) {
                         ((SearchResultLdapPromiseImpl) pendingRequest).handleEntry(entry);
@@ -352,7 +352,7 @@
                 throws DecodeException, IOException {
             final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
             if (ldapConnection != null) {
-                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
+                final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
                 if (pendingRequest != null) {
                     if (pendingRequest instanceof SearchResultLdapPromiseImpl) {
                         ((SearchResultLdapPromiseImpl) pendingRequest).handleReference(reference);
@@ -367,14 +367,15 @@
         private <R extends ExtendedResult> void handleExtendedResult0(
                 final GrizzlyLDAPConnection conn, final ExtendedResultLdapPromiseImpl<R> promise,
                 final ExtendedResult result) throws DecodeException {
-            final R decodedResponse = promise.decodeResult(result, conn.getLdapOptions().getDecodeOptions());
+            final R decodedResponse = promise.decodeResult(result, conn.getLDAPOptions().getDecodeOptions());
 
             if (result.getResultCode() == ResultCode.SUCCESS
                     && promise.getRequest() instanceof StartTLSExtendedRequest) {
                 try {
                     final StartTLSExtendedRequest request = (StartTLSExtendedRequest) promise.getRequest();
                     conn.startTLS(request.getSSLContext(), request.getEnabledProtocols(),
-                            request.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
+                            request.getEnabledCipherSuites(),
+                            new EmptyCompletionHandler<SSLEngine>() {
                                 @Override
                                 public void completed(final SSLEngine result) {
                                     conn.setBindOrStartTLSInProgress(false);
@@ -383,19 +384,19 @@
 
                                 @Override
                                 public void failed(final Throwable throwable) {
-                                    final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(throwable)
-                                            .setDiagnosticMessage("SSL handshake failed");
+                                    final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR)
+                                            .setCause(throwable).setDiagnosticMessage("SSL handshake failed");
                                     conn.setBindOrStartTLSInProgress(false);
-                                    conn.connectionErrorOccurred(false, errorResult);
+                                    conn.close(null, false, errorResult);
                                     promise.adaptErrorResult(errorResult);
                                 }
                             });
                     return;
                 } catch (final IOException e) {
-                    final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(e).setDiagnosticMessage(
-                            e.getMessage());
+                    final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(e)
+                            .setDiagnosticMessage(e.getMessage());
                     promise.adaptErrorResult(errorResult);
-                    conn.connectionErrorOccurred(false, errorResult);
+                    conn.close(null, false, errorResult);
                     return;
                 }
             }
@@ -429,11 +430,16 @@
         }
         final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(connection);
 
-        // FIXME: what other sort of IOExceptions can be thrown?
-        // FIXME: Are they the best result codes?
-        Result errorResult = newResult(error instanceof EOFException ? ResultCode.CLIENT_SIDE_SERVER_DOWN
-                : ResultCode.CLIENT_SIDE_LOCAL_ERROR);
-        ldapConnection.connectionErrorOccurred(false, errorResult.setCause(error));
+        Result errorResult;
+        if (error instanceof EOFException) {
+            // FIXME: Is this the best result code?
+            errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN).setCause(error);
+        } else {
+            // FIXME: what other sort of IOExceptions can be thrown?
+            // FIXME: Is this the best result code?
+            errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setCause(error);
+        }
+        ldapConnection.close(null, false, errorResult);
     }
 
     @Override
@@ -441,7 +447,8 @@
         final Connection<?> connection = ctx.getConnection();
         final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.remove(connection);
         if (ldapConnection != null) {
-            ldapConnection.connectionErrorOccurred(false, newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN));
+            final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN);
+            ldapConnection.close(null, false, errorResult);
         }
         return ctx.getInvokeAction();
     }
@@ -452,7 +459,7 @@
         final Result errorResult =
                 Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e)
                         .setDiagnosticMessage(e.getMessage());
-        ldapConnection.connectionErrorOccurred(false, errorResult);
+        ldapConnection.close(null, false, errorResult);
     }
 
     /**
@@ -490,7 +497,8 @@
      * @param ldapConnection
      *            LDAP connection
      */
-    void registerConnection(final Connection<?> connection, final GrizzlyLDAPConnection ldapConnection) {
+    void registerConnection(final Connection<?> connection,
+            final GrizzlyLDAPConnection ldapConnection) {
         LDAP_CONNECTION_ATTR.set(connection, ldapConnection);
     }
 }
diff --git a/opendj-sdk/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties b/opendj-sdk/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
index 440500a..c0b3cbb 100755
--- a/opendj-sdk/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
+++ b/opendj-sdk/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
@@ -23,5 +23,13 @@
 #
 #      Copyright 2013-2014 ForgeRock AS.
 #
+LDAP_CONNECTION_REQUEST_TIMEOUT=The request has failed because no response \
+ was received from the server within the %d ms timeout
 LDAP_CONNECTION_CONNECT_TIMEOUT=The connection attempt to server %s has failed \
  because the connection timeout period of %d ms was exceeded
+LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT=The bind or StartTLS request \
+ has failed because no response was received from the server within the %d ms \
+ timeout. The LDAP connection is now in an invalid state and can no longer be used
+LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT=The LDAP connection has \
+ failed because no bind or StartTLS response was received from the server \
+ within the %d ms timeout
diff --git a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
index 5b268e8..f5351cd 100644
--- a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
+++ b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -45,15 +45,15 @@
 import org.forgerock.opendj.ldap.ConnectionPool;
 import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
+import org.forgerock.opendj.ldap.LdapPromise;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
 import org.forgerock.opendj.ldap.LDAPClientContext;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPListener;
 import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.LDAPServer;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
 import org.forgerock.opendj.ldap.MockConnectionEventListener;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.ResultHandler;
@@ -85,14 +85,10 @@
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-import static java.util.concurrent.TimeUnit.*;
-
 import static org.fest.assertions.Assertions.*;
 import static org.forgerock.opendj.ldap.Connections.*;
 import static org.forgerock.opendj.ldap.LdapException.*;
 import static org.forgerock.opendj.ldap.TestCaseUtils.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-import static org.forgerock.opendj.ldap.responses.Responses.*;
 import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
 import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
@@ -107,12 +103,6 @@
     private static final long TEST_TIMEOUT = 30L;
     private static final long TEST_TIMEOUT_MS = TEST_TIMEOUT * 1000L;
 
-    /** The heart-beat timeout after which a connection will be marked as failed. */
-    private static final long OPERATION_TIMEOUT_SEC = 3;
-
-    /** The time unit for the interval between keepalive pings. */
-    private static final long HEART_BEAT_INTERVAL_SEC = 10;
-
     /**
      * Ensures that the LDAP Server is running.
      *
@@ -155,30 +145,33 @@
             "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "(objectclass=*)", "cn");
 
         InetSocketAddress serverAddress = getServerSocketAddress();
-        final LDAPOptions commonsOptions = new LDAPOptions().setTimeout(OPERATION_TIMEOUT_SEC, SECONDS);
-        factories[0][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
-                            new LDAPOptions().setHeartBeatSearchRequest(request).setTimeout(500, MILLISECONDS)
-                            .setHeartBeatInterval(1000, MILLISECONDS));
+        factories[0][0] =
+                Connections.newHeartBeatConnectionFactory(new LDAPConnectionFactory(
+                        serverAddress.getHostName(), serverAddress.getPort()),
+                        1000, 500, TimeUnit.MILLISECONDS, request);
 
         // InternalConnectionFactory
-        factories[1][0] = newInternalConnectionFactory(LDAPServer.getInstance(), null);
+        factories[1][0] = Connections.newInternalConnectionFactory(LDAPServer.getInstance(), null);
 
         // AuthenticatedConnectionFactory
-        factories[2][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
-                        new LDAPOptions(commonsOptions).setBindRequest(newSimpleBindRequest("", new char[0])));
+        factories[2][0] =
+                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
+                                serverAddress.getHostName(), serverAddress.getPort()),
+                        Requests.newSimpleBindRequest("", new char[0]));
 
         // AuthenticatedConnectionFactory with multi-stage SASL
-        factories[3][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
-                        new LDAPOptions(commonsOptions).setBindRequest(
-                                newCRAMMD5SASLBindRequest("id:user", "password".toCharArray())));
+        factories[3][0] =
+                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
+                                serverAddress.getHostName(), serverAddress.getPort()),
+                        Requests.newCRAMMD5SASLBindRequest("id:user", "password".toCharArray()));
 
         // LDAPConnectionFactory with default options
-        factories[4][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort());
+        factories[4][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort());
 
         // LDAPConnectionFactory with startTLS
         SSLContext sslContext =
                 new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext();
-        LDAPOptions options = new LDAPOptions(commonsOptions).setSSLContext(sslContext).setUseStartTLS(true)
+        LDAPOptions options = new LDAPOptions().setSSLContext(sslContext).setUseStartTLS(true)
                         .addEnabledCipherSuite(
                                 new String[] { "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
                                     "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
@@ -186,29 +179,34 @@
                                     "SSL_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_WITH_RC4_128_MD5",
                                     "TLS_DH_anon_WITH_AES_128_CBC_SHA",
                                     "TLS_DH_anon_WITH_AES_256_CBC_SHA" });
-        factories[5][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(), options);
+        factories[5][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(), options);
 
         // startTLS + SASL confidentiality
         // Use IP address here so that DIGEST-MD5 host verification works if
         // local host name is not localhost (e.g. on some machines it might be
         // localhost.localdomain).
         // FIXME: enable QOP once OPENDJ-514 is fixed.
-        factories[6][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
-                options.setBindRequest(newDigestMD5SASLBindRequest("id:user", "password".toCharArray())
-                        .setCipher(DigestMD5SASLBindRequest.CIPHER_LOW)));
+        factories[6][0] =
+                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
+                        serverAddress.getHostName(), serverAddress.getPort(), options),
+                        Requests.newDigestMD5SASLBindRequest(
+                            "id:user", "password".toCharArray()).setCipher(
+                                DigestMD5SASLBindRequest.CIPHER_LOW));
 
         // Connection pool and load balancing tests.
         InetSocketAddress offlineSocketAddress1 = findFreeSocketAddress();
-        ConnectionFactory offlineServer1 = newNamedConnectionFactory(
-                        newLDAPConnectionFactory(offlineSocketAddress1.getHostName(),
+        ConnectionFactory offlineServer1 =
+                Connections.newNamedConnectionFactory(
+                        new LDAPConnectionFactory(offlineSocketAddress1.getHostName(),
                                 offlineSocketAddress1.getPort()), "offline1");
-
         InetSocketAddress offlineSocketAddress2 = findFreeSocketAddress();
-        ConnectionFactory offlineServer2 = newNamedConnectionFactory(
-                        newLDAPConnectionFactory(offlineSocketAddress2.getHostName(),
+        ConnectionFactory offlineServer2 =
+                Connections.newNamedConnectionFactory(
+                        new LDAPConnectionFactory(offlineSocketAddress2.getHostName(),
                                 offlineSocketAddress2.getPort()), "offline2");
-        ConnectionFactory onlineServer = newNamedConnectionFactory(
-                        newLDAPConnectionFactory(serverAddress.getHostName(),
+        ConnectionFactory onlineServer =
+                Connections.newNamedConnectionFactory(
+                        new LDAPConnectionFactory(serverAddress.getHostName(),
                                 serverAddress.getPort()), "online");
 
         // Connection pools.
@@ -329,7 +327,7 @@
         // Create a connection factory: this should always use the default
         // schema, even if it is updated.
         InetSocketAddress socketAddress = getServerSocketAddress();
-        final ConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
+        final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                 socketAddress.getPort());
         final Schema defaultSchema = Schema.getDefaultSchema();
 
@@ -522,13 +520,17 @@
     @Test(dataProvider = "closeNotifyConfig")
     public void testCloseNotify(final CloseNotify config) throws Exception {
         final CountDownLatch connectLatch = new CountDownLatch(1);
-        final AtomicReference<LDAPClientContext> contextHolder = new AtomicReference<LDAPClientContext>();
+        final AtomicReference<LDAPClientContext> contextHolder =
+                new AtomicReference<LDAPClientContext>();
 
-        final ServerConnectionFactory<LDAPClientContext, Integer> mockServer = mock(ServerConnectionFactory.class);
+        final ServerConnectionFactory<LDAPClientContext, Integer> mockServer =
+                mock(ServerConnectionFactory.class);
         when(mockServer.handleAccept(any(LDAPClientContext.class))).thenAnswer(
                 new Answer<ServerConnection<Integer>>() {
+
                     @Override
-                    public ServerConnection<Integer> answer(InvocationOnMock invocation) throws Throwable {
+                    public ServerConnection<Integer> answer(InvocationOnMock invocation)
+                            throws Throwable {
                         // Allow the context to be accessed from outside the mock.
                         contextHolder.set((LDAPClientContext) invocation.getArguments()[0]);
                         connectLatch.countDown(); /* is this needed? */
@@ -541,8 +543,10 @@
                                 @Override
                                 public Void answer(InvocationOnMock invocation) throws Throwable {
                                     ResultHandler<? super BindResult> resultHandler =
-                                            (ResultHandler<? super BindResult>) invocation.getArguments()[4];
-                                    resultHandler.handleResult(newBindResult(ResultCode.SUCCESS));
+                                            (ResultHandler<? super BindResult>) invocation
+                                                    .getArguments()[4];
+                                    resultHandler.handleResult(Responses
+                                            .newBindResult(ResultCode.SUCCESS));
                                     return null;
                                 }
                             }).when(mockConnection).handleBind(anyInt(), anyInt(),
@@ -553,19 +557,20 @@
                     }
                 });
 
-        LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mockServer);
+        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
         try {
-            LDAPConnectionFactory clientFactory = newLDAPConnectionFactory(listener.getHostName(), listener.getPort());
-            final Connection clientConnection = clientFactory.getConnection();
-            connectLatch.await(TEST_TIMEOUT, SECONDS);
+            LDAPConnectionFactory clientFactory =
+                    new LDAPConnectionFactory(listener.getHostName(), listener.getPort());
+            final Connection client = clientFactory.getConnection();
+            connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
             MockConnectionEventListener mockListener = null;
             try {
                 if (config.useEventListener) {
                     mockListener = new MockConnectionEventListener();
-                    clientConnection.addConnectionEventListener(mockListener);
+                    client.addConnectionEventListener(mockListener);
                 }
                 if (config.doBindFirst) {
-                    clientConnection.bind("cn=test", "password".toCharArray());
+                    client.bind("cn=test", "password".toCharArray());
                 }
                 if (!config.closeOnAccept) {
                     // Disconnect using client context.
@@ -582,29 +587,30 @@
                 // Block until remote close is signalled.
                 if (mockListener != null) {
                     // Block using listener.
-                    mockListener.awaitError(TEST_TIMEOUT, SECONDS);
+                    mockListener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
                     assertThat(mockListener.getInvocationCount()).isEqualTo(1);
-                    assertThat(mockListener.isDisconnectNotification()).isEqualTo(config.sendDisconnectNotification);
+                    assertThat(mockListener.isDisconnectNotification()).isEqualTo(
+                            config.sendDisconnectNotification);
                     assertThat(mockListener.getError()).isNotNull();
                 } else {
                     // Block by spinning on isValid.
                     waitForCondition(new Callable<Boolean>() {
                         @Override
                         public Boolean call() throws Exception {
-                            return !clientConnection.isValid();
+                            return !client.isValid();
                         }
                     });
                 }
-                assertThat(clientConnection.isValid()).isFalse();
-                assertThat(clientConnection.isClosed()).isFalse();
+                assertThat(client.isValid()).isFalse();
+                assertThat(client.isClosed()).isFalse();
             } finally {
-                clientConnection.close();
+                client.close();
             }
             // Check state after remote close and local close.
-            assertThat(clientConnection.isValid()).isFalse();
-            assertThat(clientConnection.isClosed()).isTrue();
+            assertThat(client.isValid()).isFalse();
+            assertThat(client.isClosed()).isTrue();
             if (mockListener != null) {
-                mockListener.awaitClose(TEST_TIMEOUT, SECONDS);
+                mockListener.awaitClose(TEST_TIMEOUT, TimeUnit.SECONDS);
                 assertThat(mockListener.getInvocationCount()).isEqualTo(2);
             }
         } finally {
@@ -634,9 +640,11 @@
                     }
                 });
 
-        LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mockServer);
+        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
         try {
-            LDAPConnectionFactory clientFactory = newLDAPConnectionFactory(listener.getHostName(), listener.getPort());
+            LDAPConnectionFactory clientFactory =
+                    new LDAPConnectionFactory(listener.getHostName(),
+                            listener.getPort());
             final Connection client = clientFactory.getConnection();
             connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
             try {
@@ -670,12 +678,11 @@
     @Test(description = "Test for OPENDJ-1121: Closing a connection after "
             + "closing the connection factory causes NPE")
     public void testFactoryCloseBeforeConnectionClose() throws Exception {
-        LDAPOptions heartBeatOptions = new LDAPOptions().setHeartBeatInterval(HEART_BEAT_INTERVAL_SEC, SECONDS)
-                .setTimeout(OPERATION_TIMEOUT_SEC, SECONDS);
         InetSocketAddress socketAddress = getServerSocketAddress();
-        final ConnectionFactory factory = newLoadBalancer(new FailoverLoadBalancingAlgorithm(
-                Arrays.asList(newFixedConnectionPool(newLDAPConnectionFactory(
-                        socketAddress.getHostName(), socketAddress.getPort(), heartBeatOptions), 2))));
+        final ConnectionFactory factory =
+                newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(newFixedConnectionPool(
+                        newHeartBeatConnectionFactory(new LDAPConnectionFactory(
+                                socketAddress.getHostName(), socketAddress.getPort())), 2))));
         Connection conn = null;
         try {
             conn = factory.getConnection();
diff --git a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
index fc8b88b..8886b23 100644
--- a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
+++ b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -37,13 +37,13 @@
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
 import org.forgerock.opendj.ldap.LDAPClientContext;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPListener;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
 import org.forgerock.opendj.ldap.MockConnectionEventListener;
 import org.forgerock.opendj.ldap.ProviderNotFoundException;
 import org.forgerock.opendj.ldap.ResultCode;
@@ -71,7 +71,6 @@
 
 import static org.fest.assertions.Assertions.*;
 import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.ldap.Connections.*;
 import static org.forgerock.opendj.ldap.TestCaseUtils.*;
 import static org.forgerock.opendj.ldap.requests.Requests.*;
 import static org.mockito.Matchers.*;
@@ -108,7 +107,7 @@
             new AtomicReference<LDAPClientContext>();
     private final LDAPListener server = createServer();
     private final InetSocketAddress socketAddress = server.getSocketAddress();
-    private final ConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
+    private final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
             socketAddress.getPort(), new LDAPOptions().setTimeout(1, TimeUnit.MILLISECONDS));
     private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10);
     private volatile ServerConnection<Integer> serverConnection;
@@ -123,7 +122,7 @@
     @Test(description = "OPENDJ-1197")
     public void testClientSideConnectTimeout() throws Exception {
         // Use an non-local unreachable network address.
-        final ConnectionFactory factory = newLDAPConnectionFactory("10.20.30.40", 1389,
+        final ConnectionFactory factory = new LDAPConnectionFactory("10.20.30.40", 1389,
                 new LDAPOptions().setConnectTimeout(1, TimeUnit.MILLISECONDS));
         try {
             for (int i = 0; i < ITERATIONS; i++) {
@@ -301,7 +300,7 @@
     public void testCreateLDAPConnectionFactory() throws Exception {
         // test no exception is thrown, which means transport provider is correctly loaded
         InetSocketAddress socketAddress = findFreeSocketAddress();
-        LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(), socketAddress.getPort());
+        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), socketAddress.getPort());
         factory.close();
     }
 
@@ -310,7 +309,7 @@
     public void testCreateLDAPConnectionFactoryFailureProviderNotFound() throws Exception {
         LDAPOptions options = new LDAPOptions().setTransportProvider("unknown");
         InetSocketAddress socketAddress = findFreeSocketAddress();
-        LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
+        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                 socketAddress.getPort(), options);
         factory.close();
     }
@@ -320,7 +319,7 @@
         // test no exception is thrown, which means transport provider is correctly loaded
         LDAPOptions options = new LDAPOptions().setProviderClassLoader(Thread.currentThread().getContextClassLoader());
         InetSocketAddress socketAddress = findFreeSocketAddress();
-        LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
+        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                 socketAddress.getPort(), options);
         factory.close();
     }
@@ -353,7 +352,7 @@
 
     private LDAPListener createServer() {
         try {
-            return newLDAPListener(findFreeSocketAddress(),
+            return new LDAPListener(findFreeSocketAddress(),
                     new ServerConnectionFactory<LDAPClientContext, Integer>() {
                         @Override
                         public ServerConnection<Integer> handleAccept(
diff --git a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
index ec61b86..9dab53b 100644
--- a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
+++ b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
@@ -26,13 +26,19 @@
 
 package org.forgerock.opendj.grizzly;
 
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
 import java.net.InetSocketAddress;
 import java.util.concurrent.TimeUnit;
 
+import org.forgerock.opendj.ldap.Connections;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPListener;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.RequestHandler;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SdkTestCase;
@@ -47,10 +53,6 @@
 import org.mockito.ArgumentCaptor;
 import org.testng.annotations.Test;
 
-import static org.fest.assertions.Assertions.*;
-import static org.forgerock.opendj.ldap.Connections.*;
-import static org.mockito.Mockito.*;
-
 /**
  * Tests LDAP connection implementation class.
  */
@@ -82,13 +84,15 @@
          * and leave the client waiting forever for a response.
          */
         @SuppressWarnings("unchecked")
-        LDAPListener listener = newLDAPListener(address, newServerConnectionFactory(mock(RequestHandler.class)));
+        LDAPListener listener =
+                new LDAPListener(address, Connections
+                        .newServerConnectionFactory(mock(RequestHandler.class)));
 
         /*
          * Use a very long time out in order to prevent the timeout thread from
          * triggering the timeout.
          */
-        LDAPConnectionFactory factory = newLDAPConnectionFactory(address.getHostName(),
+        LDAPConnectionFactory factory = new LDAPConnectionFactory(address.getHostName(),
                 address.getPort(), new LDAPOptions().setTimeout(100, TimeUnit.SECONDS));
         GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnection();
         try {
diff --git a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
index e70d05d..4bc9740 100644
--- a/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
+++ b/opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -74,7 +74,6 @@
 
 import static org.fest.assertions.Assertions.*;
 import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.ldap.Connections.*;
 import static org.forgerock.opendj.ldap.LdapException.*;
 import static org.forgerock.opendj.ldap.TestCaseUtils.*;
 import static org.mockito.Mockito.*;
@@ -231,7 +230,7 @@
     public void testCreateLDAPListener() throws Exception {
         // test no exception is thrown, which means transport provider is
         // correctly loaded
-        LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class));
+        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class));
         listener.close();
     }
 
@@ -245,7 +244,7 @@
         // correctly loaded
         LDAPListenerOptions options = new LDAPListenerOptions().
                 setProviderClassLoader(Thread.currentThread().getContextClassLoader());
-        LDAPListener listener = newLDAPListener(findFreeSocketAddress(),
+        LDAPListener listener = new LDAPListener(findFreeSocketAddress(),
                 mock(ServerConnectionFactory.class), options);
         listener.close();
     }
@@ -258,7 +257,7 @@
         expectedExceptionsMessageRegExp = "^The requested provider 'unknown' .*")
     public void testCreateLDAPListenerFailureProviderNotFound() throws Exception {
         LDAPListenerOptions options = new LDAPListenerOptions().setTransportProvider("unknown");
-        LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options);
+        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options);
         listener.close();
     }
 
@@ -273,11 +272,13 @@
         final MockServerConnection serverConnection = new MockServerConnection();
         final MockServerConnectionFactory serverConnectionFactory =
                 new MockServerConnectionFactory(serverConnection);
-        final LDAPListener listener = newLDAPListener(new InetSocketAddress(0), serverConnectionFactory);
+        final LDAPListener listener =
+                new LDAPListener(new InetSocketAddress(0), serverConnectionFactory);
         try {
             // Connect and close.
-            final Connection connection = newLDAPConnectionFactory(listener.getHostName(), listener.getPort())
-                    .getConnection();
+            final Connection connection =
+                    new LDAPConnectionFactory(listener.getHostName(),
+                            listener.getPort()).getConnection();
             assertThat(serverConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
             assertThat(serverConnection.isClosed.getCount()).isEqualTo(1);
             connection.close();
@@ -302,22 +303,22 @@
         final MockServerConnectionFactory onlineServerConnectionFactory =
                 new MockServerConnectionFactory(onlineServerConnection);
         final LDAPListener onlineServerListener =
-                newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
 
         try {
             // Connection pool and load balancing tests.
             InetSocketAddress offlineAddress1 = findFreeSocketAddress();
             final ConnectionFactory offlineServer1 =
-                    newNamedConnectionFactory(newLDAPConnectionFactory(
+                    Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
                             offlineAddress1.getHostName(),
                             offlineAddress1.getPort()), "offline1");
             InetSocketAddress offlineAddress2 = findFreeSocketAddress();
             final ConnectionFactory offlineServer2 =
-                    newNamedConnectionFactory(newLDAPConnectionFactory(
+                    Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
                             offlineAddress2.getHostName(),
                             offlineAddress2.getPort()), "offline2");
             final ConnectionFactory onlineServer =
-                    newNamedConnectionFactory(newLDAPConnectionFactory(
+                    Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
                             onlineServerListener.getHostName(),
                             onlineServerListener.getPort()), "online");
 
@@ -345,11 +346,13 @@
 
                     };
 
-            final LDAPListener proxyListener = newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+            final LDAPListener proxyListener =
+                    new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
             try {
                 // Connect and close.
-                final Connection connection = newLDAPConnectionFactory(
-                        proxyListener.getHostName(), proxyListener.getPort()).getConnection();
+                final Connection connection =
+                        new LDAPConnectionFactory(proxyListener.getHostName(),
+                                proxyListener.getPort()).getConnection();
 
                 assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
                 assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -380,18 +383,24 @@
         final MockServerConnectionFactory onlineServerConnectionFactory =
                 new MockServerConnectionFactory(onlineServerConnection);
         final LDAPListener onlineServerListener =
-                newLDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory);
+                new LDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory);
 
         try {
             // Connection pool and load balancing tests.
             InetSocketAddress offlineAddress1 = findFreeSocketAddress();
-            final ConnectionFactory offlineServer1 = newNamedConnectionFactory(newLDAPConnectionFactory(
-                    offlineAddress1.getHostName(), offlineAddress1.getPort()), "offline1");
+            final ConnectionFactory offlineServer1 =
+                    Connections.newNamedConnectionFactory(
+                            new LDAPConnectionFactory(offlineAddress1.getHostName(),
+                                    offlineAddress1.getPort()), "offline1");
             InetSocketAddress offlineAddress2 = findFreeSocketAddress();
-            final ConnectionFactory offlineServer2 = newNamedConnectionFactory(newLDAPConnectionFactory(
-                    offlineAddress2.getHostName(), offlineAddress2.getPort()), "offline2");
-            final ConnectionFactory onlineServer = newNamedConnectionFactory(newLDAPConnectionFactory(
-                    onlineServerListener.getHostName(), onlineServerListener.getPort()), "online");
+            final ConnectionFactory offlineServer2 =
+                    Connections.newNamedConnectionFactory(
+                            new LDAPConnectionFactory(offlineAddress2.getHostName(),
+                                    offlineAddress2.getPort()), "offline2");
+            final ConnectionFactory onlineServer =
+                    Connections.newNamedConnectionFactory(
+                            new LDAPConnectionFactory(onlineServerListener.getHostName(),
+                                    onlineServerListener.getPort()), "online");
 
             // Round robin.
             final ConnectionFactory loadBalancer =
@@ -427,11 +436,12 @@
                     new MockServerConnectionFactory(proxyServerConnection);
 
             final LDAPListener proxyListener =
-                    newLDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory);
+                    new LDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory);
             try {
                 // Connect, bind, and close.
                 final Connection connection =
-                        newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
+                        new LDAPConnectionFactory(proxyListener.getHostName(),
+                                proxyListener.getPort()).getConnection();
                 try {
                     connection.bind("cn=test", "password".toCharArray());
 
@@ -466,7 +476,7 @@
         final MockServerConnectionFactory onlineServerConnectionFactory =
                 new MockServerConnectionFactory(onlineServerConnection);
         final LDAPListener onlineServerListener =
-                newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
 
         try {
             final MockServerConnection proxyServerConnection = new MockServerConnection();
@@ -479,7 +489,8 @@
                             // First attempt offline server.
                             InetSocketAddress offlineAddress = findFreeSocketAddress();
                             LDAPConnectionFactory lcf =
-                                    newLDAPConnectionFactory(offlineAddress.getHostName(), offlineAddress.getPort());
+                                    new LDAPConnectionFactory(offlineAddress.getHostName(),
+                                            offlineAddress.getPort());
                             try {
                                 // This is expected to fail.
                                 lcf.getConnection().close();
@@ -488,7 +499,8 @@
                             } catch (final ConnectionException ce) {
                                 // This is expected - so go to online server.
                                 try {
-                                    lcf = newLDAPConnectionFactory(
+                                    lcf =
+                                            new LDAPConnectionFactory(
                                                     onlineServerListener.getHostName(),
                                                     onlineServerListener.getPort());
                                     lcf.getConnection().close();
@@ -511,11 +523,13 @@
                         }
 
                     };
-            final LDAPListener proxyListener = newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+            final LDAPListener proxyListener =
+                    new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
             try {
                 // Connect and close.
                 final Connection connection =
-                        newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
+                        new LDAPConnectionFactory(proxyListener.getHostName(),
+                                proxyListener.getPort()).getConnection();
 
                 assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
                 assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -545,7 +559,7 @@
         final MockServerConnectionFactory onlineServerConnectionFactory =
                 new MockServerConnectionFactory(onlineServerConnection);
         final LDAPListener onlineServerListener =
-                newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+                new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
 
         try {
             final MockServerConnection proxyServerConnection = new MockServerConnection() {
@@ -559,8 +573,8 @@
                         throws UnsupportedOperationException {
                     // First attempt offline server.
                     InetSocketAddress offlineAddress = findFreeSocketAddress();
-                    LDAPConnectionFactory lcf =
-                            newLDAPConnectionFactory(offlineAddress.getHostName(), offlineAddress.getPort());
+                    LDAPConnectionFactory lcf = new LDAPConnectionFactory(offlineAddress.getHostName(),
+                            offlineAddress.getPort());
                     try {
                         // This is expected to fail.
                         lcf.getConnection().close();
@@ -570,7 +584,7 @@
                     } catch (final ConnectionException ce) {
                         // This is expected - so go to online server.
                         try {
-                            lcf = newLDAPConnectionFactory(onlineServerListener.getHostName(),
+                            lcf = new LDAPConnectionFactory(onlineServerListener.getHostName(),
                                     onlineServerListener.getPort());
                             lcf.getConnection().close();
                             resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
@@ -592,11 +606,12 @@
             final MockServerConnectionFactory proxyServerConnectionFactory =
                     new MockServerConnectionFactory(proxyServerConnection);
             final LDAPListener proxyListener =
-                    newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+                    new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
             try {
                 // Connect, bind, and close.
                 final Connection connection =
-                        newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
+                        new LDAPConnectionFactory(proxyListener.getHostName(),
+                                proxyListener.getPort()).getConnection();
                 try {
                     connection.bind("cn=test", "password".toCharArray());
 
@@ -630,12 +645,12 @@
         final MockServerConnectionFactory factory =
                 new MockServerConnectionFactory(serverConnection);
         final LDAPListenerOptions options = new LDAPListenerOptions().setMaxRequestSize(2048);
-        final LDAPListener listener = newLDAPListener(findFreeSocketAddress(), factory, options);
+        final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options);
 
         Connection connection = null;
         try {
-            connection =
-                    newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
+            connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).
+                    getConnection();
 
             // Small request
             connection.bind("cn=test", "password".toCharArray());
@@ -687,12 +702,12 @@
         final MockServerConnection serverConnection = new MockServerConnection();
         final MockServerConnectionFactory factory =
                 new MockServerConnectionFactory(serverConnection);
-        final LDAPListener listener = newLDAPListener(findFreeSocketAddress(), factory);
+        final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory);
 
         final Connection connection;
         try {
             // Connect and bind.
-            connection = newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
+            connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
             try {
                 connection.bind("cn=test", "password".toCharArray());
             } catch (final LdapException e) {
@@ -707,7 +722,8 @@
         try {
             // Connect and bind.
             final Connection failedConnection =
-                    newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
+                    new LDAPConnectionFactory(listener.getHostName(),
+                            listener.getPort()).getConnection();
             failedConnection.close();
             connection.close();
             fail("Connection attempt to closed listener succeeded unexpectedly");
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
index 13d941e..ce72211 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
@@ -34,9 +34,9 @@
 import org.forgerock.opendj.ldap.DecodeException;
 import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.RootDSE;
@@ -83,8 +83,6 @@
 import org.forgerock.opendj.ldif.ConnectionEntryReader;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * This command-line client demonstrates use of LDAP controls. The client takes
  * as arguments the host and port for the directory server, and expects to find
@@ -110,10 +108,10 @@
             System.err.println("For example: localhost 1389");
             System.exit(1);
         }
-        final String hostName = args[0];
+        final String host = args[0];
         final int port = Integer.parseInt(args[1]);
 
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
         try {
             connection = factory.getConnection();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
index a0ace4e..6f36b3a 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
@@ -29,8 +29,8 @@
 import java.util.Collection;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.RootDSE;
 import org.forgerock.opendj.ldap.requests.PasswordModifyExtendedRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
@@ -39,8 +39,6 @@
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.WhoAmIExtendedResult;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * This command-line client demonstrates use of LDAP extended operations. The
  * client takes as arguments the host and port for the directory server, and
@@ -66,10 +64,10 @@
             System.err.println("For example: localhost 1389");
             System.exit(1);
         }
-        final String hostName = args[0];
+        final String host = args[0];
         final int port = Integer.parseInt(args[1]);
 
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
 
         try {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
index 49df6c2..215e805 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
@@ -30,8 +30,8 @@
 import java.io.IOException;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.controls.GenericControl;
@@ -42,8 +42,6 @@
 import org.forgerock.opendj.ldif.ConnectionEntryReader;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which searches Microsoft Active Directory
  * synchronously, using {@link GenericControl} to pass the <a
@@ -95,7 +93,8 @@
         final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
 
         // Connect and bind to the server.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory =
+                new LDAPConnectionFactory(hostName, port);
         Connection connection = null;
 
         try {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
index c45578b..a47400a 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
@@ -28,21 +28,19 @@
 import java.io.IOException;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * Demonstrates accessing server information about capabilities and schema.
  */
 public final class GetInfo {
     /** Connection information. */
-    private static String hostName;
+    private static String host;
     private static int port;
     /** The kind of server information to request (all, controls, extops). */
     private static String infoType;
@@ -64,7 +62,7 @@
      */
     private static void connect() {
         // --- JCite ---
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
 
         try {
@@ -87,7 +85,7 @@
                     attributeList); // Return these requested attributes.
 
             final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
-            writer.writeComment("Root DSE for LDAP server at " + hostName + ":" + port);
+            writer.writeComment("Root DSE for LDAP server at " + host + ":" + port);
             if (entry != null) {
                 writer.writeEntry(entry);
             }
@@ -124,7 +122,7 @@
             giveUp();
         }
 
-        hostName = args[0];
+        host = args[0];
         port = Integer.parseInt(args[1]);
         infoType = args[2];
         final String infoTypeLc = infoType.toLowerCase();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
index 8720559..35adca5 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
@@ -33,15 +33,13 @@
 import java.io.InputStream;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldif.ChangeRecord;
 import org.forgerock.opendj.ldif.ConnectionChangeRecordWriter;
 import org.forgerock.opendj.ldif.LDIFChangeRecordReader;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which applies update operations to a Directory
  * Server. The update operations will be read from an LDIF file, or stdin if no
@@ -91,7 +89,7 @@
         final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldif);
 
         // Connect and bind to the server.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
         Connection connection = null;
 
         try {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
index 04c3b0e..d723b84 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
@@ -34,8 +34,8 @@
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
@@ -45,8 +45,6 @@
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * This command-line client demonstrates parsing entry attribute values to
  * objects. The client takes as arguments the host and port for the directory
@@ -68,11 +66,11 @@
             System.err.println("For example: localhost 1389");
             System.exit(1);
         }
-        final String hostName = args[0];
+        final String host = args[0];
         final int port = Integer.parseInt(args[1]);
 
         // --- JCite ---
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
         try {
             connection = factory.getConnection();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
index 1b68bcd..6e7a79d 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
@@ -26,16 +26,11 @@
 
 package org.forgerock.opendj.examples;
 
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-
-import javax.net.ssl.SSLContext;
-
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
@@ -43,7 +38,9 @@
 import org.forgerock.opendj.ldap.requests.ModifyRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 
-import static org.forgerock.opendj.ldap.Connections.*;
+import javax.net.ssl.SSLContext;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
 
 /**
  * This command-line client demonstrates how to reset a user password in
@@ -87,7 +84,8 @@
 
         Connection connection = null;
         try {
-            final LDAPConnectionFactory factory = newLDAPConnectionFactory(host, port, getTrustAllOptions());
+            final LDAPConnectionFactory factory =
+                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
             connection = factory.getConnection();
             connection.bind(bindDN, bindPassword.toCharArray());
 
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
index 3d39544..f2be0b8 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
@@ -30,24 +30,20 @@
 import java.io.IOException;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.Connections;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LDAPClientContext;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPListener;
 import org.forgerock.opendj.ldap.LDAPListenerOptions;
-import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.RequestContext;
 import org.forgerock.opendj.ldap.RequestHandlerFactory;
 import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
 import org.forgerock.opendj.ldap.ServerConnectionFactory;
-import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An LDAP load balancing proxy which forwards requests to one or more remote
  * Directory Servers. This is implementation is very simple and is only intended
@@ -66,12 +62,6 @@
  * </pre>
  */
 public final class Proxy {
-    /** The timeout after which a connection will be marked as failed. */
-    private static final long TIMEOUT_SECONDS = 3;
-
-    /** The interval between keepalive pings. */
-    private static final long HEARTBEAT_INTERVAL_SECONDS = 10;
-
     /**
      * Main method.
      *
@@ -102,16 +92,15 @@
         for (int i = 4; i < args.length; i += 2) {
             final String remoteAddress = args[i];
             final int remotePort = Integer.parseInt(args[i + 1]);
-            final LDAPOptions commonOptions = new LDAPOptions().setTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
 
-            final BindRequest bindRequest = Requests.newSimpleBindRequest(proxyDN, proxyPassword.toCharArray());
-            final LDAPOptions options = new LDAPOptions(commonOptions).setBindRequest(bindRequest);
-            factories.add(newCachedConnectionPool(newLDAPConnectionFactory(remoteAddress, remotePort, options)));
-
-            final LDAPOptions heartBeatOptions =
-                    new LDAPOptions(commonOptions).setHeartBeatInterval(HEARTBEAT_INTERVAL_SECONDS, TimeUnit.SECONDS);
-            bindFactories.add(newCachedConnectionPool(
-                newLDAPConnectionFactory(remoteAddress, remotePort, heartBeatOptions)));
+            factories.add(Connections.newCachedConnectionPool(Connections
+                    .newAuthenticatedConnectionFactory(Connections
+                            .newHeartBeatConnectionFactory(new LDAPConnectionFactory(remoteAddress,
+                                    remotePort)), Requests.newSimpleBindRequest(proxyDN,
+                            proxyPassword.toCharArray()))));
+            bindFactories.add(Connections.newCachedConnectionPool(Connections
+                    .newHeartBeatConnectionFactory(new LDAPConnectionFactory(remoteAddress,
+                            remotePort))));
         }
         // --- JCite pools ---
 
@@ -147,7 +136,7 @@
         final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
         LDAPListener listener = null;
         try {
-            listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
+            listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
             System.out.println("Press any key to stop the server...");
             System.in.read();
         } catch (final IOException e) {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
index 18f4729..a3c29f9 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
@@ -30,16 +30,14 @@
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.MatchingRule;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldap.schema.Syntax;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which prints a summary of the schema on the
  * named server as well as any warnings encountered while parsing the schema.
@@ -70,7 +68,7 @@
 
         // --- JCite ---
         // Connect and bind to the server.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
         Connection connection = null;
 
         try {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
index c0c3f8b..5f78d81 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
@@ -31,7 +31,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
@@ -39,13 +38,13 @@
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
 import org.forgerock.opendj.ldap.LDAPClientContext;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPListener;
 import org.forgerock.opendj.ldap.LDAPListenerOptions;
-import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.Modification;
 import org.forgerock.opendj.ldap.RequestContext;
 import org.forgerock.opendj.ldap.RequestHandler;
@@ -71,8 +70,6 @@
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * This example is based on the {@link Proxy}. This example does no load
  * balancing, but instead rewrites attribute descriptions and DN suffixes in
@@ -110,10 +107,6 @@
  * and {@code proxyUserPassword} to {@code password}.
  */
 public final class RewriterProxy {
-
-    /** The timeout after which a connection will be marked as failed. */
-    private static final long TIMEOUT_SECONDS = 3;
-
     private static final class Rewriter implements RequestHandler<RequestContext> {
 
         /** This example hard codes the attribute... */
@@ -400,15 +393,15 @@
         final String proxyPassword = args[3];
         final String remoteAddress = args[4];
         final int remotePort = Integer.parseInt(args[5]);
-        final LDAPOptions factoryOptions = new LDAPOptions()
-            .setBindRequest(Requests.newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()))
-            .setTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
 
         // Create connection factories.
-        final ConnectionFactory factory = newCachedConnectionPool(
-            newLDAPConnectionFactory(remoteAddress, remotePort, factoryOptions));
-        final ConnectionFactory bindFactory = newCachedConnectionPool(
-            newLDAPConnectionFactory(remoteAddress, remotePort));
+        final ConnectionFactory factory =
+                Connections.newCachedConnectionPool(Connections.newAuthenticatedConnectionFactory(
+                        new LDAPConnectionFactory(remoteAddress, remotePort), Requests
+                                .newSimpleBindRequest(proxyDN, proxyPassword.toCharArray())));
+        final ConnectionFactory bindFactory =
+                Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
+                        remotePort));
 
         /*
          * Create a server connection adapter which will create a new proxy
@@ -431,7 +424,7 @@
         final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
         LDAPListener listener = null;
         try {
-            listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
+            listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
             System.out.println("Press any key to stop the server...");
             System.in.read();
         } catch (final IOException e) {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
index d2b6b66..50ae094 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
@@ -37,17 +37,15 @@
 import javax.net.ssl.SSLContext;
 
 import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.TrustManagers;
 import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which performs SASL PLAIN authentication to a
  * directory server over LDAP with StartTLS. This example takes the following
@@ -89,7 +87,8 @@
 
         // --- JCite ---
         try {
-            final LDAPConnectionFactory factory = newLDAPConnectionFactory(host, port, getTrustAllOptions());
+            final LDAPConnectionFactory factory =
+                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
             connection = factory.getConnection();
             PlainSASLBindRequest request =
                     Requests.newPlainSASLBindRequest(authcid, passwd.toCharArray())
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
index 7bbf21c..9a70944 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
@@ -31,8 +31,8 @@
 import java.util.Arrays;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
@@ -40,8 +40,6 @@
 import org.forgerock.opendj.ldif.ConnectionEntryReader;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which searches a Directory Server. This example
  * takes the following command line parameters:
@@ -94,7 +92,7 @@
         final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
 
         // Connect and bind to the server.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
         Connection connection = null;
 
         try {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
index eec45ef..d4007ef 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
@@ -31,7 +31,6 @@
 import java.util.concurrent.CountDownLatch;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LdapPromise;
@@ -176,7 +175,7 @@
 
         // --- Using Promises ---
         // Initiate the asynchronous connect, bind, and search.
-        final LDAPConnectionFactory factory = Connections.newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
 
         factory.getConnectionAsync()
                 .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
index 85b12cc..67db44b 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
@@ -31,13 +31,11 @@
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An interactive command-line client that performs a search and subsequent
  * simple bind. The client prompts for email address and for a password, and
@@ -64,7 +62,7 @@
             System.err.println("For example: localhost 1389 dc=example,dc=com");
             System.exit(1);
         }
-        String hostName = args[0];
+        String host = args[0];
         int port = Integer.parseInt(args[1]);
         String baseDN = args[2];
 
@@ -80,7 +78,7 @@
         char[] password = c.readPassword("Password: ");
 
         // Search using mail address, and then bind with the DN and password.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
         try {
             connection = factory.getConnection();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
index 43908cf..93dcddd 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -46,8 +46,6 @@
 import org.forgerock.opendj.ldap.TrustManagers;
 import org.forgerock.opendj.ldif.LDIFEntryReader;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An LDAP directory server which exposes data contained in an LDIF file. This
  * is implementation is very simple and is only intended as an example:
@@ -127,10 +125,10 @@
                             }
                         };
 
-                listener = newLDAPListener(localAddress, localPort, sslWrapper, options);
+                listener = new LDAPListener(localAddress, localPort, sslWrapper, options);
             } else {
                 // No SSL.
-                listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
+                listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
             }
             System.out.println("Press any key to stop the server...");
             System.in.read();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
index e5b1074..6c5d881 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
@@ -31,16 +31,14 @@
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entries;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.TreeMapEntry;
 import org.forgerock.opendj.ldap.requests.ModifyRequest;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * A command-line client that creates, updates, renames, and deletes a
  * short-lived entry in order to demonstrate LDAP write operations using the
@@ -70,7 +68,7 @@
             System.err.println("For example: localhost 1389");
             System.exit(1);
         }
-        String hostName = args[0];
+        String host = args[0];
         int port = Integer.parseInt(args[1]);
 
         // User credentials of a "Directory Administrators" group member.
@@ -97,7 +95,7 @@
 
         LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
 
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
         try {
             connection = factory.getConnection();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
index 91feb0f..60d9456 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
@@ -34,15 +34,13 @@
 import javax.net.ssl.TrustManager;
 
 import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.TrustManagers;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * An example client application which performs simple authentication to a
  * directory server. This example takes the following command line parameters:
@@ -90,7 +88,7 @@
      * Authenticate over LDAP.
      */
     private static void connect() {
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
 
         try {
@@ -169,7 +167,8 @@
         try {
 
             final LDAPConnectionFactory factory =
-                    newLDAPConnectionFactory(hostName, port, getTrustOptions(hostName, keystore, storepass));
+                    new LDAPConnectionFactory(host, port,
+                            getTrustOptions(host, keystore, storepass));
             connection = factory.getConnection();
             connection.bind(bindDN, bindPassword.toCharArray());
 
@@ -221,7 +220,8 @@
         Connection connection = null;
 
         try {
-            final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port, getTrustAllOptions());
+            final LDAPConnectionFactory factory =
+                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
             connection = factory.getConnection();
             connection.bind(bindDN, bindPassword.toCharArray());
             System.out.println("Authenticated as " + bindDN + ".");
@@ -256,7 +256,7 @@
         // trustAllConnect();
     }
 
-    private static String hostName;
+    private static String host;
     private static int port;
     private static String bindDN;
     private static String bindPassword;
@@ -276,7 +276,7 @@
             giveUp();
         }
 
-        hostName = args[0];
+        host = args[0];
         port = Integer.parseInt(args[1]);
         bindDN = args[2];
         bindPassword = args[3];
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
index 31cd6f4..dd4cfa1 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
@@ -29,8 +29,8 @@
 import java.util.Collection;
 
 import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.RootDSE;
@@ -40,8 +40,6 @@
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.responses.CompareResult;
 
-import static org.forgerock.opendj.ldap.Connections.*;
-
 /**
  * This command-line client demonstrates adding and removing a member from a
  * (potentially large) static group.
@@ -83,13 +81,13 @@
         if (args.length != 5) {
             printUsage();
         }
-        final String hostName = args[0];
+        final String host = args[0];
         final int port = Integer.parseInt(args[1]);
         final String groupDN = args[2];
         final String memberDN = args[3];
         final ModificationType modType = getModificationType(args[4]);
 
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
         Connection connection = null;
         try {
             connection = factory.getConnection();
diff --git a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
index ba096bc..2ccdf53 100644
--- a/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
+++ b/opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
@@ -27,16 +27,14 @@
 
 package org.forgerock.opendj.examples;
 
-import java.io.IOException;
-
 import org.forgerock.opendj.io.ASN1;
 import org.forgerock.opendj.io.ASN1Writer;
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchScope;
@@ -48,7 +46,7 @@
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldif.LDIFEntryWriter;
 
-import static org.forgerock.opendj.ldap.Connections.*;
+import java.io.IOException;
 
 /**
  * An example client application which uses
@@ -90,7 +88,8 @@
         final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
 
         // Connect and bind to the server.
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
+        final LDAPConnectionFactory factory =
+                new LDAPConnectionFactory(hostName, port);
         Connection connection = null;
 
         // Prepare the value for the GenericControl.
diff --git a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
index 8cc108d..09cf837 100644
--- a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
+++ b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
@@ -29,8 +29,7 @@
 import static com.forgerock.opendj.cli.ArgumentConstants.*;
 import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
 import static com.forgerock.opendj.cli.Utils.filterExitCode;
-import static com.forgerock.opendj.ldap.tools.Utils.*;
-
+import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
 import static org.forgerock.util.Utils.closeSilently;
 
 import java.io.FileInputStream;
@@ -57,7 +56,6 @@
 import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
 import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
 import org.forgerock.opendj.ldap.requests.AddRequest;
-import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.DeleteRequest;
 import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
 import org.forgerock.opendj.ldap.requests.ModifyRequest;
@@ -250,10 +248,10 @@
     int run(final String[] args) {
         // Create the command-line argument parser for use with this program.
         final LocalizableMessage toolDescription = INFO_LDAPMODIFY_TOOL_DESCRIPTION.get();
-        final ArgumentParser argParser = new ArgumentParser(LDAPModify.class.getName(), toolDescription, false);
+        final ArgumentParser argParser =
+                new ArgumentParser(LDAPModify.class.getName(), toolDescription, false);
         ConnectionFactoryProvider connectionFactoryProvider;
         ConnectionFactory connectionFactory;
-        BindRequest bindRequest;
 
         BooleanArgument continueOnError;
         // TODO: Remove this due to new LDIF reader api?
@@ -368,8 +366,7 @@
                 return 0;
             }
 
-            connectionFactory = connectionFactoryProvider.getConnectionFactory();
-            bindRequest = connectionFactoryProvider.getBindRequest();
+            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
         } catch (final ArgumentException ae) {
             final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
             errPrintln(message);
@@ -459,14 +456,13 @@
         if (!noop.isPresent()) {
             try {
                 connection = connectionFactory.getConnection();
-                if (bindRequest != null) {
-                    printPasswordPolicyResults(this, connection.bind(bindRequest));
-                }
             } catch (final LdapException ere) {
-                return printErrorMessage(this, ere);
+                return Utils.printErrorMessage(this, ere);
             }
         }
 
+        Utils.printPasswordPolicyResults(this, connection);
+
         writer = new LDIFEntryWriter(getOutputStream());
         final VisitorImpl visitor = new VisitorImpl();
         ChangeRecordReader reader = null;
diff --git a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
index 36ba5f2..dd1c16b 100644
--- a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
+++ b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
@@ -63,7 +63,6 @@
 import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
 import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl;
 import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl;
-import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.Result;
@@ -89,7 +88,6 @@
 import static com.forgerock.opendj.cli.ArgumentConstants.*;
 import static com.forgerock.opendj.cli.Utils.*;
 import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
-import static com.forgerock.opendj.ldap.tools.Utils.*;
 
 /**
  * A tool that can be used to issue Search requests to the Directory Server.
@@ -241,7 +239,6 @@
                         "[filter] [attributes ...]");
         ConnectionFactoryProvider connectionFactoryProvider;
         ConnectionFactory connectionFactory;
-        BindRequest bindRequest;
 
         BooleanArgument countEntries;
         BooleanArgument dontWrap;
@@ -443,8 +440,7 @@
                 return 0;
             }
 
-            connectionFactory = connectionFactoryProvider.getConnectionFactory();
-            bindRequest = connectionFactoryProvider.getBindRequest();
+            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
         } catch (final ArgumentException ae) {
             final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
             errPrintln(message);
@@ -829,13 +825,11 @@
         Connection connection;
         try {
             connection = connectionFactory.getConnection();
-            if (bindRequest != null) {
-                printPasswordPolicyResults(this, connection.bind(bindRequest));
-            }
         } catch (final LdapException ere) {
-            return printErrorMessage(this, ere);
+            return Utils.printErrorMessage(this, ere);
         }
 
+        Utils.printPasswordPolicyResults(this, connection);
 
         try {
             int filterIndex = 0;
diff --git a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
index 8b728d8..50e0795 100644
--- a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
+++ b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -53,6 +53,7 @@
 
 import com.forgerock.opendj.cli.ArgumentException;
 import com.forgerock.opendj.cli.ArgumentParser;
+import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
 import com.forgerock.opendj.cli.BooleanArgument;
 import com.forgerock.opendj.cli.ConsoleApplication;
 import com.forgerock.opendj.cli.IntegerArgument;
@@ -546,9 +547,10 @@
                     }
                 } else {
                     connection = this.connection;
-                    if (!noRebind && app.getBindRequest() != null) {
+                    if (!noRebind && connection instanceof AuthenticatedConnection) {
+                        final AuthenticatedConnection ac = (AuthenticatedConnection) connection;
                         try {
-                            connection.bindAsync(app.getBindRequest()).getOrThrow();
+                            ac.rebindAsync().getOrThrow();
                         } catch (final InterruptedException e) {
                             // Ignore and check stop requested
                             continue;
diff --git a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
index 7ac2692..2d7a493 100644
--- a/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
+++ b/opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
@@ -26,8 +26,13 @@
  */
 package com.forgerock.opendj.ldap.tools;
 
+import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
+import static com.forgerock.opendj.cli.Utils.readBytesFromFile;
+import static com.forgerock.opendj.cli.Utils.secondsToTimeString;
+
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DecodeException;
 import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.LdapException;
@@ -46,11 +51,9 @@
 
 import com.forgerock.opendj.cli.ConsoleApplication;
 import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
+import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
 import com.forgerock.opendj.util.StaticUtils;
 
-import static com.forgerock.opendj.cli.Utils.*;
-import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
-
 /**
  * This class provides utility functions for all the client side tools.
  */
@@ -193,77 +196,84 @@
         return ere.getResult().getResultCode().intValue();
     }
 
-    static void printPasswordPolicyResults(final ConsoleApplication app, BindResult result) {
-        try {
-            final AuthorizationIdentityResponseControl control =
-                    result.getControl(AuthorizationIdentityResponseControl.DECODER, new DecodeOptions());
-            if (control != null) {
-                final LocalizableMessage message = INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID());
-                app.println(message);
-            }
-        } catch (final DecodeException e) {
-            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
-        }
+    static void printPasswordPolicyResults(final ConsoleApplication app, final Connection connection) {
+        if (connection instanceof AuthenticatedConnection) {
+            final AuthenticatedConnection conn = (AuthenticatedConnection) connection;
+            final BindResult result = conn.getAuthenticatedBindResult();
 
-        try {
-            final PasswordExpiredResponseControl control =
-                    result.getControl(PasswordExpiredResponseControl.DECODER,
-                            new DecodeOptions());
-            if (control != null) {
-                final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
-                app.println(message);
+            try {
+                final AuthorizationIdentityResponseControl control =
+                        result.getControl(AuthorizationIdentityResponseControl.DECODER,
+                                new DecodeOptions());
+                if (control != null) {
+                    final LocalizableMessage message =
+                            INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID());
+                    app.println(message);
+                }
+            } catch (final DecodeException e) {
+                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
             }
-        } catch (final DecodeException e) {
-            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
-        }
 
-        try {
-            final PasswordExpiringResponseControl control =
-                    result.getControl(PasswordExpiringResponseControl.DECODER,
-                            new DecodeOptions());
-            if (control != null) {
-                final LocalizableMessage timeString =
-                        secondsToTimeString(control.getSecondsUntilExpiration());
-                final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
-                app.println(message);
-            }
-        } catch (final DecodeException e) {
-            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
-        }
-
-        try {
-            final PasswordPolicyResponseControl control =
-                    result.getControl(PasswordPolicyResponseControl.DECODER,
-                            new DecodeOptions());
-            if (control != null) {
-                final PasswordPolicyErrorType errorType = control.getErrorType();
-                if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
+            try {
+                final PasswordExpiredResponseControl control =
+                        result.getControl(PasswordExpiredResponseControl.DECODER,
+                                new DecodeOptions());
+                if (control != null) {
                     final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
                     app.println(message);
-                } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) {
-                    final LocalizableMessage message = INFO_BIND_ACCOUNT_LOCKED.get();
-                    app.println(message);
-                } else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET) {
-
-                    final LocalizableMessage message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
-                    app.println(message);
                 }
-
-                final PasswordPolicyWarningType warningType = control.getWarningType();
-                if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
-                    final LocalizableMessage timeString =
-                            secondsToTimeString(control.getWarningValue());
-                    final LocalizableMessage message =
-                            INFO_BIND_PASSWORD_EXPIRING.get(timeString);
-                    app.println(message);
-                } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) {
-                    final LocalizableMessage message =
-                            INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue());
-                    app.println(message);
-                }
+            } catch (final DecodeException e) {
+                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
             }
-        } catch (final DecodeException e) {
-            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+
+            try {
+                final PasswordExpiringResponseControl control =
+                        result.getControl(PasswordExpiringResponseControl.DECODER,
+                                new DecodeOptions());
+                if (control != null) {
+                    final LocalizableMessage timeString =
+                            secondsToTimeString(control.getSecondsUntilExpiration());
+                    final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+                    app.println(message);
+                }
+            } catch (final DecodeException e) {
+                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+            }
+
+            try {
+                final PasswordPolicyResponseControl control =
+                        result.getControl(PasswordPolicyResponseControl.DECODER,
+                                new DecodeOptions());
+                if (control != null) {
+                    final PasswordPolicyErrorType errorType = control.getErrorType();
+                    if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
+                        final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
+                        app.println(message);
+                    } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) {
+                        final LocalizableMessage message = INFO_BIND_ACCOUNT_LOCKED.get();
+                        app.println(message);
+                    } else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET) {
+
+                        final LocalizableMessage message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
+                        app.println(message);
+                    }
+
+                    final PasswordPolicyWarningType warningType = control.getWarningType();
+                    if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
+                        final LocalizableMessage timeString =
+                                secondsToTimeString(control.getWarningValue());
+                        final LocalizableMessage message =
+                                INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+                        app.println(message);
+                    } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) {
+                        final LocalizableMessage message =
+                                INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue());
+                        app.println(message);
+                    }
+                }
+            } catch (final DecodeException e) {
+                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+            }
         }
     }
 
diff --git a/opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 7e5e26a..a0a3d26 100644
--- a/opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -16,6 +16,11 @@
 
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
+import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
+import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
+
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
@@ -45,10 +50,11 @@
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.EntryNotFoundException;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
 import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPOptions;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
 import org.forgerock.opendj.ldap.RDN;
@@ -58,18 +64,12 @@
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.TimeoutResultException;
 import org.forgerock.opendj.ldap.TrustManagers;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.Schema;
 
-import static java.util.concurrent.TimeUnit.*;
-
-import static org.forgerock.opendj.ldap.Connections.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
-import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.*;
-import static org.forgerock.opendj.rest2ldap.Utils.*;
-
 /**
  * Provides core factory methods and builders for constructing LDAP resource
  * collections.
@@ -970,28 +970,31 @@
                 Math.max(configuration.get("connectionPoolSize").defaultTo(10).asInteger(), 1);
         final int heartBeatIntervalSeconds =
                 Math.max(configuration.get("heartBeatIntervalSeconds").defaultTo(30).asInteger(), 1);
-        final int heartBeatTimeoutMS =
-                Math.max(configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500).asInteger(), 100);
-        final LDAPOptions options =
-            new LDAPOptions().setHeartBeatInterval(heartBeatIntervalSeconds, SECONDS)
-                             .setTimeout(heartBeatTimeoutMS, MILLISECONDS);
+        final int heartBeatTimeoutMilliSeconds =
+                Math.max(configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500)
+                        .asInteger(), 100);
 
         // Parse authentication parameters.
+        final BindRequest bindRequest;
         if (configuration.isDefined("authentication")) {
             final JsonValue authn = configuration.get("authentication");
             if (authn.isDefined("simple")) {
                 final JsonValue simple = authn.get("simple");
-                options.setBindRequest(newSimpleBindRequest(simple.get("bindDN").required().asString(),
-                                simple.get("bindPassword").required().asString().toCharArray()));
+                bindRequest =
+                        Requests.newSimpleBindRequest(simple.get("bindDN").required().asString(),
+                                simple.get("bindPassword").required().asString().toCharArray());
             } else {
                 throw new IllegalArgumentException("Only simple authentication is supported");
             }
+        } else {
+            bindRequest = null;
         }
 
         // Parse SSL/StartTLS parameters.
         final ConnectionSecurity connectionSecurity =
                 configuration.get("connectionSecurity").defaultTo(ConnectionSecurity.NONE).asEnum(
                         ConnectionSecurity.class);
+        final LDAPOptions options = new LDAPOptions();
         if (connectionSecurity != ConnectionSecurity.NONE) {
             try {
                 // Configure SSL.
@@ -1035,18 +1038,24 @@
             throw new IllegalArgumentException("No primaryLDAPServers");
         }
         final ConnectionFactory primary =
-                parseLDAPServers(primaryLDAPServers, connectionPoolSize, heartBeatIntervalSeconds, options);
+                parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
+                        heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
 
         // Parse secondary data center(s).
         final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
-        ConnectionFactory secondary = null;
+        final ConnectionFactory secondary;
         if (secondaryLDAPServers.isList()) {
             if (secondaryLDAPServers.size() > 0) {
                 secondary =
-                        parseLDAPServers(secondaryLDAPServers, connectionPoolSize, heartBeatIntervalSeconds, options);
+                        parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
+                                heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
+            } else {
+                secondary = null;
             }
         } else if (!secondaryLDAPServers.isNull()) {
             throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
+        } else {
+            secondary = null;
         }
 
         // Create fail-over.
@@ -1085,22 +1094,32 @@
         }
     }
 
-    private static ConnectionFactory parseLDAPServers(final JsonValue config, final int connectionPoolSize,
-            final int heartBeatIntervalSeconds, final LDAPOptions options) {
+    private static ConnectionFactory parseLDAPServers(final JsonValue config,
+            final BindRequest bindRequest, final int connectionPoolSize,
+            final int heartBeatIntervalSeconds, final int heartBeatTimeoutMilliSeconds,
+            final LDAPOptions options) {
         final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
-
         for (final JsonValue server : config) {
             final String host = server.get("hostname").required().asString();
             final int port = server.get("port").required().asInteger();
-            ConnectionFactory factory = newLDAPConnectionFactory(host, port, options);
+            ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
+            factory =
+                    Connections.newHeartBeatConnectionFactory(factory,
+                            heartBeatIntervalSeconds * 1000, heartBeatTimeoutMilliSeconds,
+                            TimeUnit.MILLISECONDS);
+            if (bindRequest != null) {
+                factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
+            }
             if (connectionPoolSize > 1) {
-                factory = newCachedConnectionPool(factory, 0, connectionPoolSize, 60L, SECONDS);
+                factory =
+                        Connections.newCachedConnectionPool(factory, 0, connectionPoolSize, 60L,
+                                TimeUnit.SECONDS);
             }
             servers.add(factory);
         }
-
         if (servers.size() > 1) {
-            return newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers, heartBeatIntervalSeconds, SECONDS));
+            return Connections.newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers,
+                    heartBeatIntervalSeconds, TimeUnit.SECONDS));
         } else {
             return servers.get(0);
         }
diff --git a/opendj-sdk/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java b/opendj-sdk/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
index f02982f..d0cc6e2 100644
--- a/opendj-sdk/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
+++ b/opendj-sdk/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
@@ -32,6 +32,7 @@
 import org.forgerock.opendj.ldap.AuthorizationException;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.ConstraintViolationException;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.DecodeException;
@@ -40,7 +41,6 @@
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.EntryNotFoundException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
-import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.ModificationType;
@@ -74,14 +74,9 @@
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-import static java.util.concurrent.TimeUnit.*;
-
 import static org.fest.assertions.Assertions.*;
 import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.adapter.server2x.Adapters.*;
 import static org.forgerock.opendj.adapter.server2x.EmbeddedServerTestCaseUtils.*;
-import static org.forgerock.opendj.ldap.Connections.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
 
 /**
  * This class defines a set of tests for the Adapters.class.
@@ -90,13 +85,6 @@
 @Test
 public class AdaptersTestCase extends ForgeRockTestCase {
 
-    /** The timeout after which a connection will be marked as failed. */
-    private static final long TIMEOUT_SEC = 3;
-
-    private Integer getListenPort() {
-        return Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"));
-    }
-
     /**
      * Provides an anonymous connection factories.
      *
@@ -105,8 +93,9 @@
     @DataProvider
     public Object[][] anonymousConnectionFactories() {
         return new Object[][] {
-            { newLDAPConnectionFactory("localhost", getListenPort()) },
-            { newAnonymousConnectionFactory() } };
+            { new LDAPConnectionFactory("localhost",
+                    Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) },
+            { Adapters.newAnonymousConnectionFactory() } };
     }
 
     /**
@@ -117,13 +106,11 @@
     @DataProvider
     public Object[][] rootConnectionFactories() {
         return new Object[][] {
-            { newLDAPConnectionFactory(
-                    "localhost",
-                    getListenPort(),
-                    new LDAPOptions()
-                        .setBindRequest(newSimpleBindRequest("cn=directory manager", "password".toCharArray()))
-                        .setTimeout(TIMEOUT_SEC, SECONDS)) },
-            { newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
+            { Connections.newAuthenticatedConnectionFactory(
+                   new LDAPConnectionFactory("localhost",
+                           Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))),
+                   Requests.newSimpleBindRequest("cn=directory manager", "password".toCharArray())) },
+            { Adapters.newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
     }
 
     /**
@@ -227,7 +214,8 @@
     @Test
     public void testSimpleLDAPConnectionFactorySimpleBind() throws LdapException {
         final LDAPConnectionFactory factory =
-                newLDAPConnectionFactory("localhost", getListenPort());
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
         Connection connection = null;
         try {
             connection = factory.getConnection();
@@ -251,7 +239,8 @@
     @Test
     public void testLDAPSASLBind() throws NumberFormatException, GeneralSecurityException, LdapException {
         LDAPConnectionFactory factory =
-                newLDAPConnectionFactory("localhost", getListenPort());
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
 
         Connection connection = factory.getConnection();
         PlainSASLBindRequest request =
@@ -1004,7 +993,9 @@
         final DeleteRequest deleteRequest = Requests.newDeleteRequest("sn=babs,dc=example,dc=org");
 
         // LDAP Connection
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory("localhost", getListenPort());
+        final LDAPConnectionFactory factory =
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
         Connection connection = null;
         connection = factory.getConnection();
         connection.bind("cn=Directory Manager", "password".toCharArray());
diff --git a/opendj-sdk/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java b/opendj-sdk/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
index 7a9c597..87acf0d 100644
--- a/opendj-sdk/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
+++ b/opendj-sdk/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
@@ -32,6 +32,7 @@
 import org.forgerock.opendj.ldap.AuthorizationException;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.ConstraintViolationException;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.DecodeException;
@@ -40,7 +41,6 @@
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.EntryNotFoundException;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
-import org.forgerock.opendj.ldap.LDAPOptions;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.ModificationType;
@@ -74,14 +74,9 @@
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-import static java.util.concurrent.TimeUnit.*;
-
 import static org.fest.assertions.Assertions.*;
 import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.adapter.server3x.Adapters.*;
 import static org.forgerock.opendj.adapter.server3x.EmbeddedServerTestCaseUtils.*;
-import static org.forgerock.opendj.ldap.Connections.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
 
 /**
  * This class defines a set of tests for the Adapters.class.
@@ -90,13 +85,6 @@
 @Test
 public class AdaptersTestCase extends ForgeRockTestCase {
 
-    /** The timeout after which a connection will be marked as failed. */
-    private static final long TIMEOUT_SEC = 3;
-
-    private Integer getListenPort() {
-        return Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"));
-    }
-
     /**
      * Provides an anonymous connection factories.
      *
@@ -105,8 +93,9 @@
     @DataProvider
     public Object[][] anonymousConnectionFactories() {
         return new Object[][] {
-            { newLDAPConnectionFactory("localhost", getListenPort()) },
-            { newAnonymousConnectionFactory() } };
+            { new LDAPConnectionFactory("localhost",
+                    Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) },
+            { Adapters.newAnonymousConnectionFactory() } };
     }
 
     /**
@@ -117,13 +106,11 @@
     @DataProvider
     public Object[][] rootConnectionFactories() {
         return new Object[][] {
-            { newLDAPConnectionFactory(
-                    "localhost",
-                    getListenPort(),
-                    new LDAPOptions()
-                        .setBindRequest(newSimpleBindRequest("cn=directory manager", "password".toCharArray()))
-                        .setTimeout(TIMEOUT_SEC, SECONDS)) },
-            { newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
+            { Connections.newAuthenticatedConnectionFactory(
+                   new LDAPConnectionFactory("localhost",
+                           Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))),
+                   Requests.newSimpleBindRequest("cn=directory manager", "password".toCharArray())) },
+            { Adapters.newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
     }
 
     /**
@@ -227,7 +214,8 @@
     @Test
     public void testSimpleLDAPConnectionFactorySimpleBind() throws LdapException {
         final LDAPConnectionFactory factory =
-                newLDAPConnectionFactory("localhost", getListenPort());
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
         Connection connection = null;
         try {
             connection = factory.getConnection();
@@ -251,7 +239,8 @@
     @Test
     public void testLDAPSASLBind() throws NumberFormatException, GeneralSecurityException, LdapException {
         LDAPConnectionFactory factory =
-                newLDAPConnectionFactory("localhost", getListenPort());
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
 
         Connection connection = factory.getConnection();
         PlainSASLBindRequest request =
@@ -1004,7 +993,9 @@
         final DeleteRequest deleteRequest = Requests.newDeleteRequest("sn=babs,dc=example,dc=org");
 
         // LDAP Connection
-        final LDAPConnectionFactory factory = newLDAPConnectionFactory("localhost", getListenPort());
+        final LDAPConnectionFactory factory =
+                new LDAPConnectionFactory("localhost",
+                        Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
         Connection connection = null;
         connection = factory.getConnection();
         connection.bind("cn=Directory Manager", "password".toCharArray());

--
Gitblit v1.10.0