From 0589dbf6c4e36eb5575982d24ab5a0fb16d134e4 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 27 Feb 2014 16:33:22 +0000
Subject: [PATCH] Fix OPENDJ-1197: API is lacking functionality to specify TCP connect timeout
---
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java | 114 +++--
opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java | 88 +++-
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java | 328 ++++++---------
opendj-core/clirr-ignored-api-changes.xml | 13
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java | 126 ------
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java | 11
opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java | 333 ++++++++++++++++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java | 12
opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties | 4
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java | 49 ++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java | 78 ++-
11 files changed, 732 insertions(+), 424 deletions(-)
diff --git a/opendj-core/clirr-ignored-api-changes.xml b/opendj-core/clirr-ignored-api-changes.xml
index 0c92380..e02b4ef 100644
--- a/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-core/clirr-ignored-api-changes.xml
@@ -165,4 +165,17 @@
<differenceType>8001</differenceType>
<justification>Renamed SchemaValidationPolicy.Policy to SchemaValidationPolicy.Action</justification>
</difference>
+
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPListenerOptions</className>
+ <differenceType>7002</differenceType>
+ <method>org.forgerock.opendj.ldap.LDAPListenerOptions setDecodeOptions(org.forgerock.opendj.ldap.DecodeOptions)</method>
+ <justification>OPENDJ-1197: Method return type has changed due to reification</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPOptions</className>
+ <differenceType>7002</differenceType>
+ <method>org.forgerock.opendj.ldap.LDAPOptions setDecodeOptions(org.forgerock.opendj.ldap.DecodeOptions)</method>
+ <justification>OPENDJ-1197: Method return type has changed due to reification</justification>
+ </difference>
</differences>
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java b/opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
index 1ffc3c7..3e76e12 100644
--- a/opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
+++ b/opendj-core/src/main/java/com/forgerock/opendj/util/AsynchronousFutureResult.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
- * Portions copyright 2013 ForgeRock AS.
+ * Portions copyright 2013-2014 ForgeRock AS.
*/
package com.forgerock.opendj.util;
@@ -162,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 {
@@ -316,6 +318,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-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
new file mode 100644
index 0000000..c3c3bc5
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
@@ -0,0 +1,333 @@
+/*
+ * 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.forgerock.util.Reject;
+
+/**
+ * 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 ClassLoader providerClassLoader;
+ private String transportProvider;
+ private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY;
+ private boolean keepAlive = DEFAULT_KEEPALIVE;
+ private boolean reuseAddress = DEFAULT_REUSE_ADDRESS;
+ private int linger = DEFAULT_LINGER;
+
+ CommonLDAPOptions() {
+ this.decodeOptions = new DecodeOptions();
+ }
+
+ CommonLDAPOptions(final CommonLDAPOptions<?> options) {
+ this.decodeOptions = new DecodeOptions(options.decodeOptions);
+ this.providerClassLoader = options.providerClassLoader;
+ this.transportProvider = options.transportProvider;
+ 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 class loader which will be used to load the
+ * {@code TransportProvider}.
+ * <p>
+ * By default this method will return {@code null} indicating that the
+ * default class loader will be used.
+ * <p>
+ * The transport provider is loaded using {@code java.util.ServiceLoader},
+ * the JDK service-provider loading facility. The provider must be
+ * accessible from the same class loader that was initially queried to
+ * locate the configuration file; note that this is not necessarily the
+ * class loader from which the file was actually loaded. This method allows
+ * to provide a class loader to be used for loading the provider.
+ *
+ * @return The class loader which will be used when loading the transport
+ * provider, or {@code null} if the default class loader should be
+ * used.
+ */
+ public ClassLoader getProviderClassLoader() {
+ return providerClassLoader;
+ }
+
+ /**
+ * Returns the name of the provider used for transport.
+ * <p>
+ * Transport providers implement {@code TransportProvider} interface.
+ * <p>
+ * The name should correspond to the name of an existing provider, as
+ * returned by {@code TransportProvider#getName()} method.
+ *
+ * @return The name of transport provider. The name is {@code null} if no
+ * specific provider has been selected. In that case, the first
+ * provider found will be used.
+ */
+ public String getTransportProvider() {
+ return transportProvider;
+ }
+
+ /**
+ * 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) {
+ Reject.ifNull(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();
+ }
+
+ /**
+ * Sets the class loader which will be used to load the
+ * {@code TransportProvider}.
+ * <p>
+ * The default class loader will be used if no class loader is set using
+ * this method.
+ * <p>
+ * The transport provider is loaded using {@code java.util.ServiceLoader},
+ * the JDK service-provider loading facility. The provider must be
+ * accessible from the same class loader that was initially queried to
+ * locate the configuration file; note that this is not necessarily the
+ * class loader from which the file was actually loaded. This method allows
+ * to provide a class loader to be used for loading the provider.
+ *
+ * @param classLoader
+ * The class loader which will be used when loading the transport
+ * provider, or {@code null} if the default class loader should
+ * be used.
+ * @return A reference to this set of options.
+ */
+ public T setProviderClassLoader(final ClassLoader classLoader) {
+ this.providerClassLoader = classLoader;
+ 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();
+ }
+
+ /**
+ * Sets the name of the provider to use for transport.
+ * <p>
+ * Transport providers implement {@code TransportProvider} interface.
+ * <p>
+ * The name should correspond to the name of an existing provider, as
+ * returned by {@code TransportProvider#getName()} method.
+ *
+ * @param providerName
+ * The name of transport provider, or {@code null} if no specific
+ * provider is preferred. In that case, the first provider found
+ * will be used.
+ * @return A reference to this set of options.
+ */
+ public T setTransportProvider(final String providerName) {
+ this.transportProvider = providerName;
+ return getThis();
+ }
+
+ abstract T getThis();
+}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
index 84eba97..76e371b 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListenerOptions.java
@@ -22,30 +22,24 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012-2013 ForgeRock AS.
+ * Portions copyright 2012-2014 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
-import org.forgerock.util.Reject;
-
/**
* 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 ClassLoader providerClassLoader;
- private String transportProvider;
/**
* 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.decodeOptions = new DecodeOptions();
+ super();
}
/**
@@ -56,11 +50,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.providerClassLoader = options.providerClassLoader;
- this.transportProvider = options.transportProvider;
}
/**
@@ -76,17 +68,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
@@ -114,23 +95,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) {
- Reject.ifNull(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
@@ -145,86 +109,8 @@
return this;
}
- /**
- * Gets the class loader which will be used to load the
- * {@code TransportProvider}.
- * <p>
- * By default this method will return {@code null} indicating that the
- * default class loader will be used.
- * <p>
- * The transport provider is loaded using {@code java.util.ServiceLoader},
- * the JDK service-provider loading facility. The provider must be
- * accessible from the same class loader that was initially queried to
- * locate the configuration file; note that this is not necessarily the
- * class loader from which the file was actually loaded. This method allows
- * to provide a class loader to be used for loading the provider.
- *
- * @return The class loader which will be used to load the transport
- * provider, or {@code null} if the default class loader should be
- * used.
- */
- public final ClassLoader getProviderClassLoader() {
- return providerClassLoader;
- }
-
- /**
- * Sets the class loader which will be used to load the
- * {@code TransportProvider}.
- * <p>
- * The default class loader will be used if no class loader is set using
- * this method.
- * <p>
- * The transport provider is loaded using {@code java.util.ServiceLoader},
- * the JDK service-provider loading facility. The provider must be
- * accessible from the same class loader that was initially queried to
- * locate the configuration file; note that this is not necessarily the
- * class loader from which the file was actually loaded. This method allows
- * to provide a class loader to be used for loading the provider.
- *
- * @param classLoader
- * The class loader which will be used load the transport
- * provider, or {@code null} if the default class loader should
- * be used.
- * @return A reference to this LDAP listener options.
- */
- public final LDAPListenerOptions setProviderClassLoader(ClassLoader classLoader) {
- this.providerClassLoader = classLoader;
+ @Override
+ LDAPListenerOptions getThis() {
return this;
}
-
- /**
- * Returns the name of the provider used for transport.
- * <p>
- * Transport providers implement {@code TransportProvider} interface.
- * <p>
- * The name should correspond to the name of an existing provider, as
- * returned by {@code TransportProvider#getName()} method.
- *
- * @return The name of transport provider. The name is {@code null} if no
- * specific provider has been selected. In that case, the first
- * provider found will be used.
- */
- public String getTransportProvider() {
- return transportProvider;
- }
-
- /**
- * Sets the name of the provider to use for transport.
- * <p>
- * Transport providers implement {@code TransportProvider} interface.
- * <p>
- * The name should correspond to the name of an existing provider, as
- * returned by {@code TransportProvider#getName()} method.
- *
- * @param providerName
- * The name of transport provider, or {@code null} if no specific
- * provider is preferred. In that case, the first provider found
- * will be used.
- * @return A reference to this LDAP listener options.
- */
- public LDAPListenerOptions setTransportProvider(String providerName) {
- this.transportProvider = providerName;
- return this;
- }
-
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
index dd489b2..c399e49 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java
@@ -54,22 +54,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 ClassLoader providerClassLoader;
- private String transportProvider;
+ 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.decodeOptions = new DecodeOptions();
+ super();
}
/**
@@ -80,25 +86,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.providerClassLoader = options.providerClassLoader;
- this.transportProvider = options.transportProvider;
+ 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(Reject.checkNotNull(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(Reject.checkNotNull(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;
}
/**
@@ -114,35 +188,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 operation timeout in the specified unit.
+ * 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>
+ * 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) {
- Reject.ifNull(decodeOptions);
- this.decodeOptions = decodeOptions;
+ public LDAPOptions setConnectTimeout(final long timeout, final TimeUnit unit) {
+ this.connectTimeoutInMillis = unit.toMillis(timeout);
return this;
}
@@ -159,26 +245,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 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.
+ * 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>
+ * 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;
}
@@ -193,9 +283,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;
}
@@ -210,154 +300,12 @@
* 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.
- */
- public final LDAPOptions addEnabledProtocol(String... protocols) {
- for (final String protocol : protocols) {
- enabledProtocols.add(Reject.checkNotNull(protocol));
- }
+ @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(Reject.checkNotNull(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;
- }
-
- /**
- * Gets the class loader which will be used to load the
- * {@code TransportProvider}.
- * <p>
- * By default this method will return {@code null} indicating that the
- * default class loader will be used.
- * <p>
- * The transport provider is loaded using {@code java.util.ServiceLoader},
- * the JDK service-provider loading facility. The provider must be
- * accessible from the same class loader that was initially queried to
- * locate the configuration file; note that this is not necessarily the
- * class loader from which the file was actually loaded. This method allows
- * to provide a class loader to be used for loading the provider.
- *
- * @return The class loader which will be used when loading the transport
- * provider, or {@code null} if the default class loader should be
- * used.
- */
- public final ClassLoader getProviderClassLoader() {
- return providerClassLoader;
- }
-
- /**
- * Sets the class loader which will be used to load the
- * {@code TransportProvider}.
- * <p>
- * The default class loader will be used if no class loader is set using
- * this method.
- * <p>
- * The transport provider is loaded using {@code java.util.ServiceLoader},
- * the JDK service-provider loading facility. The provider must be
- * accessible from the same class loader that was initially queried to
- * locate the configuration file; note that this is not necessarily the
- * class loader from which the file was actually loaded. This method allows
- * to provide a class loader to be used for loading the provider.
- *
- * @param classLoader
- * The class loader which will be used when loading the transport
- * provider, or {@code null} if the default class loader should
- * be used.
- * @return A reference to this LDAP connection options.
- */
- public final LDAPOptions setProviderClassLoader(ClassLoader classLoader) {
- this.providerClassLoader = classLoader;
- return this;
- }
-
- /**
- * Returns the name of the provider used for transport.
- * <p>
- * Transport providers implement {@code TransportProvider}
- * interface.
- * <p>
- * The name should correspond to the name of an existing provider, as
- * returned by {@code TransportProvider#getName()} method.
- *
- * @return The name of transport provider. The name is {@code null} if no
- * specific provider has been selected. In that case, the first
- * provider found will be used.
- */
- public String getTransportProvider() {
- return transportProvider;
- }
-
- /**
- * Sets the name of the provider to use for transport.
- * <p>
- * Transport providers implement {@code TransportProvider}
- * interface.
- * <p>
- * The name should correspond to the name of an existing provider, as
- * returned by {@code TransportProvider#getName()} method.
- *
- * @param providerName
- * The name of transport provider, or {@code null} if no specific
- * provider is preferred. In that case, the first provider found
- * will be used.
- * @return A reference to this LDAP connection options.
- */
- public LDAPOptions setTransportProvider(String providerName) {
- this.transportProvider = providerName;
- return this;
- }
-
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
index 465ec23..36656f3 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -27,7 +27,10 @@
package org.forgerock.opendj.grizzly;
+import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_CONNECT_TIMEOUT;
import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT;
+import static org.forgerock.opendj.grizzly.GrizzlyUtils.buildFilterChain;
+import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER;
@@ -40,6 +43,7 @@
import javax.net.ssl.SSLEngine;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FutureResult;
@@ -47,6 +51,7 @@
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.TimeoutChecker;
+import org.forgerock.opendj.ldap.TimeoutEventListener;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
@@ -65,19 +70,24 @@
* LDAP connection factory implementation using Grizzly for transport.
*/
public final class GrizzlyLDAPConnectionFactory implements LDAPConnectionFactoryImpl {
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
/**
* Adapts a Grizzly connection completion handler to an LDAP connection
* asynchronous future result.
*/
@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
@@ -98,10 +108,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;
}
@@ -150,6 +160,7 @@
@Override
public void failed(final Throwable throwable) {
// Adapt and forward.
+ timeoutChecker.get().removeListener(this);
future.handleErrorResult(adaptConnectionException(throwable));
releaseTransportAndTimeoutChecker();
}
@@ -159,17 +170,14 @@
// Ignore this.
}
- private GrizzlyLDAPConnection 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);
+ private GrizzlyLDAPConnection adaptConnection(
+ final org.glassfish.grizzly.Connection<?> connection) {
+ configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(), options
+ .isReuseAddress(), options.getLinger(), logger);
+
final GrizzlyLDAPConnection ldapConnection =
new GrizzlyLDAPConnection(connection, GrizzlyLDAPConnectionFactory.this);
- if (options.getTimeout(TimeUnit.MILLISECONDS) > 0) {
- timeoutChecker.get().addListener(ldapConnection);
- }
+ timeoutChecker.get().addListener(ldapConnection);
clientFilter.registerConnection(connection, ldapConnection);
return ldapConnection;
}
@@ -188,20 +196,36 @@
private void onFailure(final GrizzlyLDAPConnection 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 GrizzlyLDAPConnection 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_TIMEOUT,
+ LDAP_CONNECTION_CONNECT_TIMEOUT.get(socketAddress, getTimeout()).toString()));
+ return 0;
+ }
+ }
+
+ @Override
+ public long getTimeout() {
+ return options.getConnectTimeout(TimeUnit.MILLISECONDS);
+ }
}
private final LDAPClientFilter clientFilter;
@@ -226,9 +250,9 @@
.acquire();
/**
- * Creates a new LDAP connection factory based on Grizzly which can be used to
- * create connections to the Directory Server at the provided host and port
- * address using provided connection options.
+ * Creates a new LDAP connection factory based on Grizzly which can be used
+ * to create connections to the Directory Server at the provided host and
+ * port address using provided connection options.
*
* @param address
* The address of the Directory Server to connect to.
@@ -260,8 +284,7 @@
this.options = new LDAPOptions(options);
this.clientFilter = new LDAPClientFilter(this.options.getDecodeOptions(), 0);
this.defaultFilterChain =
- GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), clientFilter);
-
+ buildFilterChain(this.transport.get().getProcessor(), clientFilter);
}
@Override
@@ -289,8 +312,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-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
index 359bfaa..931ed4a 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -35,7 +35,6 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Connections;
-import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
@@ -51,13 +50,13 @@
* LDAP listener implementation using Grizzly for transport.
*/
public final class GrizzlyLDAPListener implements LDAPListenerImpl {
-
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory;
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
@@ -104,9 +103,10 @@
final LDAPListenerOptions options, TCPNIOTransport transport) throws IOException {
this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
this.connectionFactory = factory;
- final DecodeOptions decodeOptions = new DecodeOptions(options.getDecodeOptions());
+ this.options = new LDAPListenerOptions(options);
final LDAPServerFilter serverFilter =
- new LDAPServerFilter(this, decodeOptions, options.getMaxRequestSize());
+ new LDAPServerFilter(this, this.options.getDecodeOptions(), this.options
+ .getMaxRequestSize());
final FilterChain ldapChain =
GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter);
final TCPNIOBindingHandler bindingHandler =
@@ -155,4 +155,8 @@
ServerConnectionFactory<LDAPClientContext, Integer> getConnectionFactory() {
return connectionFactory;
}
+
+ LDAPListenerOptions getLDAPListenerOptions() {
+ return options;
+ }
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
index 50f6477..b05f2e9 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
@@ -25,10 +25,17 @@
*/
package org.forgerock.opendj.grizzly;
+import java.io.IOException;
+import java.net.SocketOption;
+import java.net.StandardSocketOptions;
+import java.nio.channels.SocketChannel;
+
+import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPReader;
import org.forgerock.opendj.io.LDAPWriter;
import org.forgerock.opendj.ldap.DecodeOptions;
+import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Processor;
import org.glassfish.grizzly.ThreadCache;
import org.glassfish.grizzly.filterchain.Filter;
@@ -36,6 +43,7 @@
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.memory.MemoryManager;
+import org.glassfish.grizzly.nio.transport.TCPNIOConnection;
import org.glassfish.grizzly.ssl.SSLFilter;
/**
@@ -65,7 +73,7 @@
* is a {@code FilterChain}, and having the provided filter as the
* last filter
*/
- public static FilterChain buildFilterChain(Processor<?> processor, Filter filter) {
+ static FilterChain buildFilterChain(Processor<?> processor, Filter filter) {
if (processor instanceof FilterChain) {
return FilterChainBuilder.stateless().addAll((FilterChain) processor).add(filter).build();
} else {
@@ -89,7 +97,7 @@
* connection to update with the new filter chain containing the
* provided filter
*/
- public static void addFilterToConnection(final Filter filter, org.glassfish.grizzly.Connection<?> connection) {
+ static void addFilterToConnection(final Filter filter, Connection<?> connection) {
final FilterChain currentChain = (FilterChain) connection.getProcessor();
final FilterChain newChain = addFilterToChain(filter, currentChain);
connection.setProcessor(newChain);
@@ -111,7 +119,7 @@
* initial filter chain
* @return a new filter chain which includes the provided filter
*/
- public static FilterChain addFilterToChain(final Filter filter, final FilterChain chain) {
+ static FilterChain addFilterToChain(final Filter filter, final FilterChain chain) {
// By default, before LDAP filter which is the last one
int indexToAddFilter = chain.size() - 1;
if (filter instanceof SSLFilter) {
@@ -139,8 +147,8 @@
* The memory manager to use for buffering.
* @return a LDAP reader
*/
- public static LDAPReader<ASN1BufferReader> createReader(DecodeOptions decodeOptions, int maxASN1ElementSize,
- MemoryManager<?> memoryManager) {
+ static LDAPReader<ASN1BufferReader> createReader(DecodeOptions decodeOptions,
+ int maxASN1ElementSize, MemoryManager<?> memoryManager) {
ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, memoryManager);
return LDAP.getReader(asn1Reader, decodeOptions);
}
@@ -155,7 +163,7 @@
* @return a LDAP writer
*/
@SuppressWarnings("unchecked")
- public static LDAPWriter<ASN1BufferWriter> getWriter() {
+ static LDAPWriter<ASN1BufferWriter> getWriter() {
LDAPWriter<ASN1BufferWriter> writer = ThreadCache.takeFromCache(WRITER_INDEX);
if (writer == null) {
writer = LDAP.getWriter(new ASN1BufferWriter());
@@ -172,11 +180,38 @@
*
* @param writer LDAP writer to recycle
*/
- public static void recycleWriter(LDAPWriter<ASN1BufferWriter> writer) {
+ static void recycleWriter(LDAPWriter<ASN1BufferWriter> writer) {
writer.getASN1Writer().recycle();
ThreadCache.putToCache(WRITER_INDEX, writer);
}
+ static void configureConnection(final Connection<?> connection, final boolean tcpNoDelay,
+ final boolean keepAlive, final boolean reuseAddress, final int linger,
+ final LocalizedLogger logger) {
+ /*
+ * 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, logger);
+ setSocketOption(channel, StandardSocketOptions.SO_KEEPALIVE, keepAlive, logger);
+ setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, reuseAddress, logger);
+ setSocketOption(channel, StandardSocketOptions.SO_LINGER, linger, logger);
+ }
+
+ private static <T> void setSocketOption(final SocketChannel channel,
+ final SocketOption<T> option, final T value, final LocalizedLogger logger) {
+ try {
+ channel.setOption(option, value);
+ } catch (final IOException e) {
+ logger.traceException(e, "Unable to set " + option.name()
+ + " to %d on client connection", value);
+ }
+ }
+
// Prevent instantiation.
private GrizzlyUtils() {
// No implementation required.
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
index e4a433b..d9893dd 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -27,6 +27,8 @@
package org.forgerock.opendj.grizzly;
+import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
@@ -36,6 +38,7 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.AbstractLDAPMessageHandler;
import org.forgerock.opendj.io.LDAP;
import org.forgerock.opendj.io.LDAPReader;
@@ -46,6 +49,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.SSLContextBuilder;
@@ -82,7 +86,6 @@
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import org.glassfish.grizzly.ssl.SSLUtils;
-
import org.forgerock.util.Reject;
/**
@@ -639,6 +642,8 @@
private static final Attribute<ServerRequestHandler> REQUEST_HANDLER_ATTR =
Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ServerRequestHandler");
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
/**
* A dummy SSL client engine configurator as SSLFilter only needs server
* config. This prevents Grizzly from needlessly using JVM defaults which
@@ -829,7 +834,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(), logger);
try {
final ClientContextImpl clientContext = new ClientContextImpl(connection);
final ServerConnection<Integer> serverConn =
diff --git a/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties b/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
index d47c5af..c0b3cbb 100755
--- a/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
+++ b/opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties
@@ -21,10 +21,12 @@
# CDDL HEADER END
#
#
-# Copyright 2013 ForgeRock AS.
+# Copyright 2013-2014 ForgeRock AS.
#
LDAP_CONNECTION_REQUEST_TIMEOUT=The request has failed because no response \
was received from the server within the %d ms timeout
+LDAP_CONNECTION_CONNECT_TIMEOUT=The connection attempt to server %s has failed \
+ because the connection timeout period of %d ms was exceeded
LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT=The bind or StartTLS request \
has failed because no response was received from the server within the %d ms \
timeout. The LDAP connection is now in an invalid state and can no longer be used
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
index 121ea12..c42eb70 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -21,17 +21,23 @@
* CDDL HEADER END
*
*
- * Copyright 2013 ForgeRock AS.
+ * Copyright 2013-2014 ForgeRock AS.
*/
package org.forgerock.opendj.grizzly;
import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
-import static org.mockito.Mockito.mock;
+import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.Connection;
@@ -55,19 +61,6 @@
import org.forgerock.opendj.ldap.ServerConnection;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.TimeoutResultException;
-import org.testng.annotations.Test;
-
-import static org.fest.assertions.Fail.fail;
-import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import java.util.concurrent.TimeoutException;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -78,13 +71,18 @@
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubber;
import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
/**
* Tests the {@link LDAPConnectionFactory} class.
*/
@SuppressWarnings({ "javadoc", "unchecked" })
public class GrizzlyLDAPConnectionFactoryTestCase 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.
@@ -117,6 +115,30 @@
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 (TimeoutResultException e) {
+ verifyResultCodeIsClientSideTimeout(e);
+ 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
@@ -130,41 +152,39 @@
registerBindEvent();
registerCloseEvent();
- for (int i = 0; i < ITERATIONS; i++) {
- final Connection connection = factory.getConnection();
+ final Connection connection = factory.getConnection();
+ try {
+ waitForConnect();
+ final MockConnectionEventListener listener = new MockConnectionEventListener();
+ connection.addConnectionEventListener(listener);
+
+ final ResultHandler<BindResult> handler = mock(ResultHandler.class);
+ final FutureResult<BindResult> future =
+ connection.bindAsync(newSimpleBindRequest(), null, handler);
+ waitForBind();
+
+ // Wait for the request to timeout.
try {
- waitForConnect();
- final MockConnectionEventListener listener = new MockConnectionEventListener();
- connection.addConnectionEventListener(listener);
+ future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
+ fail("The bind request succeeded unexpectedly");
+ } catch (TimeoutResultException e) {
+ verifyResultCodeIsClientSideTimeout(e);
+ verify(handler).handleErrorResult(same(e));
- final ResultHandler<BindResult> handler = mock(ResultHandler.class);
- final FutureResult<BindResult> future =
- connection.bindAsync(newSimpleBindRequest(), null, handler);
- waitForBind();
-
- // Wait for the request to timeout.
- try {
- future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
- fail("The bind request succeeded unexpectedly");
- } catch (TimeoutResultException e) {
- verifyResultCodeIsClientSideTimeout(e);
- verify(handler).handleErrorResult(same(e));
-
- /*
- * The connection should no longer be valid, the event
- * listener should have been notified, but no abandon should
- * have been sent.
- */
- listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
- assertThat(connection.isValid()).isFalse();
- verifyResultCodeIsClientSideTimeout(listener.getError());
- connection.close();
- waitForClose();
- verifyNoAbandonSent();
- }
- } finally {
+ /*
+ * The connection should no longer be valid, the event listener
+ * should have been notified, but no abandon should have been
+ * sent.
+ */
+ listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
+ assertThat(connection.isValid()).isFalse();
+ verifyResultCodeIsClientSideTimeout(listener.getError());
connection.close();
+ waitForClose();
+ verifyNoAbandonSent();
}
+ } finally {
+ connection.close();
}
}
--
Gitblit v1.10.0