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

Matthew Swift
28.31.2014 ed08a89377a333c10202ead88d355e16bcb3a0fd
Backport fix for OPENDJ-1197: API is lacking functionality to specify TCP connect timeout

* added support for setting connect timeout in LDAPOptions
* added support for other common socket options: TCP nodelay, keepalive, linger, and reuseaddr
3 files added
11 files modified
1128 ■■■■ changed files
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java 74 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java 13 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java 61 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java 16 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java 8 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java 100 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java 56 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java 87 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java 285 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java 94 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java 297 ●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties 2 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java 2 ●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java 33 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java
New file
@@ -0,0 +1,74 @@
/*
 * 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 com.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEBUG_LOG;
import java.io.IOException;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.nio.transport.TCPNIOConnection;
/**
 * Common utility methods.
 */
final class GrizzlyUtils {
    static void configureConnection(final Connection<?> connection, final boolean tcpNoDelay,
            final boolean keepAlive, final boolean reuseAddress, final int linger) {
        /*
         * Test shows that its much faster with non block writes but risk
         * running out of memory if the server is slow.
         */
        connection.configureBlocking(true);
        // Configure socket options.
        final SocketChannel channel = (SocketChannel) ((TCPNIOConnection) connection).getChannel();
        setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, tcpNoDelay);
        setSocketOption(channel, StandardSocketOptions.SO_KEEPALIVE, keepAlive);
        setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, reuseAddress);
        setSocketOption(channel, StandardSocketOptions.SO_LINGER, linger);
    }
    private static <T> void setSocketOption(final SocketChannel channel,
            final SocketOption<T> option, final T value) {
        try {
            channel.setOption(option, value);
        } catch (final IOException e) {
            DEBUG_LOG.log(Level.FINE, "Unable to set " + option.name() + " to " + value
                    + " on client connection", e);
        }
    }
    // Prevent instantiation.
    private GrizzlyUtils() {
        // No implementation required.
    }
}
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
@@ -87,7 +87,8 @@
/**
 * LDAP connection implementation.
 */
final class LDAPConnection extends AbstractAsynchronousConnection implements Connection {
final class LDAPConnection extends AbstractAsynchronousConnection implements Connection,
        TimeoutEventListener {
    /**
     * A dummy SSL client engine configurator as SSLFilter only needs client
     * config. This prevents Grizzly from needlessly using JVM defaults which
@@ -385,6 +386,11 @@
    }
    @Override
    public long getTimeout() {
        return factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
    }
    @Override
    public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
            final ExtendedRequest<R> request,
            final IntermediateResponseHandler intermediateResponseHandler,
@@ -568,7 +574,8 @@
        return builder.toString();
    }
    long cancelExpiredRequests(final long currentTime) {
    @Override
    public long handleTimeout(final long currentTime) {
        final long timeout = factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
        if (timeout <= 0) {
            return 0;
@@ -701,7 +708,7 @@
            } finally {
                asn1Writer.recycle();
            }
            factory.getTimeoutChecker().removeConnection(this);
            factory.getTimeoutChecker().removeListener(this);
            connection.closeSilently();
            factory.releaseTransportAndTimeoutChecker();
        }
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java
@@ -28,7 +28,9 @@
package com.forgerock.opendj.ldap;
import static com.forgerock.opendj.ldap.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
import static com.forgerock.opendj.ldap.GrizzlyUtils.configureConnection;
import static com.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER;
import static org.forgerock.opendj.ldap.CoreMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static org.forgerock.opendj.ldap.ErrorResultException.*;
import java.io.IOException;
@@ -72,13 +74,16 @@
     */
    @SuppressWarnings("rawtypes")
    private final class CompletionHandlerAdapter implements
            CompletionHandler<org.glassfish.grizzly.Connection> {
            CompletionHandler<org.glassfish.grizzly.Connection>, TimeoutEventListener {
        private final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future;
        private final long timeoutEndTime;
        private CompletionHandlerAdapter(
                final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future) {
            this.future = future;
            final long timeoutMS = getTimeout();
            this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0;
            timeoutChecker.get().addListener(this);
        }
        @Override
@@ -99,10 +104,10 @@
            // Start TLS or install SSL layer asynchronously.
            // Give up immediately if the future has been cancelled.
            if (future.isCancelled()) {
            // Give up immediately if the future has been cancelled or timed out.
            if (future.isDone()) {
                timeoutChecker.get().removeListener(this);
                connection.close();
                releaseTransportAndTimeoutChecker();
                return;
            }
@@ -151,6 +156,7 @@
        @Override
        public void failed(final Throwable throwable) {
            // Adapt and forward.
            timeoutChecker.get().removeListener(this);
            future.handleErrorResult(adaptConnectionException(throwable));
            releaseTransportAndTimeoutChecker();
        }
@@ -161,16 +167,11 @@
        }
        private LDAPConnection adaptConnection(final org.glassfish.grizzly.Connection<?> connection) {
            /*
             * Test shows that its much faster with non block writes but risk
             * running out of memory if the server is slow.
             */
            connection.configureBlocking(true);
            configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(), options
                    .isReuseAddress(), options.getLinger());
            final LDAPConnection ldapConnection =
                    new LDAPConnection(connection, LDAPConnectionFactoryImpl.this);
            if (options.getTimeout(TimeUnit.MILLISECONDS) > 0) {
                timeoutChecker.get().addConnection(ldapConnection);
            }
            timeoutChecker.get().addListener(ldapConnection);
            clientFilter.registerConnection(connection, ldapConnection);
            return ldapConnection;
        }
@@ -189,20 +190,37 @@
        private void onFailure(final LDAPConnection connection, final Throwable t) {
            // Abort connection attempt due to error.
            connection.close();
            timeoutChecker.get().removeListener(this);
            future.handleErrorResult(adaptConnectionException(t));
            releaseTransportAndTimeoutChecker();
            connection.close();
        }
        private void onSuccess(final LDAPConnection connection) {
            future.handleResult(connection);
            // Close the connection if the future was cancelled.
            if (future.isCancelled()) {
            timeoutChecker.get().removeListener(this);
            if (!future.tryHandleResult(connection)) {
                // The connection has been either cancelled or it has timed out.
                connection.close();
                releaseTransportAndTimeoutChecker();
            }
        }
        @Override
        public long handleTimeout(final long currentTime) {
            if (timeoutEndTime == 0) {
                return 0;
            } else if (timeoutEndTime > currentTime) {
                return timeoutEndTime - currentTime;
            } else {
                future.handleErrorResult(newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
                        LDAP_CONNECTION_CONNECT_TIMEOUT.get(socketAddress.toString(), getTimeout())
                                .toString()));
                return 0;
            }
        }
        @Override
        public long getTimeout() {
            return options.getConnectTimeout(TimeUnit.MILLISECONDS);
        }
    }
    private final LDAPClientFilter clientFilter;
@@ -271,8 +289,7 @@
                        .build();
        final AsynchronousFutureResult<Connection, ResultHandler<? super Connection>> future =
                new AsynchronousFutureResult<Connection, ResultHandler<? super Connection>>(handler);
        final CompletionHandlerAdapter cha = new CompletionHandlerAdapter(future);
        connectorHandler.connect(socketAddress, cha);
        connectorHandler.connect(socketAddress, new CompletionHandlerAdapter(future));
        return future;
    }
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java
@@ -36,7 +36,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
@@ -60,6 +59,7 @@
    private final TCPNIOServerConnection serverConnection;
    private final AtomicBoolean isClosed = new AtomicBoolean();
    private final InetSocketAddress socketAddress;
    private final LDAPListenerOptions options;
    /**
     * Creates a new LDAP listener implementation which will listen for LDAP
@@ -81,12 +81,12 @@
            final LDAPListenerOptions options) throws IOException {
        this.transport = DEFAULT_TRANSPORT.acquireIfNull(options.getTCPNIOTransport());
        this.connectionFactory = factory;
        final DecodeOptions decodeOptions = new DecodeOptions(options.getDecodeOptions());
        this.options = new LDAPListenerOptions(options);
        final LDAPServerFilter serverFilter =
                new LDAPServerFilter(this, new LDAPReader(this.options.getDecodeOptions()),
                        this.options.getMaxRequestSize());
        this.defaultFilterChain =
                FilterChainBuilder.stateless().add(new TransportFilter()).add(
                        new LDAPServerFilter(this, new LDAPReader(decodeOptions), options
                                .getMaxRequestSize())).build();
                FilterChainBuilder.stateless().add(new TransportFilter()).add(serverFilter).build();
        final TCPNIOBindingHandler bindingHandler =
                TCPNIOBindingHandler.builder(transport.get()).processor(defaultFilterChain).build();
        this.serverConnection = bindingHandler.bind(address, options.getBacklog());
@@ -140,4 +140,8 @@
    FilterChain getDefaultFilterChain() {
        return defaultFilterChain;
    }
    LDAPListenerOptions getLDAPListenerOptions() {
        return options;
    }
}
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java
@@ -22,11 +22,12 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012-2013 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package com.forgerock.opendj.ldap;
import static com.forgerock.opendj.ldap.GrizzlyUtils.configureConnection;
import static com.forgerock.opendj.ldap.LDAPConstants.OID_NOTICE_OF_DISCONNECTION;
import java.io.IOException;
@@ -43,6 +44,7 @@
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
@@ -813,7 +815,9 @@
    @Override
    public NextAction handleAccept(final FilterChainContext ctx) throws IOException {
        final Connection<?> connection = ctx.getConnection();
        connection.configureBlocking(true);
        LDAPListenerOptions options = listener.getLDAPListenerOptions();
        configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(), options
                .isReuseAddress(), options.getLinger());
        try {
            final ClientContextImpl clientContext = new ClientContextImpl(connection);
            final ServerConnection<Integer> serverConn =
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java
@@ -22,9 +22,8 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 *      Portions copyright 2013-2014 ForgeRock AS.
 */
package com.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEBUG_LOG;
@@ -37,13 +36,17 @@
import com.forgerock.opendj.util.ReferenceCountedObject;
/**
 * Checks connection for pending requests that have timed out.
 * Checks {@code TimeoutEventListener listeners} for events that have timed out.
 * <p>
 * All listeners registered with the {@code #addListener()} method are called
 * back with {@code TimeoutEventListener#handleTimeout()} to be able to handle
 * the timeout.
 */
final class TimeoutChecker {
public final class TimeoutChecker {
    /**
     * Global reference counted instance.
     * Global reference on the timeout checker.
     */
    static final ReferenceCountedObject<TimeoutChecker> TIMEOUT_CHECKER =
    public static final ReferenceCountedObject<TimeoutChecker> TIMEOUT_CHECKER =
            new ReferenceCountedObject<TimeoutChecker>() {
                @Override
                protected void destroyInstance(final TimeoutChecker instance) {
@@ -62,11 +65,11 @@
    private final Object stateLock = new Object();
    /**
     * The connection set must be safe from CMEs because expiring requests can
     * cause the connection to be closed.
     * The listener set must be safe from CMEs. For example, if the listener is
     * a connection, expiring requests can cause the connection to be closed.
     */
    private final Set<LDAPConnection> connections =
            newSetFromMap(new ConcurrentHashMap<LDAPConnection, Boolean>());
    private final Set<TimeoutEventListener> listeners =
            newSetFromMap(new ConcurrentHashMap<TimeoutEventListener, Boolean>());
    /**
     * Used to signal thread shutdown.
@@ -74,48 +77,46 @@
    private volatile boolean shutdownRequested = false;
    /**
     * Used for signalling that new connections have been added while performing
     * timeout processing.
     * Contains the minimum delay for listeners which were added while the
     * timeout check was not sleeping (i.e. while it was processing listeners).
     */
    private volatile boolean pendingNewConnections = false;
    private volatile long pendingListenerMinDelay = Long.MAX_VALUE;
    private TimeoutChecker() {
        final Thread checkerThread = new Thread("OpenDJ LDAP SDK Connection Timeout Checker") {
        final Thread checkerThread = new Thread("OpenDJ LDAP SDK Timeout Checker") {
            @Override
            public void run() {
                DEBUG_LOG.fine("Timeout Checker Starting");
                while (!shutdownRequested) {
                    final long currentTime = System.currentTimeMillis();
                    long delay = 0;
                    /*
                     * New connections may be added during iteration and may be
                     * missed resulting in the timeout checker waiting longer
                     * than it should, or even forever (e.g. if the new
                     * connection is the first).
                     * New listeners may be added during iteration and may not
                     * be included in the computation of the new delay. This
                     * could potentially result in the timeout checker waiting
                     * longer than it should, or even forever (e.g. if the new
                     * listener is the first).
                     */
                    pendingNewConnections = false;
                    for (final LDAPConnection connection : connections) {
                    final long currentTime = System.currentTimeMillis();
                    long delay = Long.MAX_VALUE;
                    pendingListenerMinDelay = Long.MAX_VALUE;
                    for (final TimeoutEventListener listener : listeners) {
                        if (DEBUG_LOG.isLoggable(Level.FINER)) {
                            DEBUG_LOG.finer("Checking connection " + connection + " delay = "
                            DEBUG_LOG.finer("Checking listener " + listener + " delay = "
                                    + delay);
                        }
                        // May update the connections set.
                        final long newDelay = connection.cancelExpiredRequests(currentTime);
                        final long newDelay = listener.handleTimeout(currentTime);
                        if (newDelay > 0) {
                            if (delay > 0) {
                                delay = Math.min(newDelay, delay);
                            } else {
                                delay = newDelay;
                            }
                            delay = Math.min(newDelay, delay);
                        }
                    }
                    try {
                        synchronized (stateLock) {
                            if (shutdownRequested || pendingNewConnections) {
                                // Loop immediately.
                                pendingNewConnections = false;
                            // Include any pending listener delays.
                            delay = Math.min(pendingListenerMinDelay, delay);
                            if (shutdownRequested) {
                                // Stop immediately.
                                break;
                            } else if (delay <= 0) {
                                /*
                                 * If there is at least one connection then the
@@ -137,16 +138,35 @@
        checkerThread.start();
    }
    void addConnection(final LDAPConnection connection) {
        connections.add(connection);
        synchronized (stateLock) {
            pendingNewConnections = true;
            stateLock.notifyAll();
    /**
     * Registers a timeout event listener for timeout notification.
     *
     * @param listener
     *            The timeout event listener.
     */
    public void addListener(final TimeoutEventListener listener) {
        /*
         * Only add the listener if it has a non-zero timeout. This assumes that
         * the timeout is fixed.
         */
        final long timeout = listener.getTimeout();
        if (timeout > 0) {
            listeners.add(listener);
            synchronized (stateLock) {
                pendingListenerMinDelay = Math.min(pendingListenerMinDelay, timeout);
                stateLock.notifyAll();
            }
        }
    }
    void removeConnection(final LDAPConnection connection) {
        connections.remove(connection);
    /**
     * Deregisters a timeout event listener for timeout notification.
     *
     * @param listener
     *            The timeout event listener.
     */
    public void removeListener(final TimeoutEventListener listener) {
        listeners.remove(listener);
        // No need to signal.
    }
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java
New file
@@ -0,0 +1,56 @@
/*
 * 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 com.forgerock.opendj.ldap;
/**
 * Listener on timeout events.
 * <p>
 * The listener must register itself with a {@code TimeoutChecker} using
 * {@code TimeoutChecker#addListener()} method to be called back with
 * {@code #handleTimeout} method.
 * <p>
 * The listener must deregister itself using
 * {@code TimeoutChecker#removeListener()} to stop being called back.
 */
public interface TimeoutEventListener {
    /**
     * Handle a timeout event.
     *
     * @param currentTime
     *            Time to use as current time for any check.
     * @return The delay to wait before next timeout callback in milliseconds,
     *         or zero if this listener should no longer be notified.
     */
    long handleTimeout(final long currentTime);
    /**
     * Returns the timeout for this listener.
     *
     * @return The timeout in milliseconds.
     */
    long getTimeout();
}
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
@@ -22,6 +22,7 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2013-2014 ForgeRock AS.
 */
package com.forgerock.opendj.util;
@@ -161,34 +162,36 @@
            return getState() > 1;
        }
        void innerSetErrorResult(final ErrorResultException errorResult) {
            if (setStatePending()) {
                this.errorResult = errorResult;
                try {
                    // Invoke error result completion handler.
                    if (handler != null) {
                        handler.handleErrorResult(errorResult);
                    }
                } finally {
                    releaseShared(FAIL); // Publishes errorResult.
                }
        boolean innerSetErrorResult(final ErrorResultException errorResult) {
            if (!setStatePending()) {
                return false;
            }
            this.errorResult = errorResult;
            try {
                // Invoke error result completion handler.
                if (handler != null) {
                    handler.handleErrorResult(errorResult);
                }
            } finally {
                releaseShared(FAIL); // Publishes errorResult.
            }
            return true;
        }
        void innerSetResult(final M result) {
            if (setStatePending()) {
                this.result = result;
                try {
                    // Invoke result completion handler.
                    if (handler != null) {
                        handler.handleResult(result);
                    }
                } finally {
                    releaseShared(SUCCESS); // Publishes result.
                }
        boolean innerSetResult(final M result) {
            if (!setStatePending()) {
                return false;
            }
            this.result = result;
            try {
                // Invoke result completion handler.
                if (handler != null) {
                    handler.handleResult(result);
                }
            } finally {
                releaseShared(SUCCESS); // Publishes result.
            }
            return true;
        }
        private M get0() throws ErrorResultException {
@@ -323,6 +326,42 @@
    }
    /**
     * Attempts to set the error result associated with this future. If (i.e.
     * {@code isDone() == true}) then the error result will be ignored and
     * {@code false} will be returned, otherwise the result handler will be
     * invoked if one was provided and, on returning {@code true}, any threads
     * waiting on {@link #get} will be released and the provided error result
     * will be thrown.
     *
     * @param errorResult
     *            The error result.
     * @return {@code false} if this future has already been completed, either
     *         due to normal termination, an exception, or cancellation (i.e.
     *         {@code isDone() == true}).
     */
    public final boolean tryHandleErrorResult(final ErrorResultException errorResult) {
        return sync.innerSetErrorResult(errorResult);
    }
    /**
     * Attempts to set the result associated with this future. If (i.e.
     * {@code isDone() == true}) then the result will be ignored and
     * {@code false} will be returned, otherwise the result handler will be
     * invoked if one was provided and, on returning {@code true}, any threads
     * waiting on {@link #get} will be released and the provided result will be
     * returned.
     *
     * @param result
     *            The result.
     * @return {@code false} if this future has already been completed, either
     *         due to normal termination, an exception, or cancellation (i.e.
     *         {@code isDone() == true}).
     */
    public final boolean tryHandleResult(final M result) {
        return sync.innerSetResult(result);
    }
    /**
     * {@inheritDoc}
     */
    @Override
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
New file
@@ -0,0 +1,285 @@
/*
 * 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;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import com.forgerock.opendj.util.Validator;
/**
 * Common options for LDAP clients and listeners.
 */
abstract class CommonLDAPOptions<T extends CommonLDAPOptions<T>> {
    // Default values for options taken from Java properties.
    private static final boolean DEFAULT_TCP_NO_DELAY;
    private static final boolean DEFAULT_REUSE_ADDRESS;
    private static final boolean DEFAULT_KEEPALIVE;
    private static final int DEFAULT_LINGER;
    static {
        DEFAULT_LINGER = getIntProperty("org.forgerock.opendj.io.linger", -1);
        DEFAULT_TCP_NO_DELAY = getBooleanProperty("org.forgerock.opendj.io.tcpNoDelay", true);
        DEFAULT_REUSE_ADDRESS = getBooleanProperty("org.forgerock.opendj.io.reuseAddress", true);
        DEFAULT_KEEPALIVE = getBooleanProperty("org.forgerock.opendj.io.keepAlive", true);
    }
    static boolean getBooleanProperty(final String name, final boolean defaultValue) {
        final String value = System.getProperty(name);
        return value != null ? Boolean.parseBoolean(value) : defaultValue;
    }
    static int getIntProperty(final String name, final int defaultValue) {
        final String value = System.getProperty(name);
        try {
            return value != null ? Integer.parseInt(value) : defaultValue;
        } catch (final NumberFormatException e) {
            return defaultValue;
        }
    }
    private DecodeOptions decodeOptions;
    private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY;
    private boolean keepAlive = DEFAULT_KEEPALIVE;
    private boolean reuseAddress = DEFAULT_REUSE_ADDRESS;
    private int linger = DEFAULT_LINGER;
    private TCPNIOTransport transport;
    CommonLDAPOptions() {
        this.decodeOptions = new DecodeOptions();
    }
    CommonLDAPOptions(final CommonLDAPOptions<?> options) {
        this.decodeOptions = new DecodeOptions(options.decodeOptions);
        this.linger = options.linger;
        this.keepAlive = options.keepAlive;
        this.reuseAddress = options.reuseAddress;
        this.tcpNoDelay = options.tcpNoDelay;
    }
    /**
     * Returns the decoding options which will be used to control how requests
     * and responses are decoded.
     *
     * @return The decoding options which will be used to control how requests
     *         and responses are decoded (never {@code null}).
     */
    public DecodeOptions getDecodeOptions() {
        return decodeOptions;
    }
    /**
     * Returns the value of the {@link java.net.StandardSocketOptions#SO_LINGER
     * SO_LINGER} socket option for new connections.
     * <p>
     * The default setting is {@code -1} (disabled) and may be configured using
     * the {@code org.forgerock.opendj.io.linger} property.
     *
     * @return The value of the {@link java.net.StandardSocketOptions#SO_LINGER
     *         SO_LINGER} socket option for new connections, or -1 if linger
     *         should be disabled.
     */
    public int getLinger() {
        return linger;
    }
    /**
     * Returns the value of the
     * {@link java.net.StandardSocketOptions#SO_KEEPALIVE SO_KEEPALIVE} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.keepAlive} property.
     *
     * @return The value of the
     *         {@link java.net.StandardSocketOptions#SO_KEEPALIVE SO_KEEPALIVE}
     *         socket option for new connections.
     */
    public boolean isKeepAlive() {
        return keepAlive;
    }
    /**
     * Returns the value of the
     * {@link java.net.StandardSocketOptions#SO_REUSEADDR SO_REUSEADDR} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.reuseAddress} property.
     *
     * @return The value of the
     *         {@link java.net.StandardSocketOptions#SO_REUSEADDR SO_REUSEADDR}
     *         socket option for new connections.
     */
    public boolean isReuseAddress() {
        return reuseAddress;
    }
    /**
     * Returns the value of the
     * {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.tcpNoDelay} property.
     *
     * @return The value of the
     *         {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY}
     *         socket option for new connections.
     */
    public boolean isTCPNoDelay() {
        return tcpNoDelay;
    }
    /**
     * Sets the decoding options which will be used to control how requests and
     * responses are decoded.
     *
     * @param decodeOptions
     *            The decoding options which will be used to control how
     *            requests and responses are decoded (never {@code null}).
     * @return A reference to this set of options.
     * @throws NullPointerException
     *             If {@code decodeOptions} was {@code null}.
     */
    public T setDecodeOptions(final DecodeOptions decodeOptions) {
        Validator.ensureNotNull(decodeOptions);
        this.decodeOptions = decodeOptions;
        return getThis();
    }
    /**
     * Specifies the value of the
     * {@link java.net.StandardSocketOptions#SO_KEEPALIVE SO_KEEPALIVE} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.keepAlive} property.
     *
     * @param keepAlive
     *            The value of the
     *            {@link java.net.StandardSocketOptions#SO_KEEPALIVE
     *            SO_KEEPALIVE} socket option for new connections.
     * @return A reference to this set of options.
     */
    public T setKeepAlive(final boolean keepAlive) {
        this.keepAlive = keepAlive;
        return getThis();
    }
    /**
     * Specifies the value of the
     * {@link java.net.StandardSocketOptions#SO_LINGER SO_LINGER} socket option
     * for new connections.
     * <p>
     * The default setting is {@code -1} (disabled) and may be configured using
     * the {@code org.forgerock.opendj.io.linger} property.
     *
     * @param linger
     *            The value of the
     *            {@link java.net.StandardSocketOptions#SO_LINGER SO_LINGER}
     *            socket option for new connections, or -1 if linger should be
     *            disabled.
     * @return A reference to this set of options.
     */
    public T setLinger(final int linger) {
        this.linger = linger;
        return getThis();
    }
    /**
     * Specifies the value of the
     * {@link java.net.StandardSocketOptions#SO_REUSEADDR SO_REUSEADDR} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.reuseAddress} property.
     *
     * @param reuseAddress
     *            The value of the
     *            {@link java.net.StandardSocketOptions#SO_REUSEADDR
     *            SO_REUSEADDR} socket option for new connections.
     * @return A reference to this set of options.
     */
    public T setReuseAddress(final boolean reuseAddress) {
        this.reuseAddress = reuseAddress;
        return getThis();
    }
    /**
     * Specifies the value of the
     * {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY} socket
     * option for new connections.
     * <p>
     * The default setting is {@code true} and may be configured using the
     * {@code org.forgerock.opendj.io.tcpNoDelay} property.
     *
     * @param tcpNoDelay
     *            The value of the
     *            {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY}
     *            socket option for new connections.
     * @return A reference to this set of options.
     */
    public T setTCPNoDelay(final boolean tcpNoDelay) {
        this.tcpNoDelay = tcpNoDelay;
        return getThis();
    }
    /**
     * Returns the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory should be used to obtain a TCP transport.
     *
     * @return The Grizzly TCP transport which will be used when initiating
     *         connections with the Directory Server, or {@code null} if the
     *         default transport factory should be used to obtain a TCP
     *         transport.
     */
    public TCPNIOTransport getTCPNIOTransport() {
        return transport;
    }
    /**
     * Sets the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory should be used to obtain a TCP transport.
     *
     * @param transport
     *            The Grizzly TCP transport which will be used when initiating
     *            connections with the Directory Server, or {@code null} if the
     *            default transport factory should be used to obtain a TCP
     *            transport.
     * @return A reference to this connection options.
     */
    public T setTCPNIOTransport(final TCPNIOTransport transport) {
        this.transport = transport;
        return getThis();
    }
    abstract T getThis();
}
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
@@ -22,34 +22,26 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import com.forgerock.opendj.util.Validator;
/**
 * Common options for LDAP listeners.
 */
public final class LDAPListenerOptions {
public final class LDAPListenerOptions extends CommonLDAPOptions<LDAPListenerOptions> {
    private int backlog;
    private DecodeOptions decodeOptions;
    private int maxRequestSize;
    private TCPNIOTransport transport;
    /**
     * Creates a new set of listener options with default settings. SSL will not
     * be enabled, and a default set of decode options will be used.
     */
    public LDAPListenerOptions() {
        this.backlog = 0;
        this.maxRequestSize = 0;
        this.decodeOptions = new DecodeOptions();
        this.transport = null;
        super();
    }
    /**
@@ -60,10 +52,9 @@
     *            The set of listener options to be copied.
     */
    public LDAPListenerOptions(final LDAPListenerOptions options) {
        super(options);
        this.backlog = options.backlog;
        this.maxRequestSize = options.maxRequestSize;
        this.decodeOptions = new DecodeOptions(options.decodeOptions);
        this.transport = options.transport;
    }
    /**
@@ -79,17 +70,6 @@
    }
    /**
     * Returns the decoding options which will be used to control how requests
     * and responses are decoded.
     *
     * @return The decoding options which will be used to control how requests
     *         and responses are decoded (never {@code null}).
     */
    public DecodeOptions getDecodeOptions() {
        return decodeOptions;
    }
    /**
     * Returns the maximum request size in bytes for incoming LDAP requests. If
     * an incoming request exceeds the limit then the connection will be aborted
     * by the listener. If the limit is less than {@code 1} then a default value
@@ -102,22 +82,6 @@
    }
    /**
     * Returns the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory should be used to obtain a TCP transport.
     *
     * @return The Grizzly TCP transport which will be used when initiating
     *         connections with the Directory Server, or {@code null} if the
     *         default transport factory should be used to obtain a TCP
     *         transport.
     */
    public TCPNIOTransport getTCPNIOTransport() {
        return transport;
    }
    /**
     * Sets the maximum queue length for incoming connections requests. If a
     * connection request arrives when the queue is full, the connection is
     * refused. If the backlog is less than {@code 1} then a default value of
@@ -133,23 +97,6 @@
    }
    /**
     * Sets the decoding options which will be used to control how requests and
     * responses are decoded.
     *
     * @param decodeOptions
     *            The decoding options which will be used to control how
     *            requests and responses are decoded (never {@code null}).
     * @return A reference to this LDAP listener options.
     * @throws NullPointerException
     *             If {@code decodeOptions} was {@code null}.
     */
    public LDAPListenerOptions setDecodeOptions(final DecodeOptions decodeOptions) {
        Validator.ensureNotNull(decodeOptions);
        this.decodeOptions = decodeOptions;
        return this;
    }
    /**
     * Sets the maximum request size in bytes for incoming LDAP requests. If an
     * incoming request exceeds the limit then the connection will be aborted by
     * the listener. If the limit is less than {@code 1} then a default value of
@@ -165,22 +112,25 @@
    }
    /**
     * Sets the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory should be used to obtain a TCP transport.
     *
     * @param transport
     *            The Grizzly TCP transport which will be used when initiating
     *            connections with the Directory Server, or {@code null} if the
     *            default transport factory should be used to obtain a TCP
     *            transport.
     * @return A reference to this connection options.
     * {@inheritDoc}
     */
    public LDAPListenerOptions setTCPNIOTransport(final TCPNIOTransport transport) {
        this.transport = transport;
        return this;
    @Override
    public LDAPListenerOptions setDecodeOptions(DecodeOptions decodeOptions) {
        // This method is required for binary compatibility.
        return super.setDecodeOptions(decodeOptions);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public LDAPListenerOptions setTCPNIOTransport(TCPNIOTransport transport) {
        // This method is required for binary compatibility.
        return super.setTCPNIOTransport(transport);
    }
    @Override
    LDAPListenerOptions getThis() {
        return this;
    }
}
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
@@ -22,11 +22,13 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 *      Portions copyright 2012-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.Validator.ensureNotNull;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -35,8 +37,6 @@
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import com.forgerock.opendj.util.Validator;
/**
 * Common options for LDAP client connections.
 * <p>
@@ -56,25 +56,28 @@
 * // Connection uses StartTLS...
 * </pre>
 */
public final class LDAPOptions {
public final class LDAPOptions extends CommonLDAPOptions<LDAPOptions> {
    // Default values for options taken from Java properties.
    private static final long DEFAULT_TIMEOUT;
    private static final long DEFAULT_CONNECT_TIMEOUT;
    static {
        DEFAULT_TIMEOUT = getIntProperty("org.forgerock.opendj.io.timeout", 0);
        DEFAULT_CONNECT_TIMEOUT = getIntProperty("org.forgerock.opendj.io.connectTimeout", 5000);
    }
    private SSLContext sslContext;
    private boolean useStartTLS;
    private long timeoutInMillis;
    private DecodeOptions decodeOptions;
    private List<String> enabledCipherSuites = new LinkedList<String>();
    private List<String> enabledProtocols = new LinkedList<String>();
    private TCPNIOTransport transport;
    private long timeoutInMillis = DEFAULT_TIMEOUT;
    private long connectTimeoutInMillis = DEFAULT_CONNECT_TIMEOUT;
    private final List<String> enabledCipherSuites = new LinkedList<String>();
    private final List<String> enabledProtocols = new LinkedList<String>();
    /**
     * Creates a new set of connection options with default settings. SSL will
     * not be enabled, and a default set of decode options will be used.
     */
    public LDAPOptions() {
        this.sslContext = null;
        this.timeoutInMillis = 0;
        this.useStartTLS = false;
        this.decodeOptions = new DecodeOptions();
        this.transport = null;
        super();
    }
    /**
@@ -85,24 +88,93 @@
     *            The set of connection options to be copied.
     */
    public LDAPOptions(final LDAPOptions options) {
        super(options);
        this.sslContext = options.sslContext;
        this.timeoutInMillis = options.timeoutInMillis;
        this.useStartTLS = options.useStartTLS;
        this.decodeOptions = new DecodeOptions(options.decodeOptions);
        this.enabledCipherSuites.addAll(options.getEnabledCipherSuites());
        this.enabledProtocols.addAll(options.getEnabledProtocols());
        this.transport = options.transport;
        this.connectTimeoutInMillis = options.connectTimeoutInMillis;
    }
    /**
     * Returns the decoding options which will be used to control how requests
     * and responses are decoded.
     * Adds the cipher suites enabled for secure connections with the Directory
     * Server.
     * <p>
     * The suites must be supported by the SSLContext specified in
     * {@link #setSSLContext(SSLContext)}. Following a successful call to this
     * method, only the suites listed in the protocols parameter are enabled for
     * use.
     *
     * @return The decoding options which will be used to control how requests
     *         and responses are decoded (never {@code null}).
     * @param suites
     *            Names of all the suites to enable.
     * @return A reference to this set of options.
     */
    public final DecodeOptions getDecodeOptions() {
        return decodeOptions;
    public LDAPOptions addEnabledCipherSuite(final String... suites) {
        for (final String suite : suites) {
            enabledCipherSuites.add(ensureNotNull(suite));
        }
        return this;
    }
    /**
     * Adds the protocol versions enabled for secure connections with the
     * Directory Server.
     * <p>
     * The protocols must be supported by the SSLContext specified in
     * {@link #setSSLContext(SSLContext)}. Following a successful call to this
     * method, only the protocols listed in the protocols parameter are enabled
     * for use.
     *
     * @param protocols
     *            Names of all the protocols to enable.
     * @return A reference to this set of options.
     */
    public LDAPOptions addEnabledProtocol(final String... protocols) {
        for (final String protocol : protocols) {
            enabledProtocols.add(ensureNotNull(protocol));
        }
        return this;
    }
    /**
     * 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
     * setting of 0 causes the OS connect timeout to be used.
     * <p>
     * The default operation timeout is 10 seconds and may be configured using
     * the {@code org.forgerock.opendj.io.connectTimeout} property.
     *
     * @param unit
     *            The time unit.
     * @return The connect timeout, which may be 0 if there is no connect
     *         timeout.
     */
    public long getConnectTimeout(final TimeUnit unit) {
        return unit.convert(connectTimeoutInMillis, TimeUnit.MILLISECONDS);
    }
    /**
     * Returns the names of the protocol versions which are currently enabled
     * for secure connections with the Directory Server.
     *
     * @return An array of protocols or empty set if the default protocols are
     *         to be used.
     */
    public List<String> getEnabledCipherSuites() {
        return enabledCipherSuites;
    }
    /**
     * Returns the names of the protocol versions which are currently enabled
     * for secure connections with the Directory Server.
     *
     * @return An array of protocols or empty set if the default protocols are
     *         to be used.
     */
    public List<String> getEnabledProtocols() {
        return enabledProtocols;
    }
    /**
@@ -118,51 +190,47 @@
     *         connections with the Directory Server, which may be {@code null}
     *         indicating that connections will not be secured.
     */
    public final SSLContext getSSLContext() {
    public SSLContext getSSLContext() {
        return sslContext;
    }
    /**
     * Returns the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * Returns the operation timeout in the specified unit. If a response is not
     * received from the Directory Server within the timeout period, then the
     * operation will be abandoned and a {@link TimeoutResultException} error
     * result returned. A timeout setting of 0 disables operation timeout
     * limits.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory should be used to obtain a TCP transport.
     *
     * @return The Grizzly TCP transport which will be used when initiating
     *         connections with the Directory Server, or {@code null} if the
     *         default transport factory should be used to obtain a TCP
     *         transport.
     */
    public final TCPNIOTransport getTCPNIOTransport() {
        return transport;
    }
    /**
     * Returns the operation timeout in the specified unit.
     * The default operation timeout is 0 (no timeout) and may be configured
     * using the {@code org.forgerock.opendj.io.timeout} property.
     *
     * @param unit
     *            The time unit of use.
     * @return The operation timeout.
     *            The time unit.
     * @return The operation timeout, which may be 0 if there is no operation
     *         timeout.
     */
    public final long getTimeout(final TimeUnit unit) {
    public long getTimeout(final TimeUnit unit) {
        return unit.convert(timeoutInMillis, TimeUnit.MILLISECONDS);
    }
    /**
     * Sets the decoding options which will be used to control how requests and
     * responses are decoded.
     * Sets the connect timeout. If a connection is not established within the
     * timeout period, then a {@link TimeoutResultException} error result will
     * be returned. A timeout setting of 0 causes the OS connect timeout to be
     * used.
     * <p>
     * The default operation timeout is 10 seconds and may be configured using
     * the {@code org.forgerock.opendj.io.connectTimeout} property.
     *
     * @param decodeOptions
     *            The decoding options which will be used to control how
     *            requests and responses are decoded (never {@code null}).
     * @return A reference to this LDAP connection options.
     * @throws NullPointerException
     *             If {@code decodeOptions} was {@code null}.
     * @param timeout
     *            The connect timeout, which may be 0 if there is no connect
     *            timeout.
     * @param unit
     *            The time unit.
     * @return A reference to this set of options.
     */
    public final LDAPOptions setDecodeOptions(final DecodeOptions decodeOptions) {
        Validator.ensureNotNull(decodeOptions);
        this.decodeOptions = decodeOptions;
    public LDAPOptions setConnectTimeout(final long timeout, final TimeUnit unit) {
        this.connectTimeoutInMillis = unit.toMillis(timeout);
        return this;
    }
@@ -179,45 +247,30 @@
     *            The SSL context which will be used when initiating secure
     *            connections with the Directory Server, which may be
     *            {@code null} indicating that connections will not be secured.
     * @return A reference to this LDAP connection options.
     * @return A reference to this set of options.
     */
    public final LDAPOptions setSSLContext(final SSLContext sslContext) {
    public LDAPOptions setSSLContext(final SSLContext sslContext) {
        this.sslContext = sslContext;
        return this;
    }
    /**
     * Sets the Grizzly TCP transport which will be used when initiating
     * connections with the Directory Server.
     * Sets the operation timeout. If a response is not received from the
     * Directory Server within the timeout period, then the operation will be
     * abandoned and a {@link TimeoutResultException} error result returned. A
     * timeout setting of 0 disables operation timeout limits.
     * <p>
     * By default this method will return {@code null} indicating that the
     * default transport factory will be used to obtain a TCP transport.
     *
     * @param transport
     *            The Grizzly TCP transport which will be used when initiating
     *            connections with the Directory Server, or {@code null} if the
     *            default transport factory should be used to obtain a TCP
     *            transport.
     * @return A reference to this LDAP connection options.
     */
    public final LDAPOptions setTCPNIOTransport(final TCPNIOTransport transport) {
        this.transport = transport;
        return this;
    }
    /**
     * Sets the operation timeout. If the response is not received from the
     * Directory Server in the timeout period, the operation will be abandoned
     * and an error result returned. A timeout setting of 0 disables timeout
     * limits.
     * The default operation timeout is 0 (no timeout) and may be configured
     * using the {@code org.forgerock.opendj.io.timeout} property.
     *
     * @param timeout
     *            The operation timeout to use.
     *            The operation timeout, which may be 0 if there is no operation
     *            timeout.
     * @param unit
     *            the time unit of the time argument.
     * @return A reference to this LDAP connection options.
     *            The time unit.
     * @return A reference to this set of options.
     */
    public final LDAPOptions setTimeout(final long timeout, final TimeUnit unit) {
    public LDAPOptions setTimeout(final long timeout, final TimeUnit unit) {
        this.timeoutInMillis = unit.toMillis(timeout);
        return this;
    }
@@ -232,9 +285,9 @@
     *            {@code true} if StartTLS should be used for securing
     *            connections when an SSL context is specified, otherwise
     *            {@code false} indicating that SSL should be used.
     * @return A reference to this LDAP connection options.
     * @return A reference to this set of options.
     */
    public final LDAPOptions setUseStartTLS(final boolean useStartTLS) {
    public LDAPOptions setUseStartTLS(final boolean useStartTLS) {
        this.useStartTLS = useStartTLS;
        return this;
    }
@@ -249,70 +302,30 @@
     *         when an SSL context is specified, otherwise {@code false}
     *         indicating that SSL should be used.
     */
    public final boolean useStartTLS() {
    public boolean useStartTLS() {
        return useStartTLS;
    }
    /**
     * Adds the protocol versions enabled for secure connections with the
     * Directory Server.
     * <p>
     * The protocols must be supported by the SSLContext specified in
     * {@link #setSSLContext(SSLContext)}. Following a successful call to this
     * method, only the protocols listed in the protocols parameter are enabled
     * for use.
     *
     * @param protocols
     *            Names of all the protocols to enable.
     * @return A reference to this LDAP connection options.
     * {@inheritDoc}
     */
    public final LDAPOptions addEnabledProtocol(String... protocols) {
        for (final String protocol : protocols) {
            enabledProtocols.add(Validator.ensureNotNull(protocol));
        }
    @Override
    public LDAPOptions setDecodeOptions(DecodeOptions decodeOptions) {
        // This method is required for binary compatibility.
        return super.setDecodeOptions(decodeOptions);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public LDAPOptions setTCPNIOTransport(TCPNIOTransport transport) {
        // This method is required for binary compatibility.
        return super.setTCPNIOTransport(transport);
    }
    @Override
    LDAPOptions getThis() {
        return this;
    }
    /**
     * Adds the cipher suites enabled for secure connections with the Directory
     * Server.
     * <p>
     * The suites must be supported by the SSLContext specified in
     * {@link #setSSLContext(SSLContext)}. Following a successful call to this
     * method, only the suites listed in the protocols parameter are enabled for
     * use.
     *
     * @param suites
     *            Names of all the suites to enable.
     * @return A reference to this LDAP connection options.
     */
    public final LDAPOptions addEnabledCipherSuite(String... suites) {
        for (final String suite : suites) {
            enabledCipherSuites.add(Validator.ensureNotNull(suite));
        }
        return this;
    }
    /**
     * Returns the names of the protocol versions which are currently enabled
     * for secure connections with the Directory Server.
     *
     * @return An array of protocols or empty set if the default protocols are
     *         to be used.
     */
    public final List<String> getEnabledProtocols() {
        return enabledProtocols;
    }
    /**
     * Returns the names of the protocol versions which are currently enabled
     * for secure connections with the Directory Server.
     *
     * @return An array of protocols or empty set if the default protocols are
     *         to be used.
     */
    public final List<String> getEnabledCipherSuites() {
        return enabledCipherSuites;
    }
}
opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
@@ -1423,6 +1423,8 @@
HBCF_HEARTBEAT_TIMEOUT=Heartbeat timed out after %d ms
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
opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java
@@ -104,7 +104,7 @@
            connection.searchAsync(request, null, handler);
            // Pass in a time which is guaranteed to trigger expiration.
            connection.cancelExpiredRequests(System.currentTimeMillis() + 1000000);
            connection.handleTimeout(System.currentTimeMillis() + 1000000);
            if (isPersistentSearch) {
                verifyZeroInteractions(handler);
            } else {
opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *     Copyright 2013 ForgeRock AS.
 *     Copyright 2013-2014 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
@@ -61,7 +61,11 @@
 */
@SuppressWarnings({ "javadoc", "unchecked" })
public class LDAPConnectionFactoryTestCase extends SdkTestCase {
    // Manual testing has gone up to 10000 iterations.
    /*
     * The number of test iterations for unit tests which attempt to expose
     * potential race conditions. Manual testing has gone up to 10000
     * iterations.
     */
    private static final int ITERATIONS = 100;
    // Test timeout for tests which need to wait for network events.
@@ -95,6 +99,31 @@
        server.close();
    }
    @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, new LDAPOptions().setConnectTimeout(
                        1, TimeUnit.MILLISECONDS));
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                final ResultHandler<Connection> handler = mock(ResultHandler.class);
                final FutureResult<Connection> future = factory.getConnectionAsync(handler);
                // Wait for the connect to timeout.
                try {
                    future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
                    fail("The connect request succeeded unexpectedly");
                } catch (ConnectionException e) {
                    assertThat(e.getResult().getResultCode()).isEqualTo(
                            ResultCode.CLIENT_SIDE_CONNECT_ERROR);
                    verify(handler).handleErrorResult(same(e));
                }
            }
        } finally {
            factory.close();
        }
    }
    /**
     * Unit test for OPENDJ-1247: a locally timed out bind request will leave a
     * connection in an invalid state since a bind (or startTLS) is in progress