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

Matthew Swift
23.36.2015 eb78aabadc36e75ca680d12d80de7dcb3f6b6b20
opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
File was deleted
opendj-sdk/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
@@ -27,10 +27,13 @@
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.CliMessages.*;
import static com.forgerock.opendj.cli.Utils.getHostNameForLdapUrl;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.AUTHN_BIND_REQUEST;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.CONNECT_TIMEOUT;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
import java.io.File;
import java.io.FileInputStream;
@@ -65,6 +68,7 @@
import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.util.Options;
import org.forgerock.util.time.Duration;
/**
 * A connection factory designed for use with command line tools.
@@ -134,8 +138,6 @@
    /**  The basic connection factory. */
    private ConnectionFactory connFactory;
    /** The authenticated connection factory. */
    protected ConnectionFactory authenticatedConnFactory;
    /** The bind request to connect with. */
    private BindRequest bindRequest;
@@ -345,15 +347,34 @@
    }
    /**
     * Checks if any conflicting arguments are present, build the connection with selected arguments and returns the
     * connection factory. If the application is interactive, it will prompt the user for missing parameters.
     * Constructs a connection factory for pre-authenticated connections. Checks if any conflicting arguments are
     * present, build the connection with selected arguments and returns the connection factory. If the application is
     * interactive, it will prompt the user for missing parameters.
     *
     * @return The connection factory.
     * @throws ArgumentException
     *             If an error occurs during the parsing of the arguments.
     *             (conflicting arguments or if an error occurs during building SSL context).
     *         If an error occurs during the parsing of the arguments (conflicting arguments or if an error occurs
     *         during building SSL context).
     */
    public ConnectionFactory getConnectionFactory() throws ArgumentException {
    public ConnectionFactory getAuthenticatedConnectionFactory() throws ArgumentException {
        return getConnectionFactory(true);
    }
    /**
     * Constructs a connection factory for unauthenticated connections. Checks if any conflicting arguments are present,
     * build the connection with selected arguments and returns the connection factory. If the application is
     * interactive, it will prompt the user for missing parameters.
     *
     * @return The connection factory.
     * @throws ArgumentException
     *         If an error occurs during the parsing of the arguments (conflicting arguments or if an error occurs
     *         during building SSL context).
     */
    public ConnectionFactory getUnauthenticatedConnectionFactory() throws ArgumentException {
        return getConnectionFactory(false);
    }
    private ConnectionFactory getConnectionFactory(boolean usePreAuthentication) throws ArgumentException {
        if (connFactory == null) {
            port = portArg.isPresent() ? portArg.getIntValue() : 0;
@@ -415,13 +436,14 @@
            }
            Options options = Options.defaultOptions();
            if (sslContext != null) {
                options.set(SSL_CONTEXT, sslContext)
                    .set(USE_STARTTLS, useStartTLSArg.isPresent());
                    .set(SSL_USE_STARTTLS, useStartTLSArg.isPresent());
            }
            options.set(CONNECT_TIMEOUT_IN_MILLISECONDS,
                TimeUnit.MILLISECONDS.toMillis(getConnectTimeout()));
            options.set(CONNECT_TIMEOUT, new Duration((long) getConnectTimeout(), TimeUnit.MILLISECONDS));
            if (usePreAuthentication) {
                options.set(AUTHN_BIND_REQUEST, getBindRequest());
            }
            connFactory = new LDAPConnectionFactory(hostNameArg.getValue(), port, options);
        }
        return connFactory;
@@ -503,24 +525,6 @@
    }
    /**
     * Returns the authenticated connection factory.
     *
     * @return The authenticated connection factory.
     * @throws ArgumentException
     *             If an error occurs during parsing the arguments.
     */
    public ConnectionFactory getAuthenticatedConnectionFactory() throws ArgumentException {
        if (authenticatedConnFactory == null) {
            authenticatedConnFactory = getConnectionFactory();
            final BindRequest bindRequest = getBindRequest();
            if (bindRequest != null) {
                authenticatedConnFactory = new AuthenticatedConnectionFactory(authenticatedConnFactory, bindRequest);
            }
        }
        return authenticatedConnFactory;
    }
    /**
     * Returns {@code true} if we can read on the provided path and
     * {@code false} otherwise.
     *
opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
@@ -543,6 +543,35 @@
    <justification>OPENDJ-1802 ByteString.valueOf() => valueOfInt(), valueOfLong(), valueOfUtf8(), valueOfBytes(), valueOfObject()</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/Connections</className>
    <differenceType>7002</differenceType>
    <method>*newAuthenticatedConnectionFactory*</method>
    <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into
      LDAPConnectionFactory. Pre-authenticated connection support is now part of
      LDAPConnectionFactory and can be enabled by specifying the AUTHN_BIND_REQUEST option.</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/Connections</className>
    <differenceType>7002</differenceType>
    <method>*newHeartBeatConnectionFactory*</method>
    <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into
      LDAPConnectionFactory. Heart-beat support is now part of
      LDAPConnectionFactory and can be configured using the HEARTBEAT_* options.</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/AuthenticatedConnectionFactory*</className>
    <differenceType>8001</differenceType>
    <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into
      LDAPConnectionFactory. Pre-authenticated connection support is now part of
      LDAPConnectionFactory and can be enabled by specifying the AUTHN_BIND_REQUEST option.</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/requests/StartTLSExtendedRequest</className>
    <differenceType>7012</differenceType>
    <method>*addEnabled*(java.util.Collection)</method>
    <justification>OPENDJ-1607: added Collection based addEnabled* methods</justification>
  </difference>
  <difference>
    <className>org/forgerock/opendj/ldap/AttributeParser</className>
    <differenceType>7014</differenceType>
    <method>java.util.Set asSetOf(org.forgerock.opendj.ldap.Function, java.lang.Object[])</method>
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
File was deleted
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
@@ -25,12 +25,108 @@
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.getProvider;
import org.forgerock.opendj.ldap.spi.TransportProvider;
import org.forgerock.util.Option;
import org.forgerock.util.Options;
/**
 * Common options for LDAP clients and listeners.
 */
abstract class CommonLDAPOptions {
    /**
     * Specifies the class loader which will be used to load the
     * {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider}.
     * <p>
     * By default the default class loader will be used.
     * <p>
     * The transport provider is loaded using {@code java.util.ServiceLoader},
     * the JDK service-provider loading facility. The provider must be
     * accessible from the same class loader that was initially queried to
     * locate the configuration file; note that this is not necessarily the
     * class loader from which the file was actually loaded. This method allows
     * to provide a class loader to be used for loading the provider.
     *
     */
    public static final Option<ClassLoader> TRANSPORT_PROVIDER_CLASS_LOADER = Option.of(ClassLoader.class, null);
    /**
     * Specifies the name of the provider to use for transport.
     * <p>
     * Transport providers implement {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider}
     * interface.
     * <p>
     * The name should correspond to the name of an existing provider, as
     * returned by {@code TransportProvider#getName()} method.
     */
    public static final Option<String> TRANSPORT_PROVIDER = Option.of(String.class, null);
    /**
     * Specifies the transport provider to use. This option is internal and only intended for testing.
     */
    static final Option<TransportProvider> TRANSPORT_PROVIDER_INSTANCE = Option.of(TransportProvider.class, null);
    /**
     * Specifies the value of the {@link java.net.SocketOptions#TCP_NODELAY
     * TCP_NODELAY} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.tcpNoDelay} property.
     */
    public static final Option<Boolean> TCP_NO_DELAY = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.tcpNoDelay", true));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_REUSEADDR
     * SO_REUSEADDR} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.reuseAddress} property.
     *
     */
    public static final Option<Boolean> SO_REUSE_ADDRESS = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.reuseAddress", true));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_LINGER
     * SO_LINGER} socket option for new connections.
     * <p>
     * The default setting is {@code -1} (disabled) and may be configured using
     * the {@code org.forgerock.opendj.io.linger} property.
     *
     * @param linger
     *            The value of the {@link java.net.SocketOptions#SO_LINGER
     *            SO_LINGER} socket option for new connections, or -1 if linger
     *            should be disabled.
     * @return A reference to this set of options.
     */
    public static final Option<Integer> SO_LINGER_IN_SECONDS = Option.withDefault(
        getIntProperty("org.forgerock.opendj.io.linger", -1));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_KEEPALIVE
     * SO_KEEPALIVE} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.keepAlive} property.
     *
     */
    public static final Option<Boolean> SO_KEEPALIVE = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.keepAlive", true));
    /** Sets the decoding options which will be used to control how requests and responses are decoded. */
    public static final Option<DecodeOptions> LDAP_DECODE_OPTIONS = Option.withDefault(new DecodeOptions());
    static TransportProvider getTransportProvider(final Options options) {
        final TransportProvider transportProvider = options.get(TRANSPORT_PROVIDER_INSTANCE);
        if (transportProvider != null) {
            return transportProvider;
        }
        return getProvider(TransportProvider.class,
                           options.get(TRANSPORT_PROVIDER),
                           options.get(TRANSPORT_PROVIDER_CLASS_LOADER));
    }
    static boolean getBooleanProperty(final String name, final boolean defaultValue) {
        final String value = System.getProperty(name);
@@ -45,74 +141,4 @@
            return defaultValue;
        }
    }
    /** Sets the decoding options which will be used to control how requests and responses are decoded. */
    public static final Option<DecodeOptions> DECODE_OPTIONS = Option.withDefault(new DecodeOptions());
    /**
     * Specifies the class loader which will be used to load the {@link TransportProvider}.
     * <p>
     * By default the default class loader will be used.
     * <p>
     * The transport provider is loaded using {@code java.util.ServiceLoader},
     * the JDK service-provider loading facility. The provider must be
     * accessible from the same class loader that was initially queried to
     * locate the configuration file; note that this is not necessarily the
     * class loader from which the file was actually loaded. This method allows
     * to provide a class loader to be used for loading the provider.
     *
     */
    public static final Option<ClassLoader> PROVIDER_CLASS_LOADER = Option.of(ClassLoader.class, null);
    /**
     * Specifies the name of the provider to use for transport.
     * <p>
     * Transport providers implement {@link TransportProvider} interface.
     * <p>
     * The name should correspond to the name of an existing provider, as
     * returned by {@code TransportProvider#getName()} method.
     */
    public static final Option<String> TRANSPORT_PROVIDER = Option.of(String.class, null);
    /**
     * Specifies the value of the {@link java.net.SocketOptions#TCP_NODELAY
     * TCP_NODELAY} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.tcpNoDelay} property.
     */
    public static final Option<Boolean> TCP_NO_DELAY = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.tcpNoDelay", true));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_REUSEADDR
     * SO_REUSEADDR} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.reuseAddress} property.
     *
     */
    public static final Option<Boolean> REUSE_ADDRESS = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.reuseAddress", true));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_LINGER
     * SO_LINGER} socket option for new connections.
     * <p>
     * The default setting is {@code -1} (disabled) and may be configured using
     * the {@code org.forgerock.opendj.io.linger} property.
     *
     * @param linger
     *            The value of the {@link java.net.SocketOptions#SO_LINGER
     *            SO_LINGER} socket option for new connections, or -1 if linger
     *            should be disabled.
     * @return A reference to this set of options.
     */
    public static final Option<Integer> LINGER = Option.withDefault(
        getIntProperty("org.forgerock.opendj.io.linger", -1));
    /**
     * Specifies the value of the {@link java.net.SocketOptions#SO_KEEPALIVE
     * SO_KEEPALIVE} socket option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.keepAlive} property.
     *
     */
    public static final Option<Boolean> KEEPALIVE = Option.withDefault(
        getBooleanProperty("org.forgerock.opendj.io.keepAlive", true));
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
@@ -34,8 +34,7 @@
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.Options;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
@@ -45,35 +44,6 @@
 */
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
     * using the provided connection factory, but will reuse previously
     * allocated connections when they are available.
@@ -243,113 +213,6 @@
    }
    /**
     * 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>
@@ -646,7 +509,7 @@
            public void close(org.forgerock.opendj.ldap.requests.UnbindRequest request,
                    String reason) {
                // Do nothing.
            };
            }
        };
    }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
File was deleted
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -27,142 +27,494 @@
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.getProvider;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_CONNECTION_CLOSED_BY_CLIENT;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_FAILED;
import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_TIMEOUT;
import static com.forgerock.opendj.ldap.CoreMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newStartTLSExtendedRequest;
import static org.forgerock.opendj.ldap.requests.Requests.unmodifiableSearchRequest;
import static org.forgerock.opendj.ldap.responses.Responses.newBindResult;
import static org.forgerock.opendj.ldap.responses.Responses.newGenericExtendedResult;
import static org.forgerock.opendj.ldap.responses.Responses.newResult;
import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.newLdapPromiseImpl;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newFailedLdapPromise;
import static org.forgerock.util.Utils.closeSilently;
import static org.forgerock.util.promise.Promises.newResultPromise;
import java.util.Collections;
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.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import javax.net.ssl.SSLContext;
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.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.LDAPConnectionFactoryImpl;
import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl;
import org.forgerock.opendj.ldap.spi.LdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.TransportProvider;
import org.forgerock.util.AsyncFunction;
import org.forgerock.util.Function;
import org.forgerock.util.Option;
import org.forgerock.util.Options;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.ExceptionHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.ResultHandler;
import org.forgerock.util.time.Duration;
import org.forgerock.util.time.TimeService;
import javax.net.ssl.SSLContext;
import java.util.Collections;
import java.util.List;
import com.forgerock.opendj.util.ReferenceCountedObject;
/**
 * A factory class which can be used to obtain connections to an LDAP Directory
 * Server.
 * A factory class which can be used to obtain connections to an LDAP Directory Server. A connection attempt comprises
 * of the following steps:
 * <ul>
 * <li>first of all a TCP connection to the remote LDAP server is obtained. The attempt will fail if a connection is
 *     not obtained within the configured {@link #CONNECT_TIMEOUT connect timeout}
 * <li>if LDAPS (not StartTLS) is requested then an SSL handshake is performed. LDAPS is enabled by specifying the
 *     {@link #SSL_CONTEXT} option along with {@link #SSL_USE_STARTTLS} set to {@code false}
 * <li>if StartTLS is requested then a StartTLS request is sent and then an SSL handshake performed once the response
 *     has been received. StartTLS is enabled by specifying the {@link #SSL_CONTEXT} option along with
 *     {@link #SSL_USE_STARTTLS} set to {@code true}
 * <li>an initial authentication request is sent if the {@link #AUTHN_BIND_REQUEST} option is specified
 * <li>if heart-beat support is enabled via the {@link #HEARTBEAT_ENABLED} option, and none of steps 2-4 were performed,
 *     then an initial heart-beat is sent in order to determine whether the directory service is available.
 * <li>the connect attempt will fail if it does not complete within the configured connection timeout. If the SSL
 *     handshake, StartTLS request, initial bind request, or initial heart-beat fail for any reason then the connection
 *     attempt will be deemed to have failed and an appropriate error returned.
 * </ul>
 * Once a connection has been established heart-beats will be sent periodically 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.
 */
public final class LDAPConnectionFactory extends CommonLDAPOptions implements ConnectionFactory {
    /**
     * Configures the connection factory to return pre-authenticated connections using the specified {@link
     * BindRequest}. The connections returned by the connection factory will 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.
     */
    public static final Option<BindRequest> AUTHN_BIND_REQUEST = Option.of(BindRequest.class, null);
    /**
     * Specifies the connect timeout spcified. If a connection is not established within the timeout period (incl. SSL
     * negotiation, initial bind request, and/or heart-beat), then a {@link TimeoutResultException} error result will be
     * returned.
     * <p>
     * The default operation timeout is 10 seconds and may be configured using the {@code
     * org.forgerock.opendj.io.connectTimeout} property. A timeout setting of 0 causes the OS connect timeout to be
     * used.
     */
    public static final Option<Duration> CONNECT_TIMEOUT =
            Option.withDefault(new Duration((long) getIntProperty("org.forgerock.opendj.io.connectTimeout", 10000),
                                            TimeUnit.MILLISECONDS));
    /**
     * Configures the connection factory to periodically send "heart-beat" or "keep-alive" requests to the Directory
     * Server. This feature allows client applications to proactively detect network problems or unresponsive
     * servers. In addition, frequent heartbeat requests may also prevent load-balancers or Directory Servers from
     * closing otherwise idle connections.
     * <p>
     * Before returning new connections to the application the factory will first send an initial heart-beat request in
     * order to determine that the remote server is responsive. If the heart-beat request fails or is too slow to
     * respond 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 request), the connection
     * factory will periodically send heart-beat requests on the connection based on the configured heart-beat interval.
     * If the Directory Server is too slow to respond to the heart-beat 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-beat requests 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. The LDAP
     * connection 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.
     */
    public static final Option<Boolean> HEARTBEAT_ENABLED = Option.withDefault(false);
    /**
     * Specifies the time between successive heart-beat requests (default interval is 10 seconds). Heart-beats will only
     * be sent if {@link #HEARTBEAT_ENABLED} is set to {@code true}.
     *
     * @see #HEARTBEAT_ENABLED
     */
    public static final Option<Duration> HEARTBEAT_INTERVAL = Option.withDefault(new Duration(10L, SECONDS));
    /**
     * Specifies the scheduler which will be used for periodically sending heart-beat requests. A system-wide scheduler
     * will be used by default. Heart-beats will only be sent if {@link #HEARTBEAT_ENABLED} is set to {@code true}.
     *
     * @see #HEARTBEAT_ENABLED
     */
    public static final Option<ScheduledExecutorService> HEARTBEAT_SCHEDULER =
            Option.of(ScheduledExecutorService.class, null);
    /**
     * Specifies the timeout for heart-beat requests, after which the remote Directory Server will be deemed to be
     * unavailable (default timeout is 3 seconds). Heart-beats will only be sent if {@link #HEARTBEAT_ENABLED} is set to
     * {@code true}. If a {@link #REQUEST_TIMEOUT request timeout} is also set then the lower of the two will be used
     * for sending heart-beats.
     *
     * @see #HEARTBEAT_ENABLED
     */
    public static final Option<Duration> HEARTBEAT_TIMEOUT = Option.withDefault(new Duration(3L, SECONDS));
    /**
     * Specifies the operation timeout. If a response is not received from the Directory Server within the timeout
     * period, then the operation will be abandoned and a {@link TimeoutResultException} error result returned. A
     * timeout setting of 0 disables operation timeout limits.
     * <p>
     * The default operation timeout is 0 (no timeout) and may be configured using the {@code
     * org.forgerock.opendj.io.requestTimeout} property or the deprecated {@code org.forgerock.opendj.io.timeout}
     * property.
     */
    public static final Option<Duration> REQUEST_TIMEOUT =
            Option.withDefault(new Duration((long) getIntProperty("org.forgerock.opendj.io.requestTimeout",
                                                                  getIntProperty("org.forgerock.opendj.io.timeout", 0)),
                                            TimeUnit.MILLISECONDS));
    /**
     * Specifies the SSL context which will be used when initiating connections with the Directory Server.
     * <p>
     * By default no SSL context will be used, indicating that connections will not be secured.
     * If an SSL context is set then connections will be secured using either SSL or StartTLS
     * depending on {@link #USE_STARTTLS}.
     * By default no SSL context will be used, indicating that connections will not be secured. If an SSL context is set
     * then connections will be secured using either SSL or StartTLS depending on {@link #SSL_USE_STARTTLS}.
     */
    public static final Option<SSLContext> SSL_CONTEXT = Option.of(SSLContext.class, null);
    /**
     * Specifies the cipher suites enabled for secure connections with the Directory Server.
     * <p>
     * The suites must be supported by the SSLContext specified by option {@link #SSL_CONTEXT}. Only the suites listed
     * in the parameter are enabled for use.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static final Option<List<String>> SSL_ENABLED_CIPHER_SUITES =
            (Option) Option.of(List.class, Collections.<String>emptyList());
    /**
     * Specifies the protocol versions enabled for secure connections with the Directory Server.
     * <p>
     * The protocols must be supported by the SSLContext specified by option {@link #SSL_CONTEXT}. Only the protocols
     * listed in the parameter are enabled for use.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static final Option<List<String>> SSL_ENABLED_PROTOCOLS =
            (Option) Option.of(List.class, Collections.<String>emptyList());
    /**
     * Specifies whether SSL or StartTLS should be used for securing connections when an SSL context is specified.
     * <p>
     * By default SSL will be used in preference to StartTLS.
     */
    public static final Option<Boolean> USE_STARTTLS = Option.withDefault(false);
    /**
     * Specifies the operation timeout in milliseconds. If a response is not
     * received from the Directory Server within the timeout period, then the
     * operation will be abandoned and a {@link TimeoutResultException} error
     * result returned. A timeout setting of 0 disables operation timeout limits.
     * <p>
     * The default operation timeout is 0 (no timeout) and may be configured
     * using the {@code org.forgerock.opendj.io.timeout} property.
     */
    public static final Option<Long> TIMEOUT_IN_MILLISECONDS = Option.of(Long.class,
        (long) getIntProperty("org.forgerock.opendj.io.timeout", 0));
    /**
     * Specifies the connect timeout spcified in milliseconds. If a connection is not established
     * within the timeout period, then a {@link TimeoutResultException} error result will be returned.
     * <p>
     * The default operation timeout is 10 seconds and may be configured using
     * the {@code org.forgerock.opendj.io.connectTimeout} property.
     * A timeout setting of 0 causes the OS connect timeout to be used.
     */
    public static final Option<Long> CONNECT_TIMEOUT_IN_MILLISECONDS = Option.of(Long.class,
        (long) getIntProperty("org.forgerock.opendj.io.connectTimeout", 10000));
    /**
     * Specifies the cipher suites enabled for secure connections with the Directory Server.
     * <p>
     * The suites must be supported by the SSLContext specified by option {@link SSL_CONTEXT}.
     * Only the suites listed in the parameter are enabled for use.
     */
    public static final Option<List<String>> ENABLED_CIPHER_SUITES = Option.withDefault(
        Collections.<String>emptyList());
    /**
     * Specifies the protocol versions enabled for secure connections with the
     * Directory Server.
     * <p>
     * The protocols must be supported by the SSLContext specified by option {@link SSL_CONTEXT}.
     * Only the protocols listed in the parameter are enabled for use.
     */
    public static final Option<List<String>> ENABLED_PROTOCOLS =
        Option.withDefault(Collections.<String>emptyList());
    public static final Option<Boolean> SSL_USE_STARTTLS = Option.withDefault(false);
    /** Default heart-beat which will target the root DSE but not return any results. */
    private static final SearchRequest DEFAULT_HEARTBEAT =
            unmodifiableSearchRequest(newSearchRequest("", SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1"));
    /**
     * We implement the factory using the pimpl idiom in order to avoid making
     * too many implementation classes public.
     * Specifies the parameters of the search request that will be used for heart-beats. The default heart-beat search
     * request is a base object search against the root DSE requesting no attributes. Heart-beats will only be sent if
     * {@link #HEARTBEAT_ENABLED} is set to {@code true}.
     *
     * @see #HEARTBEAT_ENABLED
     */
    public static final Option<SearchRequest> HEARTBEAT_SEARCH_REQUEST =
            Option.of(SearchRequest.class, DEFAULT_HEARTBEAT);
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    /** The overall timeout to use when establishing connections, including SSL, bind, and heart-beat. */
    private final long connectTimeoutMS;
    /**
     * The minimum amount of time the connection should remain idle (no responses) before starting to send heartbeats.
     */
    private final long heartBeatDelayMS;
    /** Indicates whether heartbeats should be performed. */
    private final Boolean heartBeatEnabled;
    /** The heartbeat search request. */
    private final SearchRequest heartBeatRequest;
    /**
     * 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 heartBeatTimeoutMS;
    /** The interval between successive heartbeats. */
    private final long heartBeatintervalMS;
    /** The factory responsible for handling the low-level network communication with the Directory Server. */
    private final LDAPConnectionFactoryImpl impl;
    /**
     * Transport provider that provides the implementation of this factory.
     */
    /** The optional bind request which will be used as the initial heartbeat if specified. */
    private final BindRequest initialBindRequest;
    /** Flag which indicates whether this factory has been closed. */
    private final AtomicBoolean isClosed = new AtomicBoolean();
    /** A copy of the original options. This is only useful for debugging. */
    private final Options options;
    /** Transport provider that provides the implementation of this factory. */
    private final TransportProvider provider;
    /**
     * 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.
     * 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;
    /** Non-null if SSL or StartTLS should be used when creating new connections. */
    private final SSLContext sslContext;
    /** The list of permitted SSL ciphers for SSL negotiation. */
    private final List<String> sslEnabledCipherSuites;
    /** The list of permitted SSL protocols for SSL negotiation. */
    private final List<String> sslEnabledProtocols;
    /** Indicates whether a StartTLS request should be sent immediately after connecting. */
    private final boolean sslUseStartTLS;
    /** List of valid connections to which heartbeats will be sent. */
    private final List<ConnectionImpl> validConnections = new LinkedList<>();
    /** This is package private in order to allow unit tests to inject fake time stamps. */
    TimeService timeService = TimeService.SYSTEM;
    /** 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, heartBeatTimeoutMS, TimeUnit.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 ConnectionImpl connection : getValidConnections()) {
                connection.checkForHeartBeat();
            }
        }
    };
    /** The heartbeat scheduled future - which may be null if heartbeats are not being sent (no valid connections). */
    private ScheduledFuture<?> heartBeatFuture;
    /**
     * 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.
     * @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.
     * @param host
     *         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, Options.defaultOptions());
    }
    /**
     * 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.
     * 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      If {@code host} or {@code options} was {@code null}.
     * @throws ProviderNotFoundException if no provider is available or if the
     *                                   provider requested using options is not found.
     * @param host
     *         The host name.
     * @param port
     *         The port number.
     * @param options
     *         The LDAP options to use when creating connections.
     * @throws NullPointerException
     *         If {@code host} or {@code options} 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, final Options options) {
        Reject.ifNull(host, options);
        this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER),
                options.get(PROVIDER_CLASS_LOADER));
        this.connectTimeoutMS = options.get(CONNECT_TIMEOUT).to(TimeUnit.MILLISECONDS);
        Reject.ifTrue(connectTimeoutMS < 0, "connect timeout must be >= 0");
        Reject.ifTrue(options.get(REQUEST_TIMEOUT).getValue() < 0, "request timeout must be >= 0");
        this.heartBeatEnabled = options.get(HEARTBEAT_ENABLED);
        this.heartBeatintervalMS = options.get(HEARTBEAT_INTERVAL).to(TimeUnit.MILLISECONDS);
        this.heartBeatTimeoutMS = options.get(HEARTBEAT_TIMEOUT).to(TimeUnit.MILLISECONDS);
        this.heartBeatDelayMS = heartBeatintervalMS / 2;
        this.heartBeatRequest = options.get(HEARTBEAT_SEARCH_REQUEST);
        if (heartBeatEnabled) {
            Reject.ifTrue(heartBeatintervalMS <= 0, "heart-beat interval must be positive");
            Reject.ifTrue(heartBeatTimeoutMS <= 0, "heart-beat timeout must be positive");
        }
        this.provider = getTransportProvider(options);
        this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(options.get(HEARTBEAT_SCHEDULER));
        this.impl = provider.getLDAPConnectionFactory(host, port, options);
        this.initialBindRequest = options.get(AUTHN_BIND_REQUEST);
        this.sslContext = options.get(SSL_CONTEXT);
        this.sslUseStartTLS = options.get(SSL_USE_STARTTLS);
        this.sslEnabledProtocols = options.get(SSL_ENABLED_PROTOCOLS);
        this.sslEnabledCipherSuites = options.get(SSL_ENABLED_CIPHER_SUITES);
        this.options = Options.copyOf(options);
    }
    @Override
    public void close() {
        impl.close();
    }
    @Override
    public Promise<Connection, LdapException> getConnectionAsync() {
        return impl.getConnectionAsync();
        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();
            impl.close();
        }
    }
    @Override
    public Connection getConnection() throws LdapException {
        return impl.getConnection();
        return getConnectionAsync().getOrThrowUninterruptibly();
    }
    @Override
    public Promise<Connection, LdapException> getConnectionAsync() {
        acquireScheduler(); // Protect scheduler.
        // Register the connect timeout timer.
        final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
        final AtomicReference<LDAPConnectionImpl> connectionHolder = new AtomicReference<>();
        if (connectTimeoutMS > 0) {
            scheduler.get().schedule(new Runnable() {
                @Override
                public void run() {
                    if (promise.tryHandleException(newConnectTimeoutError())) {
                        closeSilently(connectionHolder.get());
                        releaseScheduler();
                    }
                }
            }, connectTimeoutMS, TimeUnit.MILLISECONDS);
        }
        // Now connect, negotiate SSL, etc.
        impl.getConnectionAsync()
            // Save the connection.
            .then(new Function<LDAPConnectionImpl, LDAPConnectionImpl, LdapException>() {
                @Override
                public LDAPConnectionImpl apply(final LDAPConnectionImpl connection) throws LdapException {
                    connectionHolder.set(connection);
                    return connection;
                }
            })
            .thenAsync(performStartTLSIfNeeded())
            .thenAsync(performSSLHandShakeIfNeeded(connectionHolder))
            .thenAsync(performInitialBindIfNeeded(connectionHolder))
            .thenAsync(performInitialHeartBeatIfNeeded(connectionHolder))
            .thenOnResult(new ResultHandler<Result>() {
                @Override
                public void handleResult(Result result) {
                    final LDAPConnectionImpl connection = connectionHolder.get();
                    final ConnectionImpl connectionImpl = new ConnectionImpl(connection);
                    if (!promise.tryHandleResult(registerConnection(connectionImpl))) {
                        connectionImpl.close();
                    }
                }
            })
            .thenOnException(new ExceptionHandler<LdapException>() {
                @Override
                public void handleException(final LdapException e) {
                    final LdapException connectException;
                    if (e instanceof ConnectionException || e instanceof AuthenticationException) {
                        connectException = e;
                    } else if (e instanceof TimeoutResultException) {
                        connectException = newHeartBeatTimeoutError();
                    } else {
                        connectException = newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
                                                            HBCF_HEARTBEAT_FAILED.get(),
                                                            e);
                    }
                    if (promise.tryHandleException(connectException)) {
                        closeSilently(connectionHolder.get());
                        releaseScheduler();
                    }
                }
            });
        return promise;
    }
    /**
     * Returns the host name of the Directory Server. The returned host name is
     * the same host name that was provided during construction and may be an IP
     * address. More specifically, this method will not perform a reverse DNS
     * Returns the host name of the Directory Server. The returned host name is the same host name that was provided
     * during construction and may be an IP address. More specifically, this method will not perform a reverse DNS
     * lookup.
     *
     * @return The host name of the Directory Server.
@@ -181,8 +533,7 @@
    }
    /**
     * Returns the name of the transport provider, which provides the implementation
     * of this factory.
     * Returns the name of the transport provider, which provides the implementation of this factory.
     *
     * @return The name of actual transport provider.
     */
@@ -192,6 +543,683 @@
    @Override
    public String toString() {
        return impl.toString();
        return "LDAPConnectionFactory(provider=`" + getProviderName() + ", host='" + getHostName() + "', port="
                + getPort() + ", options=" + options + ")";
    }
    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 on closed factory");
        }
    }
    private ConnectionImpl[] getValidConnections() {
        synchronized (validConnections) {
            return validConnections.toArray(new ConnectionImpl[validConnections.size()]);
        }
    }
    private LdapException newConnectTimeoutError() {
        final LocalizableMessage msg = LDAP_CONNECTION_CONNECT_TIMEOUT.get(impl.getSocketAddress(), connectTimeoutMS);
        return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, msg.toString());
    }
    private LdapException newHeartBeatTimeoutError() {
        return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_TIMEOUT.get(heartBeatTimeoutMS));
    }
    private AsyncFunction<Void, BindResult, LdapException> performInitialBindIfNeeded(
            final AtomicReference<LDAPConnectionImpl> connectionHolder) {
        return new AsyncFunction<Void, BindResult, LdapException>() {
            @Override
            public Promise<BindResult, LdapException> apply(final Void ignored) throws LdapException {
                if (initialBindRequest != null) {
                    return connectionHolder.get().bindAsync(initialBindRequest, null);
                } else {
                    return newResultPromise(newBindResult(ResultCode.SUCCESS));
                }
            }
        };
    }
    private AsyncFunction<BindResult, Result, LdapException> performInitialHeartBeatIfNeeded(
            final AtomicReference<LDAPConnectionImpl> connectionHolder) {
        return new AsyncFunction<BindResult, Result, LdapException>() {
            @Override
            public Promise<Result, LdapException> apply(final BindResult ignored) throws LdapException {
                // Only send an initial heartbeat if we haven't already interacted with the server.
                if (heartBeatEnabled && sslContext == null && initialBindRequest == null) {
                    return connectionHolder.get().searchAsync(heartBeatRequest, null, null);
                } else {
                    return newResultPromise(newResult(ResultCode.SUCCESS));
                }
            }
        };
    }
    private AsyncFunction<ExtendedResult, Void, LdapException> performSSLHandShakeIfNeeded(
            final AtomicReference<LDAPConnectionImpl> connectionHolder) {
        return new AsyncFunction<ExtendedResult, Void, LdapException>() {
            @Override
            public Promise<Void, LdapException> apply(final ExtendedResult extendedResult) throws LdapException {
                if (sslContext != null && !sslUseStartTLS) {
                    return connectionHolder.get().enableTLS(sslContext, sslEnabledProtocols, sslEnabledCipherSuites);
                } else {
                    return newResultPromise(null);
                }
            }
        };
    }
    private AsyncFunction<LDAPConnectionImpl, ExtendedResult, LdapException> performStartTLSIfNeeded() {
        return new AsyncFunction<LDAPConnectionImpl, ExtendedResult, LdapException>() {
            @Override
            public Promise<ExtendedResult, LdapException> apply(final LDAPConnectionImpl connection)
                    throws LdapException {
                if (sslContext != null && sslUseStartTLS) {
                    final StartTLSExtendedRequest startTLS = newStartTLSExtendedRequest(sslContext)
                            .addEnabledCipherSuite(sslEnabledCipherSuites)
                            .addEnabledProtocol(sslEnabledProtocols);
                    return connection.extendedRequestAsync(startTLS, null);
                } else {
                    return newResultPromise((ExtendedResult) newGenericExtendedResult(ResultCode.SUCCESS));
                }
            }
        };
    }
    private Connection registerConnection(final ConnectionImpl heartBeatConnection) {
        synchronized (validConnections) {
            if (heartBeatEnabled && validConnections.isEmpty()) {
                // This is the first active connection, so start the heart beat.
                heartBeatFuture = scheduler.get()
                                           .scheduleWithFixedDelay(sendHeartBeatRunnable,
                                                                   0,
                                                                   heartBeatintervalMS,
                                                                   TimeUnit.MILLISECONDS);
            }
            validConnections.add(heartBeatConnection);
        }
        return heartBeatConnection;
    }
    private void releaseScheduler() {
        if (referenceCount.decrementAndGet() == 0) {
            scheduler.release();
        }
    }
    /**
     * 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 {
        /** 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
        /** Keep compiler quiet. */
        private static final long serialVersionUID = -3590428415442668336L;
        boolean isHeld() {
            return getState() != 0;
        }
        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 */);
        }
        @Override
        protected boolean isHeldExclusively() {
            return getState() == LOCKED_EXCLUSIVELY;
        }
        @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;
                }
            }
        }
    }
    /** A connection that sends heart beats and supports all operations. */
    private final class ConnectionImpl extends AbstractAsynchronousConnection implements ConnectionEventListener {
        /** The wrapped connection. */
        private final LDAPConnectionImpl connectionImpl;
        /** List of pending Bind or StartTLS requests which must be invoked once the current heart beat completes. */
        private final Queue<Runnable> pendingBindOrStartTLSRequests = new ConcurrentLinkedQueue<>();
        /**
         * List of pending responses for all active operations. These will be signaled if no heart beat is detected
         * within the permitted timeout period.
         */
        private final Queue<LdapResultHandler<?>> pendingResults = new ConcurrentLinkedQueue<>();
        /** 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();
        private ConnectionImpl(final LDAPConnectionImpl connectionImpl) {
            this.connectionImpl = connectionImpl;
            connectionImpl.addConnectionEventListener(this);
        }
        @Override
        public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
            return connectionImpl.abandonAsync(request);
        }
        @Override
        public LdapPromise<Result> addAsync(
                final AddRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            return timestampPromise(connectionImpl.addAsync(request, intermediateResponseHandler));
        }
        @Override
        public void addConnectionEventListener(final ConnectionEventListener listener) {
            state.addConnectionEventListener(listener);
        }
        @Override
        public LdapPromise<BindResult> bindAsync(
                final BindRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            if (sync.tryLockShared()) {
                // Fast path
                return timestampBindOrStartTLSPromise(connectionImpl.bindAsync(request, intermediateResponseHandler));
            }
            return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() {
                @Override
                public Promise<BindResult, LdapException> apply(Void value) throws LdapException {
                    return timestampBindOrStartTLSPromise(connectionImpl.bindAsync(request,
                                                                                   intermediateResponseHandler));
                }
            });
        }
        @Override
        public void close() {
            handleConnectionClosed();
            connectionImpl.close();
        }
        @Override
        public void close(final UnbindRequest request, final String reason) {
            handleConnectionClosed();
            connectionImpl.close(request, reason);
        }
        @Override
        public LdapPromise<CompareResult> compareAsync(
                final CompareRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            return timestampPromise(connectionImpl.compareAsync(request, intermediateResponseHandler));
        }
        @Override
        public LdapPromise<Result> deleteAsync(
                final DeleteRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            return timestampPromise(connectionImpl.deleteAsync(request, intermediateResponseHandler));
        }
        @Override
        public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(
                final ExtendedRequest<R> request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            if (!isStartTLSRequest(request)) {
                return timestampPromise(connectionImpl.extendedRequestAsync(request, intermediateResponseHandler));
            }
            if (sync.tryLockShared()) {
                // Fast path
                return timestampBindOrStartTLSPromise(
                        connectionImpl.extendedRequestAsync(request, intermediateResponseHandler));
            }
            return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() {
                @Override
                public Promise<R, LdapException> apply(Void value) throws LdapException {
                    return timestampBindOrStartTLSPromise(
                            connectionImpl.extendedRequestAsync(request, intermediateResponseHandler));
                }
            });
        }
        @Override
        public void handleConnectionClosed() {
            if (state.notifyConnectionClosed()) {
                failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED,
                                                    HBCF_CONNECTION_CLOSED_BY_CLIENT.get()));
                synchronized (validConnections) {
                    connectionImpl.removeConnectionEventListener(this);
                    validConnections.remove(this);
                    if (heartBeatEnabled && 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() && connectionImpl.isValid();
        }
        @Override
        public LdapPromise<Result> modifyAsync(
                final ModifyRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            return timestampPromise(connectionImpl.modifyAsync(request, intermediateResponseHandler));
        }
        @Override
        public LdapPromise<Result> modifyDNAsync(
                final ModifyDNRequest request, final IntermediateResponseHandler intermediateResponseHandler) {
            if (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            return timestampPromise(connectionImpl.modifyDNAsync(request, intermediateResponseHandler));
        }
        @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 (hasConnectionErrorOccurred()) {
                return newConnectionErrorPromise();
            }
            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(connectionImpl.searchAsync(request, intermediateResponseHandler, entryHandler)
                                                  .thenOnResultOrException(new Runnable() {
                                                      @Override
                                                      public void run() {
                                                          searchDone.getAndSet(true);
                                                      }
                                                  }));
        }
        @Override
        public String toString() {
            return connectionImpl.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 - heartBeatTimeoutMS)) {
                    logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", connectionImpl));
                    handleConnectionError(false, newHeartBeatTimeoutError());
                }
            }
        }
        private boolean hasConnectionErrorOccurred() {
            return state.getConnectionError() != null;
        }
        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(null);
                    }
                }
            });
            flushPendingBindOrStartTLSRequests();
            return result;
        }
        private void failPendingResults(final LdapException error) {
            // Peek instead of pool because notification is responsible for removing the element from the queue.
            LdapResultHandler<?> pendingResult;
            while ((pendingResult = pendingResults.peek()) != null) {
                pendingResult.handleException(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 + heartBeatDelayMS)) {
                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 {
                    connectionImpl.searchAsync(heartBeatRequest, 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;
                        }
                    }).thenOnResult(new org.forgerock.util.promise.ResultHandler<Result>() {
                        @Override
                        public void handleResult(Result result) {
                            timestamp(result);
                            releaseHeartBeatLock();
                        }
                    }).thenOnException(new ExceptionHandler<LdapException>() {
                        @Override
                        public void handleException(LdapException exception) {
                            /*
                             * 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 (!(exception 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'",
                                                                    LDAPConnectionFactory.this,
                                                                    exception));
                                timestamp(exception);
                            }
                            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> timestampBindOrStartTLSPromise(LdapPromise<R> wrappedPromise) {
            return timestampPromise(wrappedPromise).thenOnResultOrException(new Runnable() {
                @Override
                public void run() {
                    releaseBindOrStartTLSLock();
                }
            });
        }
        private <R extends Result> LdapPromise<R> timestampPromise(LdapPromise<R> wrappedPromise) {
            final LdapPromiseImpl<R> outerPromise = new LdapPromiseImplWrapper<>(wrappedPromise);
            pendingResults.add(outerPromise);
            wrappedPromise.thenOnResult(new ResultHandler<R>() {
                @Override
                public void handleResult(R result) {
                    outerPromise.handleResult(result);
                    timestamp(result);
                }
            }).thenOnException(new ExceptionHandler<LdapException>() {
                @Override
                public void handleException(LdapException exception) {
                    outerPromise.handleException(exception);
                    timestamp(exception);
                }
            });
            outerPromise.thenOnResultOrException(new Runnable() {
                @Override
                public void run() {
                    pendingResults.remove(outerPromise);
                }
            });
            if (hasConnectionErrorOccurred()) {
                outerPromise.handleException(state.getConnectionError());
            }
            return outerPromise;
        }
        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());
            }
        }
    }
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
@@ -100,13 +100,14 @@
     * Specifies the maximum queue length for incoming connections requests. If a
     * connection request arrives when the queue is full, the connection is refused.
     */
    public static final Option<Integer> BACKLOG = Option.withDefault(50);
    public static final Option<Integer> CONNECT_MAX_BACKLOG = Option.withDefault(50);
    /**
     * Specifies the maximum request size in bytes for incoming LDAP requests.
     * If an incoming request exceeds the limit then the connection will be aborted by the listener.
     * Default value is 5MiB.
     */
    public static final Option<Integer> MAX_REQUEST_SIZE_BYTES = Option.withDefault(5 * 1024 * 1024);
    public static final Option<Integer> REQUEST_MAX_SIZE_IN_BYTES = Option.withDefault(5 * 1024 * 1024);
    /**
     * We implement the factory using the pimpl idiom in order have
@@ -160,10 +161,8 @@
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        Reject.ifNull(factory, options);
        final InetSocketAddress address = new InetSocketAddress(port);
        this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER),
            options.get(PROVIDER_CLASS_LOADER));
        this.impl = provider.getLDAPListener(address, factory, options);
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(new InetSocketAddress(port), factory, options);
    }
    /**
@@ -208,8 +207,7 @@
            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
            final Options options) throws IOException {
        Reject.ifNull(address, factory, options);
        this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER),
            options.get(PROVIDER_CLASS_LOADER));
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(address, factory, options);
    }
@@ -260,8 +258,7 @@
            final Options options) throws IOException {
        Reject.ifNull(host, factory, options);
        final InetSocketAddress address = new InetSocketAddress(host, port);
        this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER),
            options.get(PROVIDER_CLASS_LOADER));
        this.provider = getTransportProvider(options);
        this.impl = provider.getLDAPListener(address, factory, options);
    }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/StartTLSExtendedRequest.java
@@ -22,11 +22,12 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 *      Portions copyright 2012-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.requests;
import java.util.Collection;
import java.util.List;
import javax.net.ssl.SSLContext;
@@ -100,6 +101,22 @@
    StartTLSExtendedRequest addEnabledCipherSuite(String... suites);
    /**
     * Adds the cipher suites enabled for secure connections with the Directory
     * Server. The suites must be supported by the SSLContext specified in
     * {@link #setSSLContext(SSLContext)}. Following a successful call to this
     * method, only the suites listed in the protocols parameter are enabled for
     * use.
     *
     * @param suites
     *            Names of all the suites to enable.
     * @return A reference to this LDAP connection options.
     * @throws UnsupportedOperationException
     *             If this start TLS extended request does not permit the
     *             enabled cipher suites to be set.
     */
    StartTLSExtendedRequest addEnabledCipherSuite(Collection<String> suites);
    /**
     * Adds the protocol versions enabled for secure connections with the
     * Directory Server. The protocols must be supported by the SSLContext
     * specified in {@link #setSSLContext(SSLContext)}. Following a successful
@@ -115,6 +132,22 @@
     */
    StartTLSExtendedRequest addEnabledProtocol(String... protocols);
    /**
     * Adds the protocol versions enabled for secure connections with the
     * Directory Server. The protocols must be supported by the SSLContext
     * specified in {@link #setSSLContext(SSLContext)}. Following a successful
     * call to this method, only the protocols listed in the protocols parameter
     * are enabled for use.
     *
     * @param protocols
     *            Names of all the protocols to enable.
     * @return A reference to this LDAP connection options.
     * @throws UnsupportedOperationException
     *             If this start TLS extended request does not permit the
     *             enabled protocols to be set.
     */
    StartTLSExtendedRequest addEnabledProtocol(Collection<String> protocols);
    @Override
    <C extends Control> C getControl(ControlDecoder<C> decoder, DecodeOptions options)
            throws DecodeException;
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/StartTLSExtendedRequestImpl.java
@@ -27,6 +27,8 @@
package org.forgerock.opendj.ldap.requests;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -113,6 +115,11 @@
    @Override
    public StartTLSExtendedRequest addEnabledCipherSuite(final String... suites) {
        return addEnabledCipherSuite(Arrays.asList(suites));
    }
    @Override
    public StartTLSExtendedRequest addEnabledCipherSuite(final Collection<String> suites) {
        for (final String suite : suites) {
            this.enabledCipherSuites.add(Reject.checkNotNull(suite));
        }
@@ -121,6 +128,11 @@
    @Override
    public StartTLSExtendedRequest addEnabledProtocol(final String... protocols) {
        return addEnabledProtocol(Arrays.asList(protocols));
    }
    @Override
    public StartTLSExtendedRequest addEnabledProtocol(final Collection<String> protocols) {
        for (final String protocol : protocols) {
            this.enabledProtocols.add(Reject.checkNotNull(protocol));
        }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableStartTLSExtendedRequestImpl.java
@@ -22,10 +22,12 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.requests;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -44,11 +46,21 @@
    }
    @Override
    public StartTLSExtendedRequest addEnabledCipherSuite(final Collection<String> suites) {
        throw new UnsupportedOperationException();
    }
    @Override
    public StartTLSExtendedRequest addEnabledCipherSuite(final String... suites) {
        throw new UnsupportedOperationException();
    }
    @Override
    public StartTLSExtendedRequest addEnabledProtocol(final Collection<String> protocols) {
        throw new UnsupportedOperationException();
    }
    @Override
    public StartTLSExtendedRequest addEnabledProtocol(final String... protocols) {
        throw new UnsupportedOperationException();
    }
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/BindResultLdapPromiseImpl.java
@@ -22,12 +22,11 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2014 ForgeRock AS.
 *      Portions copyright 2011-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
@@ -43,28 +42,16 @@
public final class BindResultLdapPromiseImpl extends ResultLdapPromiseImpl<BindRequest, BindResult> {
    private final BindClient bindClient;
    BindResultLdapPromiseImpl(final int requestID, final BindRequest request, final BindClient bindClient,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(new PromiseImpl<BindResult, LdapException>() {
            protected LdapException tryCancel(boolean mayInterruptIfRunning) {
                /*
                 * No other operations can be performed while a bind is active.
                 * Therefore it is not possible to cancel bind or requests,
                 * since doing so will leave the connection in a state which
                 * prevents other operations from being performed.
                 */
                return null;
            }
        }, requestID, request, intermediateResponseHandler, connection);
    BindResultLdapPromiseImpl(
            final PromiseImpl<BindResult, LdapException> impl,
            final int requestID,
            final BindRequest request,
            final BindClient bindClient,
            final IntermediateResponseHandler intermediateResponseHandler) {
        super(impl, requestID, request, intermediateResponseHandler);
        this.bindClient = bindClient;
    }
    @Override
    public boolean isBindOrStartTLS() {
        return true;
    }
    /**
     * Returns the bind client.
     *
@@ -75,9 +62,12 @@
    }
    @Override
    BindResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
        return Responses.newBindResult(resultCode).setDiagnosticMessage(diagnosticMessage)
                .setCause(cause);
    public boolean isBindOrStartTLS() {
        return true;
    }
    @Override
    BindResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) {
        return Responses.newBindResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
    }
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/ExtendedResultLdapPromiseImpl.java
@@ -22,53 +22,50 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2014 ForgeRock AS.
 *      Portions copyright 2011-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.util.promise.PromiseImpl;
import static org.forgerock.opendj.ldap.LdapException.*;
/**
 * Extended result promise implementation.
 *
 * @param <S>
 *            The type of result returned by this promise.
 *         The type of result returned by this promise.
 */
public final class ExtendedResultLdapPromiseImpl<S extends ExtendedResult> extends
        ResultLdapPromiseImpl<ExtendedRequest<S>, S> {
    ExtendedResultLdapPromiseImpl(final int requestID, final ExtendedRequest<S> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        super(new PromiseImpl<S, LdapException>() {
            @Override
            protected LdapException tryCancel(boolean mayInterruptIfRunning) {
                if (!StartTLSExtendedRequest.OID.equals(request.getOID())) {
                    connection.abandonAsync(Requests.newAbandonRequest(requestID));
                    return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
                }
    ExtendedResultLdapPromiseImpl(
            final PromiseImpl<S, LdapException> impl,
            final int requestID,
            final ExtendedRequest<S> request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        super(impl, requestID, request, intermediateResponseHandler);
    }
                /*
                 * No other operations can be performed while a startTLS is active.
                 * Therefore it is not possible to cancel startTLS requests,
                 * since doing so will leave the connection in a state which
                 * prevents other operations from being performed.
                 */
                return null;
            }
        }, requestID, request, intermediateResponseHandler, connection);
    /**
     * Decode an extended result.
     *
     * @param result
     *         Extended result to decode.
     * @param options
     *         Decoding options.
     * @return The decoded extended result.
     * @throws DecodeException
     *         If a problem occurs during decoding.
     */
    public S decodeResult(final ExtendedResult result, final DecodeOptions options) throws DecodeException {
        return getRequest().getResultDecoder().decodeExtendedResult(result, options);
    }
    @Override
@@ -76,24 +73,8 @@
        return StartTLSExtendedRequest.OID.equals(getRequest().getOID());
    }
    /**
     * Decode an extended result.
     *
     * @param result
     *            Extended result to decode.
     * @param options
     *            Decoding options.
     * @return The decoded extended result.
     * @throws DecodeException
     *             If a problem occurs during decoding.
     */
    public S decodeResult(final ExtendedResult result, final DecodeOptions options) throws DecodeException {
        return getRequest().getResultDecoder().decodeExtendedResult(result, options);
    }
    @Override
    S newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
    S newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) {
        return getRequest().getResultDecoder().newExtendedErrorResult(resultCode, "", diagnosticMessage);
    }
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPConnectionFactoryImpl.java
@@ -21,17 +21,18 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2013-2014 ForgeRock AS.
 *      Copyright 2013-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import java.io.Closeable;
import java.net.InetSocketAddress;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.util.promise.Promise;
/**
 * Interface for all classes that actually implement
 * {@code LDAPConnectionFactory}.
 * Interface for all classes that implement {@code LDAPConnectionFactory}.
 * <p>
 * An implementation class is provided by a {@code TransportProvider}.
 * <p>
@@ -40,7 +41,7 @@
 * {@code TransportProvider} is declared in the provider-configuration file
 * {@code META-INF/services/org.forgerock.opendj.ldap.spi.TransportProvider}.
 */
public interface LDAPConnectionFactoryImpl extends ConnectionFactory {
public interface LDAPConnectionFactoryImpl extends Closeable {
    /**
     * Returns the address used by the connections created by this factory.
@@ -57,11 +58,24 @@
    String getHostName();
    /**
     * Returns the remote port number used by the connections created by this
     * factory.
     * Returns the remote port number used by the connections created by this factory.
     *
     * @return The remote port number used by the connections.
     */
    int getPort();
    /**
     * Releases any resources associated with this connection factory implementation.
     */
    @Override
    void close();
    /**
     * Asynchronously obtains a connection to the Directory Server associated
     * with this connection factory. The returned {@code Promise} can be used to
     * retrieve the completed connection.
     *
     * @return A promise which can be used to retrieve the connection.
     */
    Promise<LDAPConnectionImpl, LdapException> getConnectionAsync();
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPConnectionImpl.java
New file
@@ -0,0 +1,324 @@
/*
 * 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 2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import java.io.Closeable;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.forgerock.opendj.ldap.ConnectionEventListener;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.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.SearchRequest;
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.util.promise.Promise;
/**
 * LDAP connection interface which implementations of {@link LDAPConnectionFactoryImpl} should implement.
 */
public interface LDAPConnectionImpl extends Closeable {
    /**
     * Abandons the unfinished operation identified in the provided abandon request.
     *
     * @param request
     *         The request identifying the operation to be abandoned.
     * @return A promise whose result is Void.
     * @throws UnsupportedOperationException
     *         If this connection does not support abandon operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#abandonAsync(AbandonRequest)
     */
    LdapPromise<Void> abandonAsync(AbandonRequest request);
    /**
     * Asynchronously adds an entry to the Directory Server using the provided add request.
     *
     * @param request
     *         The add request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support add operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#addAsync(AddRequest, IntermediateResponseHandler)
     */
    LdapPromise<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Registers the provided connection event listener so that it will be notified when this connection is closed by
     * the application, receives an unsolicited notification, or experiences a fatal error.
     *
     * @param listener
     *         The listener which wants to be notified when events occur on this connection.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If the {@code listener} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#addConnectionEventListener(ConnectionEventListener)
     */
    void addConnectionEventListener(ConnectionEventListener listener);
    /**
     * Asynchronously authenticates to the Directory Server using the provided bind request.
     *
     * @param request
     *         The bind request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support bind operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#bindAsync(BindRequest, IntermediateResponseHandler)
     */
    LdapPromise<BindResult> bindAsync(BindRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Releases any resources associated with this connection.
     * <p/>
     * Calling {@code close} on a connection that is already closed has no effect.
     * @see org.forgerock.opendj.ldap.Connection#close()
     */
    @Override
    void close();
    /**
     * Releases any resources associated with this connection.
     * <p/>
     * Calling {@code close} on a connection that is already closed has no effect.
     *
     * @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.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#close(UnbindRequest, String)
     */
    void close(UnbindRequest request, String reason);
    /**
     * Asynchronously compares an entry in the Directory Server using the provided compare request.
     *
     * @param request
     *         The compare request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support compare operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#compareAsync(CompareRequest, IntermediateResponseHandler)
     */
    LdapPromise<CompareResult> compareAsync(
            CompareRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Asynchronously deletes an entry from the Directory Server using the provided delete request.
     *
     * @param request
     *         The delete request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support delete operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#deleteAsync(DeleteRequest, IntermediateResponseHandler)
     */
    LdapPromise<Result> deleteAsync(DeleteRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Installs the TLS/SSL security layer on the underlying connection. The TLS/SSL security layer will be installed
     * beneath any existing connection security layers and can only be installed at most once.
     *
     * @param sslContext
     *         The {@code SSLContext} which should be used to secure the
     * @param protocols
     *         Names of all the protocols to enable or {@code null} to use the default protocols.
     * @param suites
     *         Names of all the suites to enable or {@code null} to use the default cipher suites.
     * @return A promise which will complete once the SSL handshake has completed.
     * @throws IllegalStateException
     *         If the TLS/SSL security layer has already been installed.
     */
    Promise<Void, LdapException> enableTLS(SSLContext sslContext, List<String> protocols, List<String> suites);
    /**
     * Asynchronously performs the provided extended request in the Directory Server.
     *
     * @param <R>
     *         The type of result returned by the extended request.
     * @param request
     *         The extended request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support extended operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#extendedRequestAsync(ExtendedRequest, IntermediateResponseHandler)
     */
    <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(
            ExtendedRequest<R> request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Indicates whether or not this connection has been explicitly closed by calling {@code close}. This method will
     * not return {@code true} if a fatal error has occurred on the connection unless {@code close} has been called.
     *
     * @return {@code true} if this connection has been explicitly closed by calling {@code close}, or {@code false}
     * otherwise.
     * @see org.forgerock.opendj.ldap.Connection#isClosed()
     */
    boolean isClosed();
    /**
     * Returns {@code true} if this connection has not been closed and no fatal errors have been detected. This method
     * is guaranteed to return {@code false} only when it is called after the method {@code close} has been called.
     *
     * @return {@code true} if this connection is valid, {@code false} otherwise.
     * @see org.forgerock.opendj.ldap.Connection#isValid()
     */
    boolean isValid();
    /**
     * Asynchronously modifies an entry in the Directory Server using the provided modify request.
     *
     * @param request
     *         The modify request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support modify operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#modifyAsync(ModifyRequest, IntermediateResponseHandler)
     */
    LdapPromise<Result> modifyAsync(ModifyRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Asynchronously renames an entry in the Directory Server using the provided modify DN request.
     *
     * @param request
     *         The modify DN request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support modify DN operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#modifyDNAsync(ModifyDNRequest, IntermediateResponseHandler)
     */
    LdapPromise<Result> modifyDNAsync(
            ModifyDNRequest request, IntermediateResponseHandler intermediateResponseHandler);
    /**
     * Removes the provided connection event listener from this connection so that it will no longer be notified when
     * this connection is closed by the application, receives an unsolicited notification, or experiences a fatal
     * error.
     *
     * @param listener
     *         The listener which no longer wants to be notified when events occur on this connection.
     * @throws NullPointerException
     *         If the {@code listener} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#removeConnectionEventListener(ConnectionEventListener)
     */
    void removeConnectionEventListener(ConnectionEventListener listener);
    /**
     * Asynchronously searches the Directory Server using the provided search request.
     *
     * @param request
     *         The search request.
     * @param intermediateResponseHandler
     *         An intermediate response handler which can be used to process any intermediate responses as they are
     *         received, may be {@code null}.
     * @param entryHandler
     *         A search result handler which can be used to asynchronously process the search result entries and
     *         references as they are received, may be {@code null}.
     * @return A promise representing the result of the operation.
     * @throws UnsupportedOperationException
     *         If this connection does not support search operations.
     * @throws IllegalStateException
     *         If this connection has already been closed, i.e. if {@code isClosed() == true}.
     * @throws NullPointerException
     *         If {@code request} was {@code null}.
     * @see org.forgerock.opendj.ldap.Connection#searchAsync(SearchRequest, IntermediateResponseHandler,
     * SearchResultHandler)
     */
    LdapPromise<Result> searchAsync(
            SearchRequest request,
            IntermediateResponseHandler intermediateResponseHandler,
            SearchResultHandler entryHandler);
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapPromiseWrapper.java
@@ -63,6 +63,7 @@
        this.requestID = requestID;
    }
    @SuppressWarnings("unchecked")
    @Override
    public int getRequestID() {
        return wrappedPromise instanceof LdapPromise ? ((LdapPromise<R>) wrappedPromise).getRequestID()
@@ -185,7 +186,6 @@
    }
    @Override
    // @Checkstyle:ignore
    public LdapPromise<R> thenFinally(Runnable onResultOrException) {
        wrappedPromise.thenFinally(onResultOrException);
        return this;
@@ -195,7 +195,7 @@
    // @Checkstyle:ignore
    public <EOUT extends Exception> Promise<R, EOUT> thenCatchAsync(
            AsyncFunction<? super LdapException, R, EOUT> onException) {
        return null;
        return wrappedPromise.thenCatchAsync(onException);
    }
    public P getWrappedPromise() {
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapPromises.java
@@ -25,6 +25,10 @@
 */
package org.forgerock.opendj.ldap.spi;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.responses.Responses.newCompareResult;
import static org.forgerock.opendj.ldap.responses.Responses.newResult;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
@@ -36,164 +40,147 @@
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.Request;
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.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.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.Promises;
import static org.forgerock.opendj.ldap.responses.Responses.*;
/**
 * Utility methods for creating and composing {@link LdapPromise}s.
 */
public final class LdapPromises {
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task which has
     * already succeeded with the provided result. Attempts to get the result
     * will immediately return the result.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param result
     *            The result of the asynchronous task.
     * @return A {@link LdapPromise} representing an asynchronous task which has
     *         already succeeded with the provided result.
     */
    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result) {
        return wrap(Promises.<R, LdapException> newResultPromise(result), -1);
    private LdapPromises() {
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task,
     * identified by the provided requestID, which has already succeeded with
     * the provided result. Attempts to get the result will immediately return
     * the result.
     * Converts a {@link Promise} to a {@link LdapPromise}.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param result
     *            The result of the asynchronous task.
     * @param requestID
     *            The request ID of the succeeded task.
     * @return A {@link LdapPromise} representing an asynchronous task which has
     *         already succeeded with the provided result.
     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
     *         side-effects).
     * @param wrappedPromise
     *         The {@link Promise} to wrap.
     * @return A {@link LdapPromise} representing the same asynchronous task as the {@link Promise} provided.
     */
    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result, int requestID) {
        return wrap(Promises.<R, LdapException> newResultPromise(result), requestID);
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task which has
     * already failed with the provided error.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param <E>
     *            The type of the exception thrown by the task if it fails.
     * @param error
     *            The exception indicating why the asynchronous task has failed.
     * @return A {@link LdapPromise} representing an asynchronous task which has
     *         already failed with the provided error.
     */
    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error) {
        return wrap(Promises.<R, LdapException> newExceptionPromise(error), -1);
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task,
     * identified by the provided requestID, which has already failed with the
     * provided error.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param <E>
     *            The type of the exception thrown by the task if it fails.
     * @param error
     *            The exception indicating why the asynchronous task has failed.
     * @param requestID
     *            The request ID of the failed task.
     * @return A {@link LdapPromise} representing an asynchronous task which has
     *         already failed with the provided error.
     */
    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error, int requestID) {
        return wrap(Promises.<R, LdapException> newExceptionPromise(error), requestID);
    }
    /**
     * Creates a new {@link ResultLdapPromiseImpl} to handle  a standard request (add, delete, modify and modidyDN).
     *
     * @param <R>
     *           The type of the task's request.
     *
     * @param requestID
     *            Identifier of the request.
     * @param request
     *            The request sent to the server.
     * @param intermediateResponseHandler
     *            Handler that consumes intermediate responses from extended operations.
     * @param connection
     *            The connection to directory server.
     *
     * @return The new {@link ResultLdapPromiseImpl}.
     */
    public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(final int requestID,
            final R request, final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        return new ResultLdapPromiseImpl<R, Result>(requestID, request, intermediateResponseHandler, connection) {
            @Override
            protected Result newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) {
                return newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
            }
        };
    public static <R> LdapPromise<R> asPromise(Promise<R, LdapException> wrappedPromise) {
        return wrap(wrappedPromise, -1);
    }
    /**
     * Creates a new bind {@link BindResultLdapPromiseImpl}.
     *
     * @param requestID
     *            Identifier of the request.
     *         Identifier of the request.
     * @param request
     *            The bind request sent to server.
     *         The bind request sent to server.
     * @param bindClient
     *            Client that binds to the server.
     *         Client that binds to the server.
     * @param intermediateResponseHandler
     *            Handler that consumes intermediate responses from extended operations.
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *            The connection to directory server.
     *
     *         The connection to directory server.
     * @return The new {@link BindResultLdapPromiseImpl}.
     */
    public static BindResultLdapPromiseImpl newBindLdapPromise(final int requestID,
            final BindRequest request, final BindClient bindClient,
            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
        return new BindResultLdapPromiseImpl(requestID, request, bindClient, intermediateResponseHandler, connection);
    public static BindResultLdapPromiseImpl newBindLdapPromise(
            final int requestID,
            final BindRequest request,
            final BindClient bindClient,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        return new BindResultLdapPromiseImpl(LdapPromises.<BindResult>newInnerBindOrStartTLSPromise(),
                                             requestID,
                                             request,
                                             bindClient,
                                             intermediateResponseHandler);
    }
    /**
     * Creates a new bind {@link BindResultLdapPromiseImpl}.
     *
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The bind request sent to server.
     * @param bindClient
     *         Client that binds to the server.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link BindResultLdapPromiseImpl}.
     */
    public static BindResultLdapPromiseImpl newBindLdapPromise(
            final int requestID,
            final BindRequest request,
            final BindClient bindClient,
            final IntermediateResponseHandler intermediateResponseHandler,
            final LDAPConnectionImpl connection) {
        return new BindResultLdapPromiseImpl(LdapPromises.<BindResult>newInnerBindOrStartTLSPromise(),
                                             requestID,
                                             request,
                                             bindClient,
                                             intermediateResponseHandler);
    }
    /**
     * Creates a new compare {@link ResultLdapPromiseImpl}.
     *
     * @param requestID
     *            Identifier of the request.
     *         Identifier of the request.
     * @param request
     *            The compare request sent to the server.
     *         The compare request sent to the server.
     * @param intermediateResponseHandler
     *            Handler that consumes intermediate responses from extended operations.
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *            The connection to directory server.
     *
     *         The connection to directory server.
     * @return The new {@link ResultLdapPromiseImpl}.
     */
    public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(final int requestID,
            final CompareRequest request, final IntermediateResponseHandler intermediateResponseHandler,
    public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
            final int requestID,
            final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        return new ResultLdapPromiseImpl<CompareRequest, CompareResult>(requestID, request, intermediateResponseHandler,
                connection) {
        final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID);
        return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
    }
    /**
     * Creates a new compare {@link ResultLdapPromiseImpl}.
     *
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The compare request sent to the server.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link ResultLdapPromiseImpl}.
     */
    public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
            final int requestID,
            final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final LDAPConnectionImpl connection) {
        final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID);
        return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
    }
    private static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(
            final PromiseImpl<CompareResult, LdapException> innerPromise,
            final int requestID,
            final CompareRequest request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        return new ResultLdapPromiseImpl<CompareRequest, CompareResult>(innerPromise,
                                                                        requestID,
                                                                        request,
                                                                        intermediateResponseHandler) {
            @Override
            protected CompareResult newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) {
                return newCompareResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
@@ -205,69 +192,292 @@
     * Creates a new extended {@link ExtendedResultLdapPromiseImpl}.
     *
     * @param <S>
     *            The type of result returned by this promise.
     *         The type of result returned by this promise.
     * @param requestID
     *            Identifier of the request.
     *         Identifier of the request.
     * @param request
     *            The extended request sent to the server.
     *         The extended request sent to the server.
     * @param intermediateResponseHandler
     *            Handler that consumes intermediate responses from extended operations.
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *            The connection to directory server.
     *
     *         The connection to directory server.
     * @return The new {@link ExtendedResultLdapPromiseImpl}.
     */
    public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise(
            final int requestID, final ExtendedRequest<S> request,
            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
        return new ExtendedResultLdapPromiseImpl<>(requestID, request, intermediateResponseHandler, connection);
            final int requestID,
            final ExtendedRequest<S> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        final PromiseImpl<S, LdapException> innerPromise;
        if (!StartTLSExtendedRequest.OID.equals(request.getOID())) {
            innerPromise = newInnerBindOrStartTLSPromise();
        } else {
            innerPromise = newInnerPromise(connection, requestID);
        }
        return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler);
    }
    /**
     * Creates a new extended {@link ExtendedResultLdapPromiseImpl}.
     *
     * @param <S>
     *         The type of result returned by this promise.
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The extended request sent to the server.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link ExtendedResultLdapPromiseImpl}.
     */
    public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise(
            final int requestID,
            final ExtendedRequest<S> request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final LDAPConnectionImpl connection) {
        final PromiseImpl<S, LdapException> innerPromise;
        if (!StartTLSExtendedRequest.OID.equals(request.getOID())) {
            innerPromise = newInnerBindOrStartTLSPromise();
        } else {
            innerPromise = newInnerPromise(connection, requestID);
        }
        return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler);
    }
    /**
     * Creates a new {@link ResultLdapPromiseImpl} to handle  a standard request (add, delete, modify and modidyDN).
     *
     * @param <R>
     *         The type of the task's request.
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The request sent to the server.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link ResultLdapPromiseImpl}.
     */
    public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
            final int requestID,
            final R request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID);
        return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
    }
    /**
     * Creates a new {@link ResultLdapPromiseImpl} to handle  a standard request (add, delete, modify and modidyDN).
     *
     * @param <R>
     *         The type of the task's request.
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The request sent to the server.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link ResultLdapPromiseImpl}.
     */
    public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
            final int requestID,
            final R request,
            final IntermediateResponseHandler intermediateResponseHandler,
            final LDAPConnectionImpl connection) {
        final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID);
        return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler);
    }
    private static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(
            final PromiseImpl<Result, LdapException> innerPromise,
            final int requestID,
            final R request,
            final IntermediateResponseHandler intermediateResponseHandler) {
        return new ResultLdapPromiseImpl<R, Result>(innerPromise, requestID, request, intermediateResponseHandler) {
            @Override
            protected Result newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) {
                return newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
            }
        };
    }
    /**
     * Creates a new search {@link SearchResultLdapPromiseImpl}.
     *
     * @param requestID
     *            Identifier of the request.
     *         Identifier of the request.
     * @param request
     *            The search request sent to the server.
     *         The search request sent to the server.
     * @param resultHandler
     *            Handler that consumes search result.
     *         Handler that consumes search result.
     * @param intermediateResponseHandler
     *            Handler that consumes intermediate responses from extended operations.
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *            The connection to directory server.
     *
     *         The connection to directory server.
     * @return The new {@link SearchResultLdapPromiseImpl}.
     */
    public static SearchResultLdapPromiseImpl newSearchLdapPromise(final int requestID, final SearchRequest request,
            final SearchResultHandler resultHandler, final IntermediateResponseHandler intermediateResponseHandler,
    public static SearchResultLdapPromiseImpl newSearchLdapPromise(
            final int requestID,
            final SearchRequest request,
            final SearchResultHandler resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final Connection connection) {
        return new SearchResultLdapPromiseImpl(requestID, request, resultHandler, intermediateResponseHandler,
                connection);
        return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID),
                                               requestID,
                                               request,
                                               resultHandler,
                                               intermediateResponseHandler);
    }
    /**
     * Creates a new search {@link SearchResultLdapPromiseImpl}.
     *
     * @param requestID
     *         Identifier of the request.
     * @param request
     *         The search request sent to the server.
     * @param resultHandler
     *         Handler that consumes search result.
     * @param intermediateResponseHandler
     *         Handler that consumes intermediate responses from extended operations.
     * @param connection
     *         The connection to directory server.
     * @return The new {@link SearchResultLdapPromiseImpl}.
     */
    public static SearchResultLdapPromiseImpl newSearchLdapPromise(
            final int requestID,
            final SearchRequest request,
            final SearchResultHandler resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler,
            final LDAPConnectionImpl connection) {
        return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID),
                                               requestID,
                                               request,
                                               resultHandler,
                                               intermediateResponseHandler);
    }
    /**
     * Converts a {@link Promise} to a {@link LdapPromise}.
     * Returns a {@link LdapPromise} representing an asynchronous task which has already failed with the provided
     * error.
     *
     * @param <R>
     *            The type of the task's result, or {@link Void} if the task
     *            does not return anything (i.e. it only has side-effects).
     * @param wrappedPromise
     *            The {@link Promise} to wrap.
     * @return A {@link LdapPromise} representing the same asynchronous task as
     *         the {@link Promise} provided.
     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
     *         side-effects).
     * @param <E>
     *         The type of the exception thrown by the task if it fails.
     * @param error
     *         The exception indicating why the asynchronous task has failed.
     * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error.
     */
    public static <R> LdapPromise<R> asPromise(Promise<R, LdapException> wrappedPromise) {
        return wrap(wrappedPromise, -1);
    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error) {
        return wrap(Promises.<R, LdapException>newExceptionPromise(error), -1);
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has
     * already failed with the provided error.
     *
     * @param <R>
     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
     *         side-effects).
     * @param <E>
     *         The type of the exception thrown by the task if it fails.
     * @param error
     *         The exception indicating why the asynchronous task has failed.
     * @param requestID
     *         The request ID of the failed task.
     * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error.
     */
    public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error, int requestID) {
        return wrap(Promises.<R, LdapException>newExceptionPromise(error), requestID);
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
     * result. Attempts to get the result will immediately return the result.
     *
     * @param <R>
     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
     *         side-effects).
     * @param result
     *         The result of the asynchronous task.
     * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
     * result.
     */
    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result) {
        return wrap(Promises.<R, LdapException>newResultPromise(result), -1);
    }
    /**
     * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has
     * already succeeded with the provided result. Attempts to get the result will immediately return the result.
     *
     * @param <R>
     *         The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has
     *         side-effects).
     * @param result
     *         The result of the asynchronous task.
     * @param requestID
     *         The request ID of the succeeded task.
     * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided
     * result.
     */
    public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result, int requestID) {
        return wrap(Promises.<R, LdapException>newResultPromise(result), requestID);
    }
    private static <S extends Result> PromiseImpl<S, LdapException> newInnerBindOrStartTLSPromise() {
        return new PromiseImpl<S, LdapException>() {
            @Override
            protected LdapException tryCancel(boolean mayInterruptIfRunning) {
                /*
                 * No other operations can be performed while a bind or StartTLS is active. Therefore it is not
                 * possible to cancel bind or StartTLS requests, since doing so will leave the connection in a
                 * state which prevents other operations from being performed.
                 */
                return null;
            }
        };
    }
    private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise(
            final Connection connection, final int requestID) {
        return new PromiseImpl<S, LdapException>() {
            @Override
            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
                /*
                 * This will abandon the request, but will also recursively cancel this future. There is no risk of
                 * an infinite loop because the state of this future has already been changed.
                 */
                connection.abandonAsync(Requests.newAbandonRequest(requestID));
                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
            }
        };
    }
    private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise(
            final LDAPConnectionImpl connection, final int requestID) {
        return new PromiseImpl<S, LdapException>() {
            @Override
            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
                /*
                 * This will abandon the request, but will also recursively cancel this future. There is no risk of
                 * an infinite loop because the state of this future has already been changed.
                 */
                connection.abandonAsync(Requests.newAbandonRequest(requestID));
                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
            }
        };
    }
    static <R> LdapPromise<R> wrap(Promise<R, LdapException> wrappedPromise, int requestID) {
        return new LdapPromiseWrapper<>(wrappedPromise, requestID);
    }
    private LdapPromises() {
    }
}
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/ResultLdapPromiseImpl.java
@@ -21,18 +21,16 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 *      Copyright 2014-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.IntermediateResponse;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
@@ -58,24 +56,8 @@
    private IntermediateResponseHandler intermediateResponseHandler;
    private volatile long timestamp;
    ResultLdapPromiseImpl(final int requestID, final R request,
            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
        this(new PromiseImpl<S, LdapException>() {
            @Override
            protected final LdapException tryCancel(final boolean mayInterruptIfRunning) {
                /*
                 * This will abandon the request, but will also recursively cancel this
                 * future. There is no risk of an infinite loop because the state of
                 * this future has already been changed.
                 */
                connection.abandonAsync(Requests.newAbandonRequest(requestID));
                return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED);
            }
        }, requestID, request, intermediateResponseHandler, connection);
    }
    ResultLdapPromiseImpl(final PromiseImpl<S, LdapException> impl, final int requestID, final R request,
            final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) {
            final IntermediateResponseHandler intermediateResponseHandler) {
        super(impl, requestID);
        this.request = request;
        this.intermediateResponseHandler = intermediateResponseHandler;
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/SearchResultLdapPromiseImpl.java
@@ -22,13 +22,13 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2014 ForgeRock AS.
 *      Portions copyright 2011-2015 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.spi;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl;
@@ -38,6 +38,7 @@
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.PromiseImpl;
/**
 * Search result promise implementation.
@@ -47,13 +48,15 @@
    private SearchResultHandler searchResultHandler;
    private final boolean isPersistentSearch;
    SearchResultLdapPromiseImpl(final int requestID, final SearchRequest request,
        final SearchResultHandler resultHandler, final IntermediateResponseHandler intermediateResponseHandler,
        final Connection connection) {
        super(requestID, request, intermediateResponseHandler, connection);
    SearchResultLdapPromiseImpl(
            final PromiseImpl<Result, LdapException> impl,
            final int requestID,
            final SearchRequest request,
            final SearchResultHandler resultHandler,
            final IntermediateResponseHandler intermediateResponseHandler) {
        super(impl, requestID, request, intermediateResponseHandler);
        this.searchResultHandler = resultHandler;
        this.isPersistentSearch =
            request.containsControl(PersistentSearchRequestControl.OID)
        this.isPersistentSearch = request.containsControl(PersistentSearchRequestControl.OID)
                || request.containsControl(ADNotificationRequestControl.OID);
    }
@@ -90,8 +93,7 @@
    }
    @Override
    Result newErrorResult(final ResultCode resultCode, final String diagnosticMessage,
            final Throwable cause) {
    Result newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) {
        return Responses.newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause);
    }
opendj-sdk/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1666,3 +1666,6 @@
DOC_SUPPORTED_SUBTYPES_TITLE=Supported Language Subtypes
DOC_SUPPORTED_SUBTYPES_INDEXTERM=Language subtypes
DOC_LANGUAGE_SH=Serbo-Croatian
LDAP_CONNECTION_CONNECT_TIMEOUT=The connection attempt to server %s has failed \
 because the connection timeout period of %d ms was exceeded
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
@@ -26,7 +26,7 @@
package org.forgerock.opendj.io;
import static org.fest.assertions.Assertions.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.DECODE_OPTIONS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS;
import java.io.IOException;
@@ -265,7 +265,7 @@
                    ExtendedRequest<R> request) throws DecodeException, IOException {
                CancelExtendedRequest cancelRequest =
                        CancelExtendedRequest.DECODER.decodeExtendedRequest(request,
                            Options.defaultOptions().get(DECODE_OPTIONS));
                            Options.defaultOptions().get(LDAP_DECODE_OPTIONS));
                assertThat(cancelRequest.getOID()).isEqualTo(oidCancel);
                assertThat(cancelRequest.getRequestID()).isEqualTo(requestID);
            }
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
@@ -26,16 +26,43 @@
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.ResultCode.SUCCESS;
import static org.forgerock.opendj.ldap.SearchScope.BASE_OBJECT;
import static org.forgerock.opendj.ldap.TestCaseUtils.mockTimeService;
import static org.forgerock.opendj.ldap.requests.Requests.newModifyRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.forgerock.opendj.ldap.responses.Responses.newBindResult;
import static org.forgerock.opendj.ldap.responses.Responses.newResult;
import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.newLdapPromiseImpl;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newBindLdapPromise;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newFailedLdapPromise;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newSearchLdapPromise;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.*;
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.opendj.ldap.spi.LDAPConnectionFactoryImpl;
import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl;
import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.TransportProvider;
import org.forgerock.util.Options;
import org.forgerock.util.promise.ExceptionHandler;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
@@ -49,19 +76,6 @@
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..
 */
@@ -86,13 +100,12 @@
    // @formatter:on
    private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT,
            "(objectclass=*)", "1.1");
    private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT, "(objectclass=*)", "1.1");
    private Connection connection;
    private ConnectionFactory factory;
    private LDAPConnectionImpl ldapConnection;
    private LDAPConnectionFactoryImpl ldapFactory;
    private Connection hbc;
    private HeartBeatConnectionFactory hbcf;
    private LDAPConnectionFactory hbcf;
    private List<ConnectionEventListener> listeners;
    private MockScheduler scheduler;
@@ -124,8 +137,8 @@
            assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check.
            hbcf.close();
        } finally {
            connection = null;
            factory = null;
            ldapConnection = null;
            ldapFactory = null;
            hbcf = null;
            listeners = null;
            scheduler = null;
@@ -143,13 +156,19 @@
         * 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)));
        final SearchResultLdapPromiseImpl heartBeatPromise =
                newSearchLdapPromise(-1, HEARTBEAT, null, null, ldapConnection);
        when(ldapConnection.searchAsync(same(HEARTBEAT),
                                        any(IntermediateResponseHandler.class),
                                        any(SearchResultHandler.class)))
                .thenReturn(heartBeatPromise);
        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));
        verify(ldapConnection, times(2)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
        /*
@@ -157,10 +176,11 @@
         * heart beat completes.
         */
        hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(0)).bindAsync(any(BindRequest.class));
        verify(ldapConnection, times(0)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        // Send fake heartbeat response, releasing the bind request.
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        heartBeatPromise.getWrappedPromise().handleResult(newResult(SUCCESS));
        verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
    }
    @Test
@@ -230,27 +250,27 @@
        // Get a connection and check that it was pinged.
        hbc = hbcf.getConnection();
        verifyHeartBeatSent(connection, 1);
        verifyHeartBeatSent(ldapConnection, 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.
        verifyHeartBeatSent(ldapConnection, 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.
        verifyHeartBeatSent(ldapConnection, 2); // Heartbeat sent.
        assertThat(hbc.isValid()).isTrue();
        // Now force the heartbeat to fail.
        mockHeartBeatResponse(connection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN);
        mockHeartBeatResponse(ldapConnection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN);
        when(hbcf.timeService.now()).thenReturn(11000L);
        scheduler.runAllTasks();
        verifyHeartBeatSent(connection, 3);
        verifyHeartBeatSent(ldapConnection, 3);
        assertThat(hbc.isValid()).isFalse();
        // Flush redundant timeout tasks.
@@ -262,8 +282,7 @@
        hbc.modifyAsync(newModifyRequest(DN.rootDN())).thenOnException(mockHandler);
        final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class);
        verify(mockHandler).handleException(arg.capture());
        assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(
                ResultCode.CLIENT_SIDE_SERVER_DOWN);
        assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
        assertThat(hbc.isValid()).isFalse();
        assertThat(hbc.isClosed()).isFalse();
@@ -277,10 +296,10 @@
        hbc = hbcf.getConnection();
        // Now force the heartbeat to fail due to timeout.
        mockHeartBeatResponse(connection, listeners, null /* no response */);
        mockHeartBeatResponse(ldapConnection, listeners, null /* no response */);
        when(hbcf.timeService.now()).thenReturn(11000L);
        scheduler.runAllTasks(); // Send the heartbeat.
        verifyHeartBeatSent(connection, 2);
        verifyHeartBeatSent(ldapConnection, 2);
        assertThat(hbc.isValid()).isTrue(); // Not checked yet.
        when(hbcf.timeService.now()).thenReturn(12000L);
        scheduler.runAllTasks(); // Check for heartbeat.
@@ -302,16 +321,18 @@
        when(hbcf.timeService.now()).thenReturn(11000L);
        hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        verify(ldapConnection, 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));
        verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
        // Send fake bind response, releasing the heartbeat.
        when(hbcf.timeService.now()).thenReturn(11099L);
        ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
        promise.getWrappedPromise().handleResult(newBindResult(SUCCESS));
        // Check that bind response acts as heartbeat.
        assertThat(hbc.isValid()).isTrue();
@@ -329,12 +350,14 @@
        // 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(ldapConnection, 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));
        verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
        // Check that lack of bind response acts as heartbeat timeout.
        assertThat(hbc.isValid()).isTrue();
@@ -352,7 +375,7 @@
        hbc = hbcf.getConnection();
        hbc.bindAsync(newSimpleBindRequest());
        verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
        /*
         * Now attempt the heartbeat which should not happen because there is a
@@ -361,16 +384,20 @@
        when(hbcf.timeService.now()).thenReturn(11000L);
        // Attempt to send the heartbeat.
        scheduler.runAllTasks();
        verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
        verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
        // Send fake bind response, releasing the heartbeat.
        ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
        promise.getWrappedPromise().handleResult(newBindResult(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));
        verify(ldapConnection, times(2)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
    }
    @Test
@@ -392,36 +419,51 @@
    private void mockConnectionWithInitialHeartbeatResult(final ResultCode initialHeartBeatResult) {
        listeners = new LinkedList<>();
        connection = mockConnection(listeners);
        when(connection.isValid()).thenReturn(true);
        mockHeartBeatResponse(connection, listeners, initialHeartBeatResult);
        // Underlying connection factory.
        factory = mockConnectionFactory(connection);
        ldapConnection = mockLDAPConnectionImpl(listeners);
        when(ldapConnection.isValid()).thenReturn(true);
        mockHeartBeatResponse(ldapConnection, listeners, initialHeartBeatResult);
        ldapFactory = mock(LDAPConnectionFactoryImpl.class);
        when(ldapFactory.getConnectionAsync()).thenAnswer(new Answer<Promise<LDAPConnectionImpl, LdapException>>() {
            @Override
            public Promise<LDAPConnectionImpl, LdapException> answer(final InvocationOnMock invocation)
                    throws Throwable {
                return newSuccessfulLdapPromise(ldapConnection);
            }
        });
        TransportProvider provider = mock(TransportProvider.class);
        when(provider.getLDAPConnectionFactory(anyString(), anyInt(), any(Options.class))).thenReturn(ldapFactory);
        scheduler = new MockScheduler();
        // Create heart beat connection factory.
        scheduler = new MockScheduler();
        hbcf = new HeartBeatConnectionFactory(factory, 10000, 100, TimeUnit.MILLISECONDS, HEARTBEAT, scheduler);
        hbcf = new LDAPConnectionFactory("dummyHost",
                                         1389,
                                         Options.defaultOptions()
                                                .set(TRANSPORT_PROVIDER_INSTANCE, provider)
                                                .set(HEARTBEAT_ENABLED, true)
                                                .set(HEARTBEAT_TIMEOUT, duration("100 ms"))
                                                .set(HEARTBEAT_SEARCH_REQUEST, HEARTBEAT)
                                                .set(HEARTBEAT_SCHEDULER, 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));
        final BindResultLdapPromiseImpl bindPromise = newBindLdapPromise(-1, null, null, null, ldapConnection);
        when(ldapConnection.bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)))
                .thenReturn(bindPromise);
        return bindPromise;
    }
    private Connection mockHeartBeatResponse(final Connection mockConnection,
            final List<ConnectionEventListener> listeners, final ResultCode resultCode) {
        Answer<LdapPromise<Result>> answer = new Answer<LdapPromise<Result>>() {
    //
    private void mockHeartBeatResponse(
            final LDAPConnectionImpl mockConnection,
            final List<ConnectionEventListener> listeners,
            final ResultCode resultCode) {
        // @Checkstyle:off
        when(mockConnection.searchAsync(any(SearchRequest.class),
                                        any(IntermediateResponseHandler.class),
                                        any(SearchResultHandler.class))).thenAnswer(new Answer<LdapPromise<Result>>() {
            @Override
            public LdapPromise<Result> answer(final InvocationOnMock invocation) throws Throwable {
                if (resultCode == null) {
@@ -440,16 +482,40 @@
                    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;
        });
        // @Checkstyle:on
    }
    private void verifyHeartBeatSent(final Connection connection, final int times) {
        verify(connection, times(times)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
    private void verifyHeartBeatSent(final LDAPConnectionImpl connection, final int times) {
        verify(connection, times(times)).searchAsync(same(HEARTBEAT),
                                                     any(IntermediateResponseHandler.class),
                                                     any(SearchResultHandler.class));
    }
    private static LDAPConnectionImpl mockLDAPConnectionImpl(final List<ConnectionEventListener> listeners) {
        final LDAPConnectionImpl mockConnection = mock(LDAPConnectionImpl.class);
        // Handle listener registration / deregistration in mock connection.
        doAnswer(new Answer<Void>() {
            @Override
            public Void answer(final InvocationOnMock invocation) throws Throwable {
                final ConnectionEventListener listener =
                        (ConnectionEventListener) invocation.getArguments()[0];
                listeners.add(listener);
                return null;
            }
        }).when(mockConnection).addConnectionEventListener(any(ConnectionEventListener.class));
        doAnswer(new Answer<Void>() {
            @Override
            public Void answer(final InvocationOnMock invocation) throws Throwable {
                final ConnectionEventListener listener =
                        (ConnectionEventListener) invocation.getArguments()[0];
                listeners.remove(listener);
                return null;
            }
        }).when(mockConnection).removeConnectionEventListener(any(ConnectionEventListener.class));
        return mockConnection;
    }
}
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -550,7 +550,7 @@
        }
        sslContext = new SSLContextBuilder().getSSLContext();
        listener = new LDAPListener(findFreeSocketAddress(), getInstance(),
                        Options.defaultOptions().set(BACKLOG, 4096));
                        Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096));
        isRunning = true;
    }
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPConnectionFactory.java
@@ -28,37 +28,23 @@
import java.net.InetSocketAddress;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.util.Options;
import org.forgerock.util.promise.Promise;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.util.promise.Promises.*;
import static org.mockito.Mockito.*;
/**
 * Basic LDAP connection factory implementation to use for tests only.
 */
public final class BasicLDAPConnectionFactory implements LDAPConnectionFactoryImpl {
    private final Options options;
final class BasicLDAPConnectionFactory implements LDAPConnectionFactoryImpl {
    private final String host;
    private final int port;
    /**
     * Creates a new LDAP connection factory which does nothing.
     *  @param host
     *            The address of the Directory Server to connect to.
     * @param port
     *            The port of the Directory Server to connect to.
     * @param options
     */
    public BasicLDAPConnectionFactory(final String host, final int port, final Options options) {
    BasicLDAPConnectionFactory(final String host, final int port, final Options options) {
        this.host = host;
        this.port = port;
        this.options = Options.copyOf(options);
    }
    @Override
@@ -67,25 +53,11 @@
    }
    @Override
    public Connection getConnection() throws LdapException {
        try {
            return getConnectionAsync().getOrThrow();
        } catch (final InterruptedException e) {
            throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
        }
    public Promise<LDAPConnectionImpl, LdapException> getConnectionAsync() {
        return newResultPromise(mock(LDAPConnectionImpl.class));
    }
    @Override
    public Promise<Connection, LdapException> getConnectionAsync() {
        return newResultPromise(mock(Connection.class));
    }
    /**
     * Returns the address of the Directory Server.
     *
     * @return The address of the Directory Server.
     */
    @Override
    public InetSocketAddress getSocketAddress() {
        return new InetSocketAddress(host, port);
    }
@@ -104,8 +76,4 @@
    public String toString() {
        return getClass().getSimpleName() + "(" + host + ':' + port + ')';
    }
    Options getLDAPOptions() {
        return options;
    }
}
opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -26,11 +26,21 @@
 */
package org.forgerock.opendj.grizzly;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_REQUEST_TIMEOUT;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.REQUEST_TIMEOUT;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.ResultCode.CLIENT_SIDE_LOCAL_ERROR;
import static org.forgerock.opendj.ldap.responses.Responses.newResult;
import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
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;
@@ -40,7 +50,6 @@
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;
@@ -61,6 +70,7 @@
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
@@ -71,24 +81,23 @@
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl;
import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl;
import org.forgerock.util.Options;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.time.Duration;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
/** LDAP connection implementation. */
final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements TimeoutEventListener {
final class GrizzlyLDAPConnection implements LDAPConnectionImpl, TimeoutEventListener {
    /**
     * A dummy SSL client engine configurator as SSLFilter only needs client
     * config. This prevents Grizzly from needlessly using JVM defaults which
@@ -112,6 +121,7 @@
    private final AtomicInteger nextMsgID = new AtomicInteger(1);
    private final GrizzlyLDAPConnectionFactory factory;
    private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingRequests = new ConcurrentHashMap<>();
    private final long requestTimeoutMS;
    private final Object stateLock = new Object();
    /** Guarded by stateLock. */
    private Result connectionInvalidReason;
@@ -133,6 +143,8 @@
            final GrizzlyLDAPConnectionFactory factory) {
        this.connection = connection;
        this.factory = factory;
        final Duration requestTimeout = factory.getLDAPOptions().get(REQUEST_TIMEOUT);
        this.requestTimeoutMS = requestTimeout.isUnlimited() ? 0 : requestTimeout.to(TimeUnit.MILLISECONDS);
    }
    @Override
@@ -306,6 +318,11 @@
    }
    @Override
    public void close() {
        close(Requests.newUnbindRequest(), null);
    }
    @Override
    public void close(final UnbindRequest request, final String reason) {
        // FIXME: I18N need to internationalize this message.
        Reject.ifNull(request);
@@ -548,17 +565,16 @@
    @Override
    public long handleTimeout(final long currentTime) {
        final long timeout = factory.getLDAPOptions().get(TIMEOUT_IN_MILLISECONDS);
        if (timeout <= 0) {
        if (requestTimeoutMS <= 0) {
            return 0;
        }
        long delay = timeout;
        long delay = requestTimeoutMS;
        for (final ResultLdapPromiseImpl<?, ?> promise : pendingRequests.values()) {
            if (promise == null || !promise.checkForTimeout()) {
                continue;
            }
            final long diff = (promise.getTimestamp() + timeout) - currentTime;
            final long diff = (promise.getTimestamp() + requestTimeoutMS) - currentTime;
            if (diff > 0) {
                // Will expire in diff milliseconds.
                delay = Math.min(delay, diff);
@@ -577,17 +593,17 @@
                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());
                        LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(requestTimeoutMS).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());
                        LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(requestTimeoutMS).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());
                        LDAP_CONNECTION_REQUEST_TIMEOUT.get(requestTimeoutMS).toString());
                promise.adaptErrorResult(result);
                /*
@@ -607,7 +623,7 @@
    @Override
    public long getTimeout() {
        return factory.getLDAPOptions().get(TIMEOUT_IN_MILLISECONDS);
        return requestTimeoutMS;
    }
    /**
@@ -769,8 +785,37 @@
        bindOrStartTLSInProgress.set(state);
    }
    @Override
    public Promise<Void, LdapException> enableTLS(
            final SSLContext sslContext,
            final List<String> sslEnabledProtocols,
            final List<String> sslEnabledCipherSuites) {
        final PromiseImpl<Void, LdapException> promise = PromiseImpl.create();
        final EmptyCompletionHandler<SSLEngine> completionHandler = new EmptyCompletionHandler<SSLEngine>() {
            @Override
            public void completed(final SSLEngine result) {
                promise.handleResult(null);
            }
            @Override
            public void failed(final Throwable throwable) {
                final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR)
                        .setCause(throwable).setDiagnosticMessage("SSL handshake failed");
                connectionErrorOccurred(errorResult);
                promise.handleException(newLdapException(errorResult));
            }
        };
        try {
            startTLS(sslContext, sslEnabledProtocols, sslEnabledCipherSuites, completionHandler);
        } catch (final IOException e) {
            completionHandler.failed(e);
        }
        return promise;
    }
    void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites,
            final CompletionHandler<SSLEngine> completionHandler) throws IOException {
                  final CompletionHandler<SSLEngine> completionHandler) throws IOException {
        synchronized (stateLock) {
            if (isTLSEnabled()) {
                throw new IllegalStateException("TLS already enabled");
opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -27,32 +27,35 @@
package org.forgerock.opendj.grizzly;
import java.io.IOException;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.buildFilterChain;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.CONNECT_TIMEOUT;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER;
import java.net.InetSocketAddress;
import java.util.List;
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;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.TimeoutChecker;
import org.forgerock.opendj.ldap.TimeoutEventListener;
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.opendj.ldap.spi.LDAPConnectionImpl;
import org.forgerock.util.Option;
import org.forgerock.util.Options;
import org.forgerock.util.promise.ExceptionHandler;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
import org.forgerock.util.promise.ResultHandler;
import org.forgerock.util.time.Duration;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.SocketConnectorHandler;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler;
@@ -60,14 +63,6 @@
import com.forgerock.opendj.util.ReferenceCountedObject;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.*;
import static org.forgerock.opendj.grizzly.GrizzlyUtils.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.TimeoutChecker.*;
import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
/**
 * LDAP connection factory implementation using Grizzly for transport.
 */
@@ -78,12 +73,11 @@
     * Adapts a Grizzly connection completion handler to an LDAP connection promise.
     */
    @SuppressWarnings("rawtypes")
    private final class CompletionHandlerAdapter implements
            CompletionHandler<org.glassfish.grizzly.Connection>, TimeoutEventListener {
        private final PromiseImpl<Connection, LdapException> promise;
    private final class CompletionHandlerAdapter implements CompletionHandler<Connection>, TimeoutEventListener {
        private final PromiseImpl<LDAPConnectionImpl, LdapException> promise;
        private final long timeoutEndTime;
        private CompletionHandlerAdapter(final PromiseImpl<Connection, LdapException> promise) {
        private CompletionHandlerAdapter(final PromiseImpl<LDAPConnectionImpl, LdapException> promise) {
            this.promise = promise;
            final long timeoutMS = getTimeout();
            this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0;
@@ -96,63 +90,13 @@
        }
        @Override
        public void completed(final org.glassfish.grizzly.Connection result) {
        public void completed(final Connection result) {
            // Adapt the connection.
            final GrizzlyLDAPConnection connection = adaptConnection(result);
            // Plain connection.
            if (options.get(SSL_CONTEXT) == null) {
                thenOnResult(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);
            timeoutChecker.get().removeListener(this);
            if (!promise.tryHandleResult(connection)) {
                // The connection has been either cancelled or it has timed out.
                connection.close();
                return;
            }
            List<String> protocols = options.get(ENABLED_PROTOCOLS);
            List<String> suites = options.get(ENABLED_CIPHER_SUITES);
            if (options.get(USE_STARTTLS)) {
                // Chain StartTLS extended request.
                final StartTLSExtendedRequest startTLS =
                        Requests.newStartTLSExtendedRequest(options.get(SSL_CONTEXT));
                startTLS.addEnabledCipherSuite(suites.toArray(new String[suites.size()]));
                startTLS.addEnabledProtocol(protocols.toArray(new String[protocols.size()]));
                connection.extendedRequestAsync(startTLS).thenOnResult(new ResultHandler<ExtendedResult>() {
                    @Override
                    public void handleResult(final ExtendedResult result) {
                        thenOnResult(connection);
                    }
                }).thenOnException(new ExceptionHandler<LdapException>() {
                    @Override
                    public void handleException(final LdapException error) {
                        onException(connection, error);
                    }
                });
            } else {
                // Install SSL/TLS layer.
                try {
                    connection.startTLS(options.get(SSL_CONTEXT), protocols, suites,
                        new EmptyCompletionHandler<SSLEngine>() {
                            @Override
                            public void completed(final SSLEngine result) {
                                thenOnResult(connection);
                            }
                            @Override
                            public void failed(final Throwable throwable) {
                                onException(connection, throwable);
                            }
                        });
                } catch (final IOException e) {
                    onException(connection, e);
                }
            }
        }
@@ -165,12 +109,11 @@
        }
        @Override
        public void updated(final org.glassfish.grizzly.Connection result) {
        public void updated(final Connection result) {
            // Ignore this.
        }
        private GrizzlyLDAPConnection adaptConnection(
                final org.glassfish.grizzly.Connection<?> connection) {
        private GrizzlyLDAPConnection adaptConnection(final Connection<?> connection) {
            configureConnection(connection, logger, options);
            final GrizzlyLDAPConnection ldapConnection =
@@ -184,7 +127,6 @@
            if (!(t instanceof LdapException) && t instanceof ExecutionException) {
                t = t.getCause() != null ? t.getCause() : t;
            }
            if (t instanceof LdapException) {
                return (LdapException) t;
            } else {
@@ -192,21 +134,6 @@
            }
        }
        private void onException(final GrizzlyLDAPConnection connection, final Throwable t) {
            // Abort connection attempt due to error.
            timeoutChecker.get().removeListener(this);
            promise.handleException(adaptConnectionException(t));
            connection.close();
        }
        private void thenOnResult(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
        public long handleTimeout(final long currentTime) {
            if (timeoutEndTime == 0) {
@@ -222,7 +149,8 @@
        @Override
        public long getTimeout() {
            return options.get(CONNECT_TIMEOUT_IN_MILLISECONDS);
            final Duration duration = options.get(CONNECT_TIMEOUT);
            return duration.isUnlimited() ? 0L : duration.to(TimeUnit.MILLISECONDS);
        }
    }
@@ -245,48 +173,32 @@
    private final AtomicBoolean isClosed = new AtomicBoolean();
    private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
    private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER
            .acquire();
    private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER.acquire();
    /**
     * Creates a new LDAP connection factory based on Grizzly which can be used
     * to create connections to the Directory Server at the provided host and
     * port address using provided connection options.
     *  @param host
     *            The hostname of the Directory Server to connect to.
     * Grizzly TCP Transport NIO implementation to use for connections. If {@code null}, default transport will be
     * used.
     */
    public static final Option<TCPNIOTransport> GRIZZLY_TRANSPORT = Option.of(TCPNIOTransport.class, null);
    /**
     * Creates a new LDAP connection factory based on Grizzly which can be used to create connections to the Directory
     * Server at the provided host and port address using provided connection options.
     *
     * @param host
     *         The hostname of the Directory Server to connect to.
     * @param port
     *            The port number of the Directory Server to connect to.
     *         The port number of the Directory Server to connect to.
     * @param options
     *            The LDAP connection options to use when creating connections.
     *         The LDAP connection options to use when creating connections.
     */
    public GrizzlyLDAPConnectionFactory(final String host, final int port, final Options options) {
        this(host, port, options, null);
    }
    /**
     * Creates a new LDAP connection factory based on Grizzly which can be used
     * to create connections to the Directory Server at the provided host and
     * port address using provided connection options and provided TCP
     * transport.
     *  @param host
     *            The hostname of the Directory Server to connect to.
     * @param port
     *            The port number of the Directory Server to connect to.
     * @param options
 *            The LDAP connection options to use when creating connections.
     * @param transport
*            Grizzly TCP Transport NIO implementation to use for
*            connections. If {@code null}, default transport will be used.
     */
    public GrizzlyLDAPConnectionFactory(final String host, final int port, final Options options,
                                        TCPNIOTransport transport) {
        this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
        this.transport = DEFAULT_TRANSPORT.acquireIfNull(options.get(GRIZZLY_TRANSPORT));
        this.host = host;
        this.port = port;
        this.options = options;
        this.clientFilter = new LDAPClientFilter(options.get(DECODE_OPTIONS), 0);
        this.defaultFilterChain =
                buildFilterChain(this.transport.get().getProcessor(), clientFilter);
        this.clientFilter = new LDAPClientFilter(options.get(LDAP_DECODE_OPTIONS), 0);
        this.defaultFilterChain = buildFilterChain(this.transport.get().getProcessor(), clientFilter);
    }
    @Override
@@ -297,21 +209,12 @@
    }
    @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() {
    public Promise<LDAPConnectionImpl, LdapException> getConnectionAsync() {
        acquireTransportAndTimeoutChecker(); // Protect resources.
        final SocketConnectorHandler connectorHandler =
                TCPNIOConnectorHandler.builder(transport.get()).processor(defaultFilterChain)
                        .build();
        final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
        final SocketConnectorHandler connectorHandler = TCPNIOConnectorHandler.builder(transport.get())
                                                                              .processor(defaultFilterChain)
                                                                              .build();
        final PromiseImpl<LDAPConnectionImpl, LdapException> promise = PromiseImpl.create();
        connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise));
        return promise;
    }
@@ -331,11 +234,6 @@
        return port;
    }
    @Override
    public String toString() {
        return getClass().getSimpleName() + "(" + host + ':' + port + ')';
    }
    TimeoutChecker getTimeoutChecker() {
        return timeoutChecker.get();
    }
opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -106,12 +106,12 @@
        this.connectionFactory = factory;
        this.options = Options.copyOf(options);
        final LDAPServerFilter serverFilter =
                new LDAPServerFilter(this, options.get(DECODE_OPTIONS), options.get(MAX_REQUEST_SIZE_BYTES));
                new LDAPServerFilter(this, options.get(LDAP_DECODE_OPTIONS), options.get(REQUEST_MAX_SIZE_IN_BYTES));
        final FilterChain ldapChain =
                GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter);
        final TCPNIOBindingHandler bindingHandler =
                TCPNIOBindingHandler.builder(this.transport.get()).processor(ldapChain).build();
        this.serverConnection = bindingHandler.bind(address, options.get(BACKLOG));
        this.serverConnection = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG));
        /*
         * Get the socket address now, ensuring that the host is the same as the
opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
@@ -198,9 +198,9 @@
        final SocketChannel channel = (SocketChannel) ((TCPNIOConnection) connection).getChannel();
        final Socket socket = channel.socket();
        final boolean tcpNoDelay = options.get(TCP_NO_DELAY);
        final boolean keepAlive = options.get(KEEPALIVE);
        final boolean reuseAddress = options.get(REUSE_ADDRESS);
        final int linger = options.get(LINGER);
        final boolean keepAlive = options.get(SO_KEEPALIVE);
        final boolean reuseAddress = options.get(SO_REUSE_ADDRESS);
        final int linger = options.get(SO_LINGER_IN_SECONDS);
        try {
            socket.setTcpNoDelay(tcpNoDelay);
        } catch (final SocketException e) {
opendj-sdk/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -368,7 +368,7 @@
        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().get(DECODE_OPTIONS));
            final R decodedResponse = promise.decodeResult(result, conn.getLDAPOptions().get(LDAP_DECODE_OPTIONS));
            if (result.getResultCode() == ResultCode.SUCCESS
                    && promise.getRequest() instanceof StartTLSExtendedRequest) {
opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -27,8 +27,31 @@
package org.forgerock.opendj.grizzly;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.Connections.newFailoverLoadBalancer;
import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool;
import static org.forgerock.opendj.ldap.Connections.newRoundRobinLoadBalancer;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import static org.forgerock.opendj.ldap.TestCaseUtils.getServerSocketAddress;
import static org.forgerock.opendj.ldap.requests.Requests.newCRAMMD5SASLBindRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newDigestMD5SASLBindRequest;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise;
import static org.forgerock.util.Options.defaultOptions;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
@@ -46,18 +69,16 @@
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.LDAPServer;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.MockConnectionEventListener;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SdkTestCase;
import org.forgerock.opendj.ldap.SearchScope;
@@ -66,9 +87,11 @@
import org.forgerock.opendj.ldap.TestCaseUtils;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CRAMMD5SASLBindRequest;
import org.forgerock.opendj.ldap.requests.DigestMD5SASLBindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.SimpleBindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
@@ -86,16 +109,6 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
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.LDAPConnectionFactory.ENABLED_CIPHER_SUITES;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
/**
 * Tests the {@code ConnectionFactory} classes.
 */
@@ -147,54 +160,59 @@
            "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "(objectclass=*)", "cn");
        InetSocketAddress serverAddress = getServerSocketAddress();
        factories[0][0] =
                Connections.newHeartBeatConnectionFactory(new LDAPConnectionFactory(
                        serverAddress.getHostName(), serverAddress.getPort()),
                        1000, 2000, TimeUnit.MILLISECONDS, request);
        factories[0][0] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
                                                    defaultOptions()
                                                           .set(HEARTBEAT_ENABLED, true)
                                                           .set(HEARTBEAT_INTERVAL, duration("1000 ms"))
                                                           .set(HEARTBEAT_TIMEOUT, duration("2000 ms"))
                                                           .set(HEARTBEAT_SEARCH_REQUEST, request));
        // InternalConnectionFactory
        factories[1][0] = Connections.newInternalConnectionFactory(LDAPServer.getInstance(), null);
        // AuthenticatedConnectionFactory
        factories[2][0] =
                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
                                serverAddress.getHostName(), serverAddress.getPort()),
                        Requests.newSimpleBindRequest("", new char[0]));
        final SimpleBindRequest anon = newSimpleBindRequest("", new char[0]);
        factories[2][0] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
                                                    defaultOptions().set(AUTHN_BIND_REQUEST, anon));
        // AuthenticatedConnectionFactory with multi-stage SASL
        factories[3][0] =
                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
                                serverAddress.getHostName(), serverAddress.getPort()),
                        Requests.newCRAMMD5SASLBindRequest("id:user", "password".toCharArray()));
        final CRAMMD5SASLBindRequest crammd5 = newCRAMMD5SASLBindRequest("id:user", "password".toCharArray());
        factories[3][0] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
                                                    defaultOptions().set(AUTHN_BIND_REQUEST, crammd5));
        // LDAPConnectionFactory with default options
        factories[4][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort());
        // LDAPConnectionFactory with startTLS
        SSLContext sslContext =
                new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext();
        Options options = Options.defaultOptions().set(ENABLED_CIPHER_SUITES,
            new ArrayList<String>(Arrays.asList(
                "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
                "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
                "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
                "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] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(), options);
        SSLContext sslContext = new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext();
        final Options startTlsOptions = defaultOptions()
                                   .set(SSL_CONTEXT, sslContext)
                                   .set(SSL_USE_STARTTLS, true)
                                   .set(SSL_ENABLED_CIPHER_SUITES,
                                        asList("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
                                                      "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
                                                      "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
                                                      "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] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
                                                    startTlsOptions);
        // 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] =
                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
                        serverAddress.getHostName(), serverAddress.getPort(), options),
                        Requests.newDigestMD5SASLBindRequest(
                            "id:user", "password".toCharArray()).setCipher(
                                DigestMD5SASLBindRequest.CIPHER_LOW));
        final DigestMD5SASLBindRequest digestmd5 = newDigestMD5SASLBindRequest("id:user", "password".toCharArray())
                .setCipher(DigestMD5SASLBindRequest.CIPHER_LOW);
        factories[6][0] = new LDAPConnectionFactory(serverAddress.getHostName(),
                                                    serverAddress.getPort(),
                                                    Options.copyOf(startTlsOptions).set(AUTHN_BIND_REQUEST, digestmd5));
        // Connection pool and load balancing tests.
        InetSocketAddress offlineSocketAddress1 = findFreeSocketAddress();
@@ -213,7 +231,7 @@
                                serverAddress.getPort()), "online");
        // Connection pools.
        factories[7][0] = Connections.newFixedConnectionPool(onlineServer, 10);
        factories[7][0] = newFixedConnectionPool(onlineServer, 10);
        // Round robin.
        factories[8][0] =
@@ -251,7 +269,7 @@
                                offlineServer1, 10), Connections.newFixedConnectionPool(
                                onlineServer, 10))));
        factories[20][0] = Connections.newFixedConnectionPool(onlineServer, 10);
        factories[20][0] = newFixedConnectionPool(onlineServer, 10);
        return factories;
    }
@@ -316,14 +334,14 @@
    }
    /**
     * Verifies that LDAP connections take into consideration changes to the
     * default schema post creation. See OPENDJ-159.
     * <p>
     * This test is disabled because it cannot be run in parallel with rest of
     * the test suite, because it modifies the global default schema.
     * Verifies that LDAP connections take into consideration changes to the default schema post creation. See
     * OPENDJ-159.
     * <p/>
     * This test is disabled because it cannot be run in parallel with rest of the test suite, because it modifies the
     * global default schema.
     *
     * @throws Exception
     *             If an unexpected error occurred.
     *         If an unexpected error occurred.
     */
    @Test(enabled = false)
    public void testSchemaUsage() throws Exception {
@@ -366,7 +384,7 @@
     * Tests connection pool closure.
     *
     * @throws Exception
     *             If an unexpected exception occurred.
     *         If an unexpected exception occurred.
     */
    @Test
    public void testConnectionPoolClose() throws Exception {
@@ -405,7 +423,7 @@
            }
        });
        ConnectionPool pool = Connections.newFixedConnectionPool(mockFactory, size);
        ConnectionPool pool = newFixedConnectionPool(mockFactory, size);
        Connection[] pooledConnections = new Connection[size];
        for (int i = 0; i < size; i++) {
            pooledConnections[i] = pool.getConnection();
opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -74,6 +74,7 @@
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
@@ -108,7 +109,7 @@
    private final LDAPListener server = createServer();
    private final InetSocketAddress socketAddress = server.getSocketAddress();
    public final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
            socketAddress.getPort(), Options.defaultOptions().set(TIMEOUT_IN_MILLISECONDS, 1L));
            socketAddress.getPort(), Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms")));
    private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10);
    private volatile ServerConnection<Integer> serverConnection;
@@ -123,7 +124,7 @@
    public void testClientSideConnectTimeout() throws Exception {
        // Use an non-local unreachable network address.
        final ConnectionFactory factory = new LDAPConnectionFactory("10.20.30.40", 1389,
            Options.defaultOptions().set(CONNECT_TIMEOUT_IN_MILLISECONDS, 1L));
            Options.defaultOptions().set(CONNECT_TIMEOUT, duration("1 ms")));
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                final PromiseImpl<LdapException, NeverThrowsException> promise = PromiseImpl.create();
@@ -317,8 +318,8 @@
    @Test
    public void testCreateLDAPConnectionFactoryWithCustomClassLoader() throws Exception {
        // test no exception is thrown, which means transport provider is correctly loaded
        Options options =
                Options.defaultOptions().set(PROVIDER_CLASS_LOADER, Thread.currentThread().getContextClassLoader());
        Options options = Options.defaultOptions()
                                 .set(TRANSPORT_PROVIDER_CLASS_LOADER, Thread.currentThread().getContextClassLoader());
        InetSocketAddress socketAddress = findFreeSocketAddress();
        LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
                socketAddress.getPort(), options);
opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
@@ -27,17 +27,16 @@
package org.forgerock.opendj.grizzly;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.util.time.Duration.duration;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.TIMEOUT_IN_MILLISECONDS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.REQUEST_TIMEOUT;
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.RequestHandler;
import org.forgerock.opendj.ldap.ResultCode;
@@ -93,10 +92,11 @@
         * Use a very long time out in order to prevent the timeout thread from
         * triggering the timeout.
         */
        LDAPConnectionFactory factory = new LDAPConnectionFactory(address.getHostName(),
                address.getPort(), Options.defaultOptions().set(
                    TIMEOUT_IN_MILLISECONDS, TimeUnit.SECONDS.toMillis((long) 100)));
        GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnection();
        GrizzlyLDAPConnectionFactory factory = new GrizzlyLDAPConnectionFactory(address.getHostName(),
                                                                  address.getPort(),
                                                                  Options.defaultOptions()
                                                                         .set(REQUEST_TIMEOUT, duration("100 ms")));
        GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnectionAsync().getOrThrow();
        try {
            SearchRequest request =
                    Requests.newSearchRequest("dc=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
@@ -106,7 +106,7 @@
            SearchResultHandler searchHandler = mock(SearchResultHandler.class);
            @SuppressWarnings("unchecked")
            ExceptionHandler<LdapException> exceptionHandler = mock(ExceptionHandler.class);
            connection.searchAsync(request, searchHandler).thenOnException(exceptionHandler);
            connection.searchAsync(request, null, searchHandler).thenOnException(exceptionHandler);
            // Pass in a time which is guaranteed to trigger expiration.
            connection.handleTimeout(System.currentTimeMillis() + 1000000);
opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -74,9 +74,11 @@
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.LDAPListener.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.util.Options.defaultOptions;
import static org.mockito.Mockito.*;
/**
@@ -242,8 +244,8 @@
    @Test
    public void testCreateLDAPListenerWithCustomClassLoader() throws Exception {
        // test no exception is thrown, which means transport provider is correctly loaded
        Options options = Options.defaultOptions().set(PROVIDER_CLASS_LOADER,
                Thread.currentThread().getContextClassLoader());
        Options options = defaultOptions().set(TRANSPORT_PROVIDER_CLASS_LOADER,
                                                       Thread.currentThread().getContextClassLoader());
        LDAPListener listener = new LDAPListener(findFreeSocketAddress(),
                mock(ServerConnectionFactory.class), options);
        listener.close();
@@ -256,7 +258,7 @@
    @Test(expectedExceptions = { ProviderNotFoundException.class },
        expectedExceptionsMessageRegExp = "^The requested provider 'unknown' .*")
    public void testCreateLDAPListenerFailureProviderNotFound() throws Exception {
        Options options = Options.defaultOptions().set(TRANSPORT_PROVIDER, "unknown");
        Options options = defaultOptions().set(TRANSPORT_PROVIDER, "unknown");
        LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options);
        listener.close();
    }
@@ -644,7 +646,7 @@
        final MockServerConnection serverConnection = new MockServerConnection();
        final MockServerConnectionFactory factory =
                new MockServerConnectionFactory(serverConnection);
        final Options options = Options.defaultOptions().set(MAX_REQUEST_SIZE_BYTES, 2048);
        final Options options = defaultOptions().set(REQUEST_MAX_SIZE_IN_BYTES, 2048);
        final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options);
        Connection connection = null;
opendj-sdk/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
@@ -32,7 +32,7 @@
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.util.Options;
import org.glassfish.grizzly.memory.HeapMemoryManager;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.DECODE_OPTIONS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS;
/**
 * Tests for LDAPWriter / LDAPReader classes using specific implementations of
@@ -47,7 +47,7 @@
    @Override
    protected LDAPReader<? extends ASN1Reader> getLDAPReader() {
        return GrizzlyUtils.createReader(Options.defaultOptions().get(DECODE_OPTIONS), 0, new HeapMemoryManager());
        return GrizzlyUtils.createReader(Options.defaultOptions().get(LDAP_DECODE_OPTIONS), 0, new HeapMemoryManager());
    }
    @Override
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
@@ -27,7 +27,10 @@
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.AUTHN_BIND_REQUEST;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.HEARTBEAT_ENABLED;
import static org.forgerock.opendj.ldap.LDAPListener.*;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import java.io.IOException;
import java.util.LinkedList;
@@ -43,7 +46,7 @@
import org.forgerock.opendj.ldap.RequestHandlerFactory;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.util.Options;
/**
@@ -90,19 +93,25 @@
        // Create load balancer.
        // --- JCite pools ---
        final List<ConnectionFactory> factories = new LinkedList<>();
        final BindRequest bindRequest = newSimpleBindRequest(proxyDN, proxyPassword.toCharArray());
        final Options factoryOptions = Options.defaultOptions()
                                              .set(HEARTBEAT_ENABLED, true)
                                              .set(AUTHN_BIND_REQUEST, bindRequest);
        final List<ConnectionFactory> bindFactories = new LinkedList<>();
        final Options bindFactoryOptions = Options.defaultOptions().set(HEARTBEAT_ENABLED, true);
        for (int i = 4; i < args.length; i += 2) {
            final String remoteAddress = args[i];
            final int remotePort = Integer.parseInt(args[i + 1]);
            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))));
            factories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
                                                                                        remotePort,
                                                                                        factoryOptions)));
            bindFactories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
                                                                                            remotePort,
                                                                                            bindFactoryOptions)));
        }
        // --- JCite pools ---
@@ -135,7 +144,7 @@
        // --- JCite listener ---
        // Create listener.
        final Options options = Options.defaultOptions().set(BACKLOG, 4096);
        final Options options = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
        LDAPListener listener = null;
        try {
            listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
@@ -27,7 +27,9 @@
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.LDAPListener.*;
import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool;
import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import java.io.IOException;
import java.util.HashSet;
@@ -40,17 +42,17 @@
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.LdapException;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.Modification;
import org.forgerock.opendj.ldap.RequestContext;
import org.forgerock.opendj.ldap.RequestHandler;
import org.forgerock.opendj.ldap.RequestHandlerFactory;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.controls.Control;
@@ -397,13 +399,14 @@
        final int remotePort = Integer.parseInt(args[5]);
        // Create connection factories.
        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));
        final Options factoryOptions = Options.defaultOptions()
                                   .set(LDAPConnectionFactory.AUTHN_BIND_REQUEST,
                                        newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()));
        final ConnectionFactory factory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
                                                                                            remotePort,
                                                                                            factoryOptions));
        final ConnectionFactory bindFactory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
                                                                                                remotePort));
        /*
         * Create a server connection adapter which will create a new proxy
@@ -423,10 +426,10 @@
                Connections.newServerConnectionFactory(proxyFactory);
        // Create listener.
        final Options options = Options.defaultOptions().set(BACKLOG, 4096);
        final Options listenerOptions = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
        LDAPListener listener = null;
        try {
            listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
            listener = new LDAPListener(localAddress, localPort, connectionHandler, listenerOptions);
            System.out.println("Press any key to stop the server...");
            System.in.read();
        } catch (final IOException e) {
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
@@ -32,7 +32,7 @@
 */
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.USE_STARTTLS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
@@ -129,7 +129,7 @@
        SSLContext sslContext =
                new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext();
        options.set(SSL_CONTEXT, sslContext);
        options.set(USE_STARTTLS, true);
        options.set(SSL_USE_STARTTLS, true);
        return options;
    }
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -27,7 +27,7 @@
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.LDAPListener.BACKLOG;
import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG;
import java.io.FileInputStream;
import java.io.IOException;
@@ -103,7 +103,7 @@
        // Create listener.
        LDAPListener listener = null;
        try {
            final Options options = Options.defaultOptions().set(BACKLOG, 4096);
            final Options options = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
            if (keyStoreFileName != null) {
                // Configure SSL/TLS and enable it when connections are
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
@@ -153,7 +153,7 @@
            options.set(SSL_CONTEXT, sslContext);
        }
        options.set(USE_STARTTLS, useStartTLS);
        options.set(SSL_USE_STARTTLS, useStartTLS);
        return options;
    }
@@ -208,7 +208,7 @@
                new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
                        .getSSLContext();
        options.set(SSL_CONTEXT, sslContext);
        options.set(USE_STARTTLS, useStartTLS);
        options.set(SSL_USE_STARTTLS, useStartTLS);
        return options;
    }
    // --- JCite trust all ---
opendj-sdk/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuthAsync.java
@@ -26,8 +26,9 @@
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.TrustManagers.checkHostName;
import static org.forgerock.util.Utils.closeSilently;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.USE_STARTTLS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
import org.forgerock.opendj.ldap.Connection;
@@ -158,10 +159,9 @@
                                           final String storepass) {
        Options options = Options.defaultOptions();
        if (useSSL || useStartTLS) {
            TrustManager trustManager = null;
            try {
                trustManager = TrustManagers.checkValidityDates(
                        TrustManagers.checkHostName(hostname,
                TrustManager trustManager = TrustManagers.checkValidityDates(
                        checkHostName(hostname,
                                TrustManagers.checkUsingTrustStore(
                                        truststore, storepass.toCharArray(), null)));
                if (trustManager != null) {
@@ -169,11 +169,12 @@
                            .setTrustManager(trustManager).getSSLContext();
                    options.set(SSL_CONTEXT, sslContext);
                }
                options.set(SSL_USE_STARTTLS, useStartTLS);
            } catch (Exception e) {
                System.err.println(e.getMessage());
                System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
                return null;
            }
            options.set(USE_STARTTLS, useStartTLS);
        }
        return options;
    }
@@ -191,18 +192,19 @@
     * @return SSL context options to trust all certificates without checking.
     */
    private static Options getTrustAllOptions() {
        Options options = Options.defaultOptions();
        try {
            Options options = Options.defaultOptions();
            SSLContext sslContext =
                    new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
                            .getSSLContext();
            options.set(SSL_CONTEXT, sslContext);
            options.set(USE_STARTTLS, useStartTLS);
            options.set(SSL_USE_STARTTLS, useStartTLS);
            return options;
        } catch (GeneralSecurityException e) {
            System.err.println(e.getMessage());
            System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
            return null;
        }
        return options;
    }
    private static String  host;
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AddRate.java
@@ -501,6 +501,7 @@
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            runner.setBindRequest(connectionFactoryProvider.getBindRequest());
            runner.validate(deleteMode, deleteSizeThreshold, deleteAgeThreshold, noPurgeArgument);
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/AuthRate.java
@@ -202,6 +202,7 @@
                    break;
                }
                final BindRequest bindRequest = getBindRequest();
                if (bindRequest instanceof SimpleBindRequest) {
                    final SimpleBindRequest o = (SimpleBindRequest) bindRequest;
                    if (br == null) {
@@ -315,7 +316,6 @@
        private SearchScope scope;
        private DereferenceAliasesPolicy dereferencesAliasesPolicy;
        private String[] attributes;
        private BindRequest bindRequest;
        private int invalidCredPercent;
        private BindPerformanceRunner(final PerformanceRunnerOptions options)
@@ -479,11 +479,11 @@
                return 0;
            }
            connectionFactory = connectionFactoryProvider.getConnectionFactory();
            connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory();
            runner.setBindRequest(connectionFactoryProvider.getBindRequest());
            runner.validate();
            runner.bindRequest = connectionFactoryProvider.getBindRequest();
            if (runner.bindRequest == null) {
            if (runner.getBindRequest() == null) {
                throw new ArgumentException(LocalizableMessage
                        .raw("Authentication information must be provided to use this tool"));
            }
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPCompare.java
@@ -30,6 +30,8 @@
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import static com.forgerock.opendj.cli.Utils.readBytesFromFile;
import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
import static com.forgerock.opendj.ldap.tools.Utils.printPasswordPolicyResults;
import static org.forgerock.util.Utils.closeSilently;
import java.io.BufferedReader;
@@ -52,6 +54,7 @@
import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CompareRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.Result;
@@ -149,6 +152,7 @@
        argParser.setShortToolDescription(REF_SHORT_DESC_LDAPCOMPARE.get());
        ConnectionFactoryProvider connectionFactoryProvider;
        ConnectionFactory connectionFactory;
        BindRequest bindRequest;
        BooleanArgument continueOnError;
        BooleanArgument noop;
@@ -224,7 +228,8 @@
                return 0;
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory();
            bindRequest = connectionFactoryProvider.getBindRequest();
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
@@ -359,16 +364,18 @@
        }
        Connection connection = null;
        if (!noop.isPresent()) {
            try {
                connection = connectionFactory.getConnection();
            } catch (final LdapException ere) {
                errPrintln(LocalizableMessage.raw(ere.getMessage()));
                return ere.getResult().getResultCode().intValue();
            }
        }
        try {
            if (!noop.isPresent()) {
                try {
                    connection = connectionFactory.getConnection();
                    if (bindRequest != null) {
                        printPasswordPolicyResults(this, connection.bind(bindRequest));
                    }
                } catch (final LdapException ere) {
                    return printErrorMessage(this, ere);
                }
            }
            int result;
            if (rdr == null) {
                for (final String dn : dnStrings) {
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
@@ -30,6 +30,7 @@
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
import static com.forgerock.opendj.ldap.tools.Utils.printPasswordPolicyResults;
import static org.forgerock.util.Utils.closeSilently;
import java.io.FileInputStream;
@@ -56,6 +57,7 @@
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;
@@ -94,7 +96,7 @@
                    printResult(opType, change.getName().toString(), r);
                    return r.getResultCode().intValue();
                } catch (final LdapException ere) {
                    return Utils.printErrorMessage(LDAPModify.this, ere);
                    return printErrorMessage(LDAPModify.this, ere);
                }
            }
            return ResultCode.SUCCESS.intValue();
@@ -130,7 +132,7 @@
                    printResult(opType, change.getName().toString(), r);
                    return r.getResultCode().intValue();
                } catch (final LdapException ere) {
                    return Utils.printErrorMessage(LDAPModify.this, ere);
                    return printErrorMessage(LDAPModify.this, ere);
                }
            }
            return ResultCode.SUCCESS.intValue();
@@ -148,7 +150,7 @@
                    printResult(opType, change.getName().toString(), r);
                    return r.getResultCode().intValue();
                } catch (final LdapException ere) {
                    return Utils.printErrorMessage(LDAPModify.this, ere);
                    return printErrorMessage(LDAPModify.this, ere);
                }
            }
            return ResultCode.SUCCESS.intValue();
@@ -253,6 +255,7 @@
        argParser.setShortToolDescription(REF_SHORT_DESC_LDAPMODIFY.get());
        ConnectionFactoryProvider connectionFactoryProvider;
        ConnectionFactory connectionFactory;
        BindRequest bindRequest;
        BooleanArgument continueOnError;
        // TODO: Remove this due to new LDIF reader api?
@@ -367,7 +370,8 @@
                return 0;
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory();
            bindRequest = connectionFactoryProvider.getBindRequest();
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
@@ -384,12 +388,6 @@
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
        }
        // modifyOptions.setShowOperations(noop.isPresent());
        // modifyOptions.setVerbose(verbose.isPresent());
        // modifyOptions.setContinueOnError(continueOnError.isPresent());
        // modifyOptions.setEncoding(encodingStr.getValue());
        // modifyOptions.setDefaultAdd(defaultAdd.isPresent());
        controls = new LinkedList<>();
        if (controlStr.isPresent()) {
            for (final String ctrlString : controlStr.getValues()) {
@@ -453,20 +451,22 @@
            controls.add(control);
        }
        if (!noop.isPresent()) {
            try {
                connection = connectionFactory.getConnection();
            } catch (final LdapException ere) {
                return Utils.printErrorMessage(this, ere);
            }
        }
        Utils.printPasswordPolicyResults(this, connection);
        writer = new LDIFEntryWriter(getOutputStream());
        final VisitorImpl visitor = new VisitorImpl();
        ChangeRecordReader reader = null;
        try {
            if (!noop.isPresent()) {
                try {
                    connection = connectionFactory.getConnection();
                    if (bindRequest != null) {
                        printPasswordPolicyResults(this, connection.bind(bindRequest));
                    }
                } catch (final LdapException ere) {
                    return printErrorMessage(this, ere);
                }
            }
            if (filename.isPresent()) {
                try {
                    reader = new LDIFChangeRecordReader(new FileInputStream(filename.getValue()));
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
@@ -63,6 +63,7 @@
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;
@@ -83,6 +84,8 @@
import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
import com.forgerock.opendj.util.StaticUtils;
import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
import static com.forgerock.opendj.ldap.tools.Utils.printPasswordPolicyResults;
import static org.forgerock.util.Utils.*;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
@@ -241,6 +244,7 @@
        argParser.setShortToolDescription(REF_SHORT_DESC_LDAPSEARCH.get());
        ConnectionFactoryProvider connectionFactoryProvider;
        ConnectionFactory connectionFactory;
        BindRequest bindRequest;
        BooleanArgument countEntries;
        BooleanArgument dontWrap;
@@ -444,7 +448,8 @@
                return 0;
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory();
            bindRequest = connectionFactoryProvider.getBindRequest();
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
            return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
@@ -823,16 +828,13 @@
            return 0;
        }
        Connection connection;
        Connection connection = null;
        try {
            connection = connectionFactory.getConnection();
        } catch (final LdapException ere) {
            return Utils.printErrorMessage(this, ere);
        }
            if (bindRequest != null) {
                printPasswordPolicyResults(this, connection.bind(bindRequest));
            }
        Utils.printPasswordPolicyResults(this, connection);
        try {
            int filterIndex = 0;
            ldifWriter = new LDIFEntryWriter(getOutputStream()).setWrapColumn(wrapColumn);
            final LDAPSearchResultHandler resultHandler = new LDAPSearchResultHandler();
@@ -920,7 +922,7 @@
                println();
            }
        } catch (final LdapException ere) {
            return Utils.printErrorMessage(this, ere);
            return printErrorMessage(this, ere);
        } finally {
            closeSilently(ldifWriter, connection);
        }
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/ModRate.java
@@ -228,6 +228,7 @@
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            runner.setBindRequest(connectionFactoryProvider.getBindRequest());
            runner.validate();
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -26,6 +26,7 @@
 */
package com.forgerock.opendj.ldap.tools;
import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
import static java.util.Locale.ENGLISH;
import static java.util.concurrent.TimeUnit.*;
@@ -54,13 +55,13 @@
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapResultHandler;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.util.promise.Promise;
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;
@@ -581,10 +582,9 @@
                    }
                } else {
                    connection = this.connection;
                    if (!noRebind && connection instanceof AuthenticatedConnection) {
                        final AuthenticatedConnection ac = (AuthenticatedConnection) connection;
                    if (!noRebind && bindRequest != null) {
                        try {
                            ac.rebindAsync().getOrThrow();
                            connection.bindAsync(bindRequest).getOrThrow();
                        } catch (final InterruptedException e) {
                            // Ignore and check stop requested
                            continue;
@@ -684,6 +684,7 @@
    private long maxDurationTime;
    private boolean isAsync;
    private boolean noRebind;
    private BindRequest bindRequest;
    private int statsInterval;
    private final IntegerArgument numThreadsArgument;
    private final IntegerArgument maxDurationArgument;
@@ -920,7 +921,8 @@
            stopRequested = true;
        } catch (final LdapException e) {
            stopRequested = true;
            app.println(LocalizableMessage.raw(e.getResult().getDiagnosticMessage()));
            printErrorMessage(app, e);
            return e.getResult().getResultCode().intValue();
        } finally {
            closeSilently(connections);
        }
@@ -928,6 +930,14 @@
        return 0;
    }
    void setBindRequest(final BindRequest request) {
        this.bindRequest = request;
    }
    BindRequest getBindRequest() {
        return bindRequest;
    }
    protected void joinAllWorkerThreads() throws InterruptedException {
        for (final Thread t : workerThreads) {
            t.join();
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunnerOptions.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2014 ForgeRock AS
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package com.forgerock.opendj.ldap.tools;
@@ -42,7 +42,6 @@
    private boolean supportsGeneratorArgument = true;
    PerformanceRunnerOptions(ArgumentParser argParser, ConsoleApplication app) {
        super();
        this.argParser = argParser;
        this.app = app;
    }
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/SearchRate.java
@@ -284,6 +284,7 @@
            }
            connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
            runner.setBindRequest(connectionFactoryProvider.getBindRequest());
            runner.validate();
        } catch (final ArgumentException ae) {
            argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
opendj-sdk/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
@@ -26,13 +26,12 @@
 */
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 static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
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;
@@ -51,7 +50,6 @@
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;
/**
@@ -196,84 +194,62 @@
        return ere.getResult().getResultCode().intValue();
    }
    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 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 BindResult result) {
        try {
            final AuthorizationIdentityResponseControl control = result.getControl(
                    AuthorizationIdentityResponseControl.DECODER, new DecodeOptions());
            if (control != null) {
                app.println(INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID()));
            }
        } catch (final DecodeException e) {
            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
        }
            try {
                final PasswordExpiredResponseControl control =
                        result.getControl(PasswordExpiredResponseControl.DECODER,
                                new DecodeOptions());
                if (control != null) {
                    final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
                    app.println(message);
                }
            } catch (final DecodeException e) {
                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
        try {
            final PasswordExpiredResponseControl control = result.getControl(PasswordExpiredResponseControl.DECODER,
                                                                             new DecodeOptions());
            if (control != null) {
                app.println(INFO_BIND_PASSWORD_EXPIRED.get());
            }
        } 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 PasswordExpiringResponseControl control = result.getControl(PasswordExpiringResponseControl.DECODER,
                                                                              new DecodeOptions());
            if (control != null) {
                final LocalizableMessage timeString = secondsToTimeString(control.getSecondsUntilExpiration());
                app.println(INFO_BIND_PASSWORD_EXPIRING.get(timeString));
            }
        } 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) {
        try {
            final PasswordPolicyResponseControl control = result.getControl(PasswordPolicyResponseControl.DECODER,
                                                                            new DecodeOptions());
            if (control != null) {
                final PasswordPolicyErrorType errorType = control.getErrorType();
                if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
                    app.println(INFO_BIND_PASSWORD_EXPIRED.get());
                } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) {
                    app.println(INFO_BIND_ACCOUNT_LOCKED.get());
                } 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);
                    }
                    app.println(INFO_BIND_MUST_CHANGE_PASSWORD.get());
                }
            } catch (final DecodeException e) {
                app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
                final PasswordPolicyWarningType warningType = control.getWarningType();
                if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
                    final LocalizableMessage timeString = secondsToTimeString(control.getWarningValue());
                    app.println(INFO_BIND_PASSWORD_EXPIRING.get(timeString));
                } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) {
                    app.println(INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue()));
                }
            }
        } catch (final DecodeException e) {
            app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
        }
    }
opendj-sdk/opendj-ldap-toolkit/src/test/java/com/forgerock/opendj/ldap/tools/ConnectionFactoryProviderTest.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2013-2014 ForgeRock AS.
 *      Copyright 2013-2015 ForgeRock AS.
 */
package com.forgerock.opendj.ldap.tools;
@@ -65,7 +65,7 @@
                .getCanonicalPath();
        argParser.parseArguments(new String[] { "--useStartTLS", "--trustStorePath", trustStorePath });
        ConnectionFactory factory = connectionFactoryProvider.getConnectionFactory();
        ConnectionFactory factory = connectionFactoryProvider.getUnauthenticatedConnectionFactory();
        assertThat(factory).isNotNull();
    }
opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/HttpAuthenticationFilter.java
@@ -15,6 +15,7 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.json.resource.ResourceException.newResourceException;
import static org.forgerock.services.context.SecurityContext.AUTHZID_DN;
import static org.forgerock.services.context.SecurityContext.AUTHZID_ID;
import static org.forgerock.opendj.ldap.Connections.uncloseable;
@@ -194,7 +195,7 @@
            final char[] password;
            if (headerUsername != null) {
                if (headerPassword == null || headerUsername.isEmpty() || headerPassword.isEmpty()) {
                    throw ResourceException.getException(401);
                    throw newResourceException(401);
                }
                username = headerUsername;
                password = headerPassword.toCharArray();
@@ -202,21 +203,21 @@
                final StringTokenizer st = new StringTokenizer(headerAuthorization);
                final String method = st.nextToken();
                if (method == null || !"BASIC".equalsIgnoreCase(method)) {
                    throw ResourceException.getException(401);
                    throw newResourceException(401);
                }
                final String b64Credentials = st.nextToken();
                if (b64Credentials == null) {
                    throw ResourceException.getException(401);
                    throw newResourceException(401);
                }
                final String credentials = ByteString.valueOfBase64(b64Credentials).toString();
                final String[] usernameAndPassword = credentials.split(":");
                if (usernameAndPassword.length != 2) {
                    throw ResourceException.getException(401);
                    throw newResourceException(401);
                }
                username = usernameAndPassword[0];
                password = usernameAndPassword[1].toCharArray();
            } else {
                throw ResourceException.getException(401);
                throw newResourceException(401);
            }
            // If we've got here then we have a username and password.
opendj-sdk/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -15,11 +15,13 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.json.resource.ResourceException.newResourceException;
import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
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 static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import java.io.IOException;
import java.security.GeneralSecurityException;
@@ -45,7 +47,6 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConnectionException;
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.Entry;
@@ -69,6 +70,7 @@
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.util.Options;
import org.forgerock.util.time.Duration;
/** Provides core factory methods and builders for constructing LDAP resource collections. */
public final class Rest2LDAP {
@@ -829,7 +831,7 @@
        } catch (final Throwable tmp) {
            resourceResultCode = ResourceException.INTERNAL_ERROR;
        }
        return ResourceException.getException(resourceResultCode, t.getMessage(), t);
        return newResourceException(resourceResultCode, t.getMessage(), t);
    }
    /**
@@ -976,36 +978,39 @@
    private static ConnectionFactory configureConnectionFactory(final JsonValue configuration,
                                                                final ClassLoader providerClassLoader) {
        final long heartBeatIntervalSeconds = configuration.get("heartBeatIntervalSeconds").defaultTo(30L).asLong();
        final Duration heartBeatInterval = new Duration(Math.max(heartBeatIntervalSeconds, 1L), TimeUnit.SECONDS);
        final long heartBeatTimeoutMillis = configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500L).asLong();
        final Duration heartBeatTimeout = new Duration(Math.max(heartBeatTimeoutMillis, 100L), TimeUnit.MILLISECONDS);
        final Options options = Options.defaultOptions()
                                       .set(TRANSPORT_PROVIDER_CLASS_LOADER, providerClassLoader)
                                       .set(HEARTBEAT_ENABLED, true)
                                       .set(HEARTBEAT_INTERVAL, heartBeatInterval)
                                       .set(HEARTBEAT_TIMEOUT, heartBeatTimeout)
        // Parse pool parameters,
        final int connectionPoolSize =
                Math.max(configuration.get("connectionPoolSize").defaultTo(10).asInteger(), 1);
        final int heartBeatIntervalSeconds =
                Math.max(configuration.get("heartBeatIntervalSeconds").defaultTo(30).asInteger(), 1);
        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");
                bindRequest =
                final BindRequest bindRequest =
                        Requests.newSimpleBindRequest(simple.get("bindDN").required().asString(),
                                simple.get("bindPassword").required().asString().toCharArray());
                options.set(AUTHN_BIND_REQUEST, bindRequest);
            } 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 Options options = Options.defaultOptions().set(PROVIDER_CLASS_LOADER, providerClassLoader);
        if (connectionSecurity != ConnectionSecurity.NONE) {
            try {
                // Configure SSL.
@@ -1033,8 +1038,8 @@
                    break;
                }
                options.set(SSL_CONTEXT, builder.getSSLContext());
                options.set(USE_STARTTLS,
                    connectionSecurity == ConnectionSecurity.STARTTLS);
                options.set(SSL_USE_STARTTLS,
                            connectionSecurity == ConnectionSecurity.STARTTLS);
            } catch (GeneralSecurityException | IOException e) {
                // Rethrow as unchecked exception.
                throw new IllegalArgumentException(e);
@@ -1046,25 +1051,17 @@
        if (!primaryLDAPServers.isList() || primaryLDAPServers.size() == 0) {
            throw new IllegalArgumentException("No primaryLDAPServers");
        }
        final ConnectionFactory primary =
                parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
                        heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
        final ConnectionFactory primary = parseLDAPServers(primaryLDAPServers, connectionPoolSize, options);
        // Parse secondary data center(s).
        final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
        final ConnectionFactory secondary;
        ConnectionFactory secondary = null;
        if (secondaryLDAPServers.isList()) {
            if (secondaryLDAPServers.size() > 0) {
                secondary =
                        parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
                                heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
            } else {
                secondary = null;
                secondary = parseLDAPServers(secondaryLDAPServers, connectionPoolSize, options);
            }
        } else if (!secondaryLDAPServers.isNull()) {
            throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
        } else {
            secondary = null;
        }
        // Create fail-over.
@@ -1102,28 +1099,17 @@
        }
    }
    private static ConnectionFactory parseLDAPServers(final JsonValue config,
            final BindRequest bindRequest, final int connectionPoolSize,
            final int heartBeatIntervalSeconds, final int heartBeatTimeoutMilliSeconds,
            final Options options) {
    private static ConnectionFactory parseLDAPServers(JsonValue config, int poolSize, Options options) {
        final List<ConnectionFactory> servers = new ArrayList<>(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 = new LDAPConnectionFactory(host, port, options);
            factory =
                    Connections.newHeartBeatConnectionFactory(factory,
                            heartBeatIntervalSeconds * 1000, heartBeatTimeoutMilliSeconds,
                            TimeUnit.MILLISECONDS);
            if (bindRequest != null) {
                factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
            final ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
            if (poolSize > 1) {
                servers.add(newCachedConnectionPool(factory, 0, poolSize, 60L, TimeUnit.SECONDS));
            } else {
                servers.add(factory);
            }
            if (connectionPoolSize > 1) {
                factory =
                        Connections.newCachedConnectionPool(factory, 0, connectionPoolSize, 60L,
                                TimeUnit.SECONDS);
            }
            servers.add(factory);
        }
        if (servers.size() > 1) {
            return Connections.newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers,