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/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java |   78 +++++++++++++++++++++++++--------------
 1 files changed, 50 insertions(+), 28 deletions(-)

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;
     }
 

--
Gitblit v1.10.0