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