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

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