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