From ed08a89377a333c10202ead88d355e16bcb3a0fd Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 27 Feb 2014 23:31:10 +0000
Subject: [PATCH] Backport fix for OPENDJ-1197: API is lacking functionality to specify TCP connect timeout
---
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java | 94 +---
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java | 74 +++
opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties | 2
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java | 100 +++--
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java | 285 +++++++++++++++
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java | 8
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java | 16
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java | 61 ++-
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java | 87 +++-
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java | 13
opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java | 297 ++++++++-------
opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java | 33 +
opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java | 56 ++
opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java | 2
14 files changed, 814 insertions(+), 314 deletions(-)
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java
new file mode 100644
index 0000000..b9002d0
--- /dev/null
+++ b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/GrizzlyUtils.java
@@ -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.
+ }
+
+}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
index ed4bd3f..1c16c06 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnection.java
+++ b/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();
}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java
index 49d6fa1..2f397d2 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPConnectionFactoryImpl.java
+++ b/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;
}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java
index e1d9604..a91b1da 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPListenerImpl.java
+++ b/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;
+ }
}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java
index abd829a..7fec5b1 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/LDAPServerFilter.java
+++ b/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 =
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java
index b169be8..4631bbb 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutChecker.java
+++ b/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.
}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java
new file mode 100644
index 0000000..01bdd04
--- /dev/null
+++ b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/ldap/TimeoutEventListener.java
@@ -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();
+}
diff --git a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java b/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
index efa811d..77b0588 100644
--- a/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
+++ b/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
diff --git a/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java b/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
new file mode 100644
index 0000000..7dac273
--- /dev/null
+++ b/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
@@ -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();
+}
diff --git a/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java b/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
index 3290349..ff06b2c 100644
--- a/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
+++ b/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;
+ }
}
diff --git a/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java b/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
index 4a8e8fc..f34c0a5 100644
--- a/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
+++ b/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;
- }
-
}
diff --git a/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties b/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
index 16fae6d..d3cb3ab 100755
--- a/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
+++ b/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
diff --git a/opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java b/opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java
index 1c1d13a..93ac8c5 100644
--- a/opendj-ldap-sdk/src/test/java/com/forgerock/opendj/ldap/LDAPConnectionTestCase.java
+++ b/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 {
diff --git a/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java b/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
index 7778e46..1fbf160 100644
--- a/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPConnectionFactoryTestCase.java
+++ b/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
--
Gitblit v1.10.0