From f41d9f79fac04988420d11ac45b054f1316c3630 Mon Sep 17 00:00:00 2001
From: Gaetan Boismal <gaetan.boismal@forgerock.com>
Date: Thu, 27 Nov 2014 16:05:26 +0000
Subject: [PATCH] OPENDJ-1607 (CR-5295) Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and GrizzlyLDAPConnectionFactory
---
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java | 8
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java | 16
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java | 3
opendj-core/clirr-ignored-api-changes.xml | 65
opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java | 16
opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java | 60
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java | 8
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java | 6
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java | 33
opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java | 316 ++-
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java | 294 --
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java | 8
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java | 17
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java | 7
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java | 6
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java | 20
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPOptions.java | 160 +
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java | 481 +++++
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java | 12
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java | 8
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java | 18
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java | 12
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java | 138
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java | 133
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java | 1124 ++++++++++++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java | 759 -------
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java | 8
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java | 563 ++++++
opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java | 41
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java | 9
opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java | 41
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java | 6
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java | 27
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java | 96
opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java | 8
opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties | 14
opendj-grizzly/src/main/resources/com/forgerock/opendj/grizzly/grizzly.properties | 8
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java | 5
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java | 8
/dev/null | 455 -----
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 71
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java | 6
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java | 70
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java | 14
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java | 18
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java | 7
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java | 123 -
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java | 6
48 files changed, 3,231 insertions(+), 2,101 deletions(-)
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
deleted file mode 100644
index db64344..0000000
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/AuthenticatedConnectionFactory.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * 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 2009-2010 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
- */
-package com.forgerock.opendj.cli;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.forgerock.opendj.ldap.AbstractConnectionWrapper;
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
-import org.forgerock.opendj.ldap.IntermediateResponseHandler;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.util.Reject;
-import org.forgerock.util.promise.AsyncFunction;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.Function;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.SuccessHandler;
-
-import static org.forgerock.util.Utils.*;
-/**
- * An authenticated connection factory can be used to create pre-authenticated
- * connections to a Directory Server.
- * <p>
- * The connections returned by an authenticated connection factory support all
- * operations with the exception of Bind requests. Attempts to perform a Bind
- * will result in an {@code UnsupportedOperationException}.
- * <p>
- * In addition, the returned connections support retrieval of the
- * {@code BindResult} returned from the initial Bind request, or last rebind.
- * <p>
- * Support for connection re-authentication is provided through the
- * {@link #setRebindAllowed} method which, if set to {@code true}, causes
- * subsequent connections created using the factory to support the
- * {@code rebind} method.
- * <p>
- * If the Bind request fails for some reason (e.g. invalid credentials), then
- * the connection attempt will fail and an {@link LdapException} will be thrown.
- */
-public final class AuthenticatedConnectionFactory implements ConnectionFactory {
-
- /**
- * An authenticated connection supports all operations except Bind
- * operations.
- */
- public static final class AuthenticatedConnection extends AbstractConnectionWrapper<Connection> {
-
- private final BindRequest request;
- private volatile BindResult result;
-
- private AuthenticatedConnection(final Connection connection, final BindRequest request,
- final BindResult result) {
- super(connection);
- this.request = request;
- this.result = result;
- }
-
- /*
- * Bind operations are not supported by pre-authenticated connections.
- * These methods will always throw {@code UnsupportedOperationException}.
- */
- /** {@inheritDoc} */
- @Override
- public BindResult bind(BindRequest request) throws LdapException {
- throw new UnsupportedOperationException();
- }
-
- /** {@inheritDoc} */
- @Override
- public BindResult bind(String name, char[] password) throws LdapException {
- throw new UnsupportedOperationException();
- }
-
- /** {@inheritDoc} */
- @Override
- public LdapPromise<BindResult> bindAsync(BindRequest request,
- IntermediateResponseHandler intermediateResponseHandler) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns an unmodifiable view of the Bind result which was returned
- * from the server after authentication.
- *
- * @return The Bind result which was returned from the server after
- * authentication.
- */
- public BindResult getAuthenticatedBindResult() {
- return result;
- }
-
- /**
- * Re-authenticates to the Directory Server using the bind request
- * associated with this connection. If re-authentication fails for some
- * reason then this connection will be automatically closed.
- *
- * @return A promise representing the result of the operation.
- * @throws UnsupportedOperationException
- * If this connection does not support rebind operations.
- * @throws IllegalStateException
- * If this connection has already been closed, i.e. if
- * {@code isClosed() == true}.
- */
- public LdapPromise<BindResult> rebindAsync() {
- if (request == null) {
- throw new UnsupportedOperationException();
- }
-
- return (LdapPromise<BindResult>) connection.bindAsync(request)
- .onSuccess(new SuccessHandler<BindResult>() {
- @Override
- public void handleResult(final BindResult result) {
- // Save the result.
- AuthenticatedConnection.this.result = result;
- }
- }).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(final LdapException error) {
- /*
- * This connection is now unauthenticated so prevent further use.
- */
- connection.close();
- }
- });
- }
-
- /**
- * Returns the string representation of this authenticated connection.
- *
- * @return The string representation of this authenticated connection factory.
- */
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("AuthenticatedConnection(");
- builder.append(connection);
- builder.append(')');
- return builder.toString();
- }
-
- }
-
- private final BindRequest request;
- private final ConnectionFactory parentFactory;
- private boolean allowRebinds;
-
- /**
- * Creates a new authenticated connection factory which will obtain
- * connections using the provided connection factory and immediately perform
- * the provided Bind request.
- *
- * @param factory
- * The connection factory to use for connecting to the Directory
- * Server.
- * @param request
- * The Bind request to use for authentication.
- * @throws NullPointerException
- * If {@code factory} or {@code request} was {@code null}.
- */
- public AuthenticatedConnectionFactory(final ConnectionFactory factory, final BindRequest request) {
- Reject.ifNull(factory, request);
- this.parentFactory = factory;
-
- // FIXME: should do a defensive copy.
- this.request = request;
- }
-
- @Override
- public void close() {
- parentFactory.close();
- }
-
- /** {@inheritDoc} */
- @Override
- public Connection getConnection() throws LdapException {
- final Connection connection = parentFactory.getConnection();
- BindResult bindResult = null;
- try {
- bindResult = connection.bind(request);
- } finally {
- if (bindResult == null) {
- connection.close();
- }
- }
-
- /*
- * If the bind didn't succeed then an exception will have been thrown
- * and this line will not be reached.
- */
- return new AuthenticatedConnection(connection, request, bindResult);
- }
-
- /** {@inheritDoc} */
- @Override
- public Promise<Connection, LdapException> getConnectionAsync() {
- final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
- return parentFactory.getConnectionAsync()
- .thenAsync(
- new AsyncFunction<Connection, BindResult, LdapException>() {
- @Override
- public Promise<BindResult, LdapException> apply(final Connection connection)
- throws LdapException {
- connectionHolder.set(connection);
- return connection.bindAsync(request);
- }
- }
- ).then(
- new Function<BindResult, Connection, LdapException>() {
- @Override
- public Connection apply(BindResult result) throws LdapException {
- // FIXME: should make the result unmodifiable.
- return new AuthenticatedConnection(connectionHolder.get(), request, result);
- }
- },
- new Function<LdapException, Connection, LdapException>() {
- @Override
- public Connection apply(LdapException errorResult) throws LdapException {
- closeSilently(connectionHolder.get());
- throw errorResult;
- }
- }
- );
- }
-
- /**
- * Indicates whether or not rebind requests are to be supported by
- * connections created by this authenticated connection factory.
- * <p>
- * Rebind requests are invoked using the connection's {@code rebind} method
- * which will throw an {@code UnsupportedOperationException} if rebinds are
- * not supported (the default).
- *
- * @return allowRebinds {@code true} if the {@code rebind} operation is to
- * be supported, otherwise {@code false}.
- */
- boolean isRebindAllowed() {
- return allowRebinds;
- }
-
- /**
- * Specifies whether or not rebind requests are to be supported by
- * connections created by this authenticated connection factory.
- * <p>
- * Rebind requests are invoked using the connection's {@code rebind} method
- * which will throw an {@code UnsupportedOperationException} if rebinds are
- * not supported (the default).
- *
- * @param allowRebinds
- * {@code true} if the {@code rebind} operation is to be
- * supported, otherwise {@code false}.
- * @return A reference to this connection factory.
- */
- AuthenticatedConnectionFactory setRebindAllowed(final boolean allowRebinds) {
- this.allowRebinds = allowRebinds;
- return this;
- }
-
- /**
- * Returns the string representation of this authenticated connection factory.
- *
- * @return The string representation of this authenticated connection factory.
- */
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("AuthenticatedConnectionFactory(");
- builder.append(parentFactory);
- builder.append(')');
- return builder.toString();
- }
-
-}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
index fb21e1b..267dfa6 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConnectionFactoryProvider.java
@@ -26,11 +26,6 @@
*/
package com.forgerock.opendj.cli;
-import static com.forgerock.opendj.cli.ArgumentConstants.*;
-import static com.forgerock.opendj.cli.CliMessages.*;
-import static com.forgerock.opendj.cli.CliConstants.DEFAULT_LDAP_PORT;
-import static com.forgerock.opendj.cli.Utils.getHostNameForLdapUrl;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -51,7 +46,6 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.KeyManagers;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.TrustManagers;
@@ -65,6 +59,15 @@
import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.forgerock.opendj.ldap.Connections.*;
+
+import static com.forgerock.opendj.cli.ArgumentConstants.*;
+import static com.forgerock.opendj.cli.CliConstants.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.cli.Utils.*;
+
/**
* A connection factory designed for use with command line tools.
*/
@@ -72,59 +75,61 @@
/** The Logger. */
static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+ private static final long DEFAULT_TIMEOUT_SECONDS = 3;
+
/** The 'hostName' global argument. */
- private StringArgument hostNameArg;
+ private final StringArgument hostNameArg;
/** The 'port' global argument. */
- private IntegerArgument portArg;
+ private final IntegerArgument portArg;
/** The 'bindDN' global argument. */
- private StringArgument bindNameArg;
+ private final StringArgument bindNameArg;
/** The 'bindPasswordFile' global argument. */
- private FileBasedArgument bindPasswordFileArg;
+ private final FileBasedArgument bindPasswordFileArg;
/** The 'password' value. */
private char[] password;
/** The 'bindPassword' global argument. */
- private StringArgument bindPasswordArg;
+ private final StringArgument bindPasswordArg;
/** The 'connectTimeOut' global argument. */
- private IntegerArgument connectTimeOut;
+ private final IntegerArgument connectTimeOut;
/** The 'trustAllArg' global argument. */
- private BooleanArgument trustAllArg;
+ private final BooleanArgument trustAllArg;
/** The 'trustStore' global argument. */
- private StringArgument trustStorePathArg;
+ private final StringArgument trustStorePathArg;
/** The 'trustStorePassword' global argument. */
- private StringArgument trustStorePasswordArg;
+ private final StringArgument trustStorePasswordArg;
/** The 'trustStorePasswordFile' global argument. */
- private FileBasedArgument trustStorePasswordFileArg;
+ private final FileBasedArgument trustStorePasswordFileArg;
/** The 'keyStore' global argument. */
- private StringArgument keyStorePathArg;
+ private final StringArgument keyStorePathArg;
/** The 'keyStorePassword' global argument. */
- private StringArgument keyStorePasswordArg;
+ private final StringArgument keyStorePasswordArg;
/** The 'keyStorePasswordFile' global argument. */
- private FileBasedArgument keyStorePasswordFileArg;
+ private final FileBasedArgument keyStorePasswordFileArg;
/** The 'certNicknameArg' global argument. */
- private StringArgument certNicknameArg;
+ private final StringArgument certNicknameArg;
/** The 'useSSLArg' global argument. */
- private BooleanArgument useSSLArg;
+ private final BooleanArgument useSSLArg;
/** The 'useStartTLSArg' global argument. */
- private BooleanArgument useStartTLSArg;
+ private final BooleanArgument useStartTLSArg;
/** Argument indicating a SASL option. */
- private StringArgument saslOptionArg;
+ private final StringArgument saslOptionArg;
/**
* Whether to request that the server return the authorization ID in the
@@ -422,8 +427,7 @@
}
}
} catch (final Exception e) {
- throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(e.toString()),
- e);
+ throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(e.toString()), e);
}
LDAPOptions options = new LDAPOptions();
@@ -432,7 +436,7 @@
options.setSSLContext(sslContext).setUseStartTLS(useStartTLSArg.isPresent());
}
options.setConnectTimeout(getConnectTimeout(), TimeUnit.MILLISECONDS);
- connFactory = new LDAPConnectionFactory(hostNameArg.getValue(), port, options);
+ connFactory = newLDAPConnectionFactory(hostNameArg.getValue(), port, options);
}
return connFactory;
}
@@ -524,7 +528,9 @@
authenticatedConnFactory = getConnectionFactory();
final BindRequest bindRequest = getBindRequest();
if (bindRequest != null) {
- authenticatedConnFactory = new AuthenticatedConnectionFactory(authenticatedConnFactory, bindRequest);
+ app.setBindRequest(bindRequest);
+ authenticatedConnFactory = newLDAPConnectionFactory(hostNameArg.getValue(), port,
+ new LDAPOptions().setBindRequest(bindRequest).setTimeout(DEFAULT_TIMEOUT_SECONDS, SECONDS));
}
}
return authenticatedConnFactory;
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
index e17c6bc..7bb5284 100755
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
@@ -45,6 +45,7 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.requests.BindRequest;
/**
* This class provides an abstract base class which can be used as the basis of a console-based application.
@@ -65,6 +66,8 @@
private boolean isProgressSuite;
+ private BindRequest bindRequest = null;
+
/**
* Defines the different line styles for output.
*/
@@ -116,6 +119,15 @@
}
/**
+ * Returns the bind request used by this application.
+ *
+ * @return The bind request used by this application.
+ */
+ public BindRequest getBindRequest() {
+ return bindRequest;
+ }
+
+ /**
* Returns the application error stream.
*
* @return The application error stream.
@@ -674,6 +686,10 @@
throw new ClientException(ReturnCode.ERROR_USER_DATA, ERR_TRIES_LIMIT_REACHED.get(maxTries));
}
+ void setBindRequest(final BindRequest bindRequest) {
+ this.bindRequest = bindRequest;
+ }
+
/**
* Inserts line breaks into the provided buffer to wrap text at no more than the specified column width (80).
*
diff --git a/opendj-core/clirr-ignored-api-changes.xml b/opendj-core/clirr-ignored-api-changes.xml
index ac802e6..620688a 100644
--- a/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-core/clirr-ignored-api-changes.xml
@@ -77,8 +77,8 @@
<className>org/forgerock/opendj/ldap/Connections</className>
<differenceType>7005</differenceType>
<method>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(org\.forgerock\.opendj\.ldap\.ConnectionFactory, long, java\.util\.concurrent\.TimeUnit, org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(, java\.util\.concurrent\.ScheduledExecutorService)?\)]</method>
- <to>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(org\.forgerock\.opendj\.ldap\.ConnectionFactory,\s*long,\s*long,\s*java\.util\.concurrent\.TimeUnit(,\s*org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(,\s*java\.util\.concurrent\.ScheduledExecutorService)?)?\)]</to>
- <justification>OPENDJ-1058: Added a timeout parameter to actively shutdown dead connections</justification>
+ <to>%regex[org\.forgerock\.opendj\.ldap\.ConnectionFactory newHeartBeatConnectionFactory\(java\.lang\.String,\s*int(,\s*long,\s*long,\s*java\.util\.concurrent\.TimeUnit(,\s*org\.forgerock\.opendj\.ldap\.requests\.SearchRequest(,\s*java\.util\.concurrent\.ScheduledExecutorService)?)?)?\)]</to>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
</difference>
<difference>
<className>org/forgerock/opendj/ldap/Connections</className>
@@ -114,14 +114,6 @@
<justification>OPENDJ-1270: Changed constructors to only accept InetSocketAddresses instead of more generic SocketAddress</justification>
</difference>
<difference>
- <className>org/forgerock/opendj/ldap/LDAPListener</className>
- <differenceType>7005</differenceType>
- <method>%regex[LDAPListener\(java\.net\.SocketAddress, org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
- <to>%regex[LDAPListener\(java\.net\.InetSocketAddress,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</to>
- <justification>OPENDJ-1270: Changed constructors to only accept InetSocketAddresses instead of more generic SocketAddress</justification>
- </difference>
-
- <difference>
<className>%regex[org/forgerock/opendj/ldap/(LDAPOptions|LDAPListenerOptions)]</className>
<differenceType>7002</differenceType>
<method>%regex[org\.glassfish\.grizzly\.nio\.transport\.TCPNIOTransport getTCPNIOTransport\(\)]</method>
@@ -389,6 +381,59 @@
<method>*toNormalizedString()</method>
<to>*toIrreversibleNormalizedByteString()</to>
<justification>OPENDJ-1585 Function has been renamed to avoid abuse</justification>
+ </difference>
+ <difference>
+ <className>%regex[org/forgerock/opendj/ldap/(Authenticated|HeartBeat)ConnectionFactory]</className>
+ <differenceType>8001</differenceType>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/AuthenticatedConnectionFactory$AuthenticatedConnection</className>
+ <differenceType>8001</differenceType>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/Connections</className>
+ <differenceType>7002</differenceType>
+ <method>%regex[org.forgerock.opendj.ldap.ConnectionFactory new(HeartBeat|Authenticated)ConnectionFactory\(org.forgerock.opendj.ldap.ConnectionFactory(.)*\)]</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPConnectionFactory</className>
+ <differenceType>7002</differenceType>
+ <method>LDAPConnectionFactory(java.lang.String, int)</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPListener</className>
+ <differenceType>7002</differenceType>
+ <method>%regex[LDAPListener\((int|java\.net\.SocketAddress),\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPListener</className>
+ <differenceType>7002</differenceType>
+ <method>%regex[LDAPListener\(java.lang.String,\s*int,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory(,\s*org\.forgerock\.opendj\.ldap\.LDAPListenerOptions)?\)]</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPListener</className>
+ <differenceType>7009</differenceType>
+ <method>LDAPListener(int, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.opendj.ldap.LDAPListenerOptions)</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPListener</className>
+ <differenceType>7005</differenceType>
+ <method>LDAPListener(int, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.opendj.ldap.LDAPListenerOptions)</method>
+ <to>LDAPListener(*)</to>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/LDAPConnectionFactory</className>
+ <differenceType>7009</differenceType>
+ <method>LDAPConnectionFactory(java.lang.String, int, org.forgerock.opendj.ldap.LDAPOptions)</method>
+ <justification>OPENDJ-1607 Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and LDAPConnectionFactory</justification>
</difference>
<difference>
<className>%regex[org/forgerock/opendj/ldap/schema/Schema(Builder)?]</className>
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
deleted file mode 100644
index d15fb31..0000000
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/AuthenticatedConnectionFactory.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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 2009-2010 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
- */
-
-package org.forgerock.opendj.ldap;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.util.promise.AsyncFunction;
-import org.forgerock.util.promise.Function;
-import org.forgerock.util.promise.Promise;
-
-import static org.forgerock.util.Utils.*;
-
-/**
- * An authenticated connection factory can be used to create pre-authenticated
- * connections to a Directory Server.
- * <p>
- * The connections returned by an authenticated connection factory support all
- * operations with the exception of Bind requests. Attempts to perform a Bind
- * will result in an {@code UnsupportedOperationException}.
- * <p>
- * If the Bind request fails for some reason (e.g. invalid credentials), then
- * the connection attempt will fail and an {@link LdapException} will be thrown.
- */
-final class AuthenticatedConnectionFactory implements ConnectionFactory {
-
- /**
- * An authenticated connection supports all operations except Bind
- * operations.
- */
- public static final class AuthenticatedConnection extends AbstractConnectionWrapper<Connection> {
-
- private AuthenticatedConnection(final Connection connection) {
- super(connection);
- }
-
- /**
- * Bind operations are not supported by pre-authenticated connections.
- * These methods will always throw {@code UnsupportedOperationException}.
- */
- public LdapPromise<BindResult> bindAsync(final BindRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super BindResult> resultHandler) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public BindResult bind(BindRequest request) throws LdapException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public BindResult bind(String name, char[] password) throws LdapException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("AuthenticatedConnection(");
- builder.append(connection);
- builder.append(')');
- return builder.toString();
- }
-
- }
-
- private final BindRequest request;
- private final ConnectionFactory parentFactory;
-
- /**
- * Creates a new authenticated connection factory which will obtain
- * connections using the provided connection factory and immediately perform
- * the provided Bind request.
- *
- * @param factory
- * The connection factory to use for connecting to the Directory
- * Server.
- * @param request
- * The Bind request to use for authentication.
- */
- AuthenticatedConnectionFactory(final ConnectionFactory factory, final BindRequest request) {
- this.parentFactory = factory;
-
- // FIXME: should do a defensive copy.
- this.request = request;
- }
-
- @Override
- public void close() {
- // Delegate.
- parentFactory.close();
- }
-
- @Override
- public Connection getConnection() throws LdapException {
- final Connection connection = parentFactory.getConnection();
- boolean bindSucceeded = false;
- try {
- connection.bind(request);
- bindSucceeded = true;
- } finally {
- if (!bindSucceeded) {
- connection.close();
- }
- }
-
- /*
- * If the bind didn't succeed then an exception will have been thrown
- * and this line will not be reached.
- */
- return new AuthenticatedConnection(connection);
- }
-
- @Override
- public Promise<Connection, LdapException> getConnectionAsync() {
- final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
- return parentFactory.getConnectionAsync()
- .thenAsync(
- new AsyncFunction<Connection, BindResult, LdapException>() {
- @Override
- public Promise<BindResult, LdapException> apply(final Connection connection)
- throws LdapException {
- connectionHolder.set(connection);
- return connection.bindAsync(request);
- }
- }
- ).then(
- new Function<BindResult, Connection, LdapException>() {
- @Override
- public Connection apply(BindResult result) throws LdapException {
- return new AuthenticatedConnection(connectionHolder.get());
- }
- },
- new Function<LdapException, Connection, LdapException>() {
- @Override
- public Connection apply(LdapException error) throws LdapException {
- closeSilently(connectionHolder.get());
- throw error;
- }
- }
- );
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("AuthenticatedConnectionFactory(");
- builder.append(parentFactory);
- builder.append(", ");
- builder.append(request);
- builder.append(')');
- return builder.toString();
- }
-}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
index 43fc6f1..e472302 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/Connections.java
@@ -27,51 +27,23 @@
package org.forgerock.opendj.ldap;
-import static org.forgerock.opendj.ldap.RequestHandlerFactoryAdapter.adaptRequestHandler;
-
+import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.util.Reject;
import org.forgerock.util.promise.Promise;
+import static org.forgerock.opendj.ldap.RequestHandlerFactoryAdapter.*;
+
/**
* This class contains methods for creating and manipulating connection
* factories and connections.
*/
public final class Connections {
- /**
- * Creates a new authenticated connection factory which will obtain
- * connections using the provided connection factory and immediately perform
- * the provided Bind request.
- * <p>
- * The connections returned by an authenticated connection factory support
- * all operations with the exception of Bind requests. Attempts to perform a
- * Bind will result in an {@code UnsupportedOperationException}.
- * <p>
- * If the Bind request fails for some reason (e.g. invalid credentials),
- * then the connection attempt will fail and an {@link LdapException}
- * will be thrown.
- *
- * @param factory
- * The connection factory to use for connecting to the Directory
- * Server.
- * @param request
- * The Bind request to use for authentication.
- * @return The new connection pool.
- * @throws NullPointerException
- * If {@code factory} or {@code request} was {@code null}.
- */
- public static ConnectionFactory newAuthenticatedConnectionFactory(
- final ConnectionFactory factory, final BindRequest request) {
- Reject.ifNull(factory, request);
- return new AuthenticatedConnectionFactory(factory, request);
- }
/**
* Creates a new connection pool which creates new connections as needed
@@ -243,113 +215,6 @@
}
/**
- * Creates a new heart-beat connection factory which will create connections
- * using the provided connection factory and periodically ping any created
- * connections in order to detect that they are still alive every 10 seconds
- * using the default scheduler. Connections will be marked as having failed
- * if a heart-beat takes longer than 3 seconds.
- *
- * @param factory
- * The connection factory to use for creating connections.
- * @return The new heart-beat connection factory.
- * @throws NullPointerException
- * If {@code factory} was {@code null}.
- */
- public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory) {
- return new HeartBeatConnectionFactory(factory, 10, 3, TimeUnit.SECONDS, null, null);
- }
-
- /**
- * Creates a new heart-beat connection factory which will create connections
- * using the provided connection factory and periodically ping any created
- * connections in order to detect that they are still alive using the
- * specified frequency and the default scheduler.
- *
- * @param factory
- * The connection factory to use for creating connections.
- * @param interval
- * The interval between keepalive pings.
- * @param timeout
- * The heart-beat timeout after which a connection will be marked
- * as failed.
- * @param unit
- * The time unit for the interval between keepalive pings.
- * @return The new heart-beat connection factory.
- * @throws IllegalArgumentException
- * If {@code interval} was negative.
- * @throws NullPointerException
- * If {@code factory} or {@code unit} was {@code null}.
- */
- public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
- final long interval, final long timeout, final TimeUnit unit) {
- return new HeartBeatConnectionFactory(factory, interval, timeout, unit, null, null);
- }
-
- /**
- * Creates a new heart-beat connection factory which will create connections
- * using the provided connection factory and periodically ping any created
- * connections using the specified search request in order to detect that
- * they are still alive.
- *
- * @param factory
- * The connection factory to use for creating connections.
- * @param interval
- * The interval between keepalive pings.
- * @param timeout
- * The heart-beat timeout after which a connection will be marked
- * as failed.
- * @param unit
- * The time unit for the interval between keepalive pings.
- * @param heartBeat
- * The search request to use for keepalive pings.
- * @return The new heart-beat connection factory.
- * @throws IllegalArgumentException
- * If {@code interval} was negative.
- * @throws NullPointerException
- * If {@code factory}, {@code unit}, or {@code heartBeat} was
- * {@code null}.
- */
- public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
- final long interval, final long timeout, final TimeUnit unit,
- final SearchRequest heartBeat) {
- return new HeartBeatConnectionFactory(factory, interval, timeout, unit, heartBeat, null);
- }
-
- /**
- * Creates a new heart-beat connection factory which will create connections
- * using the provided connection factory and periodically ping any created
- * connections using the specified search request in order to detect that
- * they are still alive.
- *
- * @param factory
- * The connection factory to use for creating connections.
- * @param interval
- * The interval between keepalive pings.
- * @param timeout
- * The heart-beat timeout after which a connection will be marked
- * as failed.
- * @param unit
- * The time unit for the interval between keepalive pings.
- * @param heartBeat
- * The search request to use for keepalive pings.
- * @param scheduler
- * The scheduler which should for periodically sending keepalive
- * pings.
- * @return The new heart-beat connection factory.
- * @throws IllegalArgumentException
- * If {@code interval} was negative.
- * @throws NullPointerException
- * If {@code factory}, {@code unit}, or {@code heartBeat} was
- * {@code null}.
- */
- public static ConnectionFactory newHeartBeatConnectionFactory(final ConnectionFactory factory,
- final long interval, final long timeout, final TimeUnit unit,
- final SearchRequest heartBeat, final ScheduledExecutorService scheduler) {
- return new HeartBeatConnectionFactory(factory, interval, timeout, unit, heartBeat,
- scheduler);
- }
-
- /**
* Creates a new internal client connection which will route requests to the
* provided {@code RequestHandler}.
* <p>
@@ -501,6 +366,181 @@
}
/**
+ * Creates a new LDAP connection factory which can be used to create LDAP
+ * connections to the Directory Server at the provided host and port
+ * number.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @return The connection factory to use for creating connections.
+ */
+ public static LDAPConnectionFactory newLDAPConnectionFactory(final String host, final int port) {
+ return new LDAPConnectionFactory(host, port, new LDAPOptions());
+ }
+
+ /**
+ * Creates a new LDAP connection factory which can be used to create LDAP
+ * connections to the Directory Server at the provided host and port
+ * number.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @param options
+ * The LDAP options to use when creating connections.
+ * @return The connection factory to use for creating connections.
+ */
+ public static LDAPConnectionFactory newLDAPConnectionFactory(final String host, final int port,
+ final LDAPOptions options) {
+ return new LDAPConnectionFactory(host, port, options);
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param port
+ * The port to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {code factory} was {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final int port,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+ return newLDAPListener(null, port, factory, new LDAPListenerOptions());
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param port
+ * The port to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @param options
+ * The LDAP listener options.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {code factory} or {@code options} was {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final int port,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+ final LDAPListenerOptions options) throws IOException {
+ return newLDAPListener(null, port, factory, options);
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param host
+ * The address to listen on.
+ * @param port
+ * The port to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {@code host} or {code factory} was {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final String host, final int port,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+ return newLDAPListener(host, port, factory, new LDAPListenerOptions());
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param host
+ * The address to listen on.
+ * @param port
+ * The port to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @param options
+ * The LDAP listener options.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {@code host}, {code factory}, or {@code options} was
+ * {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final String host, final int port,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+ final LDAPListenerOptions options) throws IOException {
+ return newLDAPListener(host != null ? new InetSocketAddress(host, port) : new InetSocketAddress(port),
+ factory, options);
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param address
+ * The address to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {@code address} or {code factory} was {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final InetSocketAddress address,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
+ return newLDAPListener(address, factory, new LDAPListenerOptions());
+ }
+
+ /**
+ * Creates a new LDAP listener implementation which will listen for LDAP
+ * client connections at the provided address.
+ *
+ * @param address
+ * The address to listen on.
+ * @param factory
+ * The server connection factory which will be used to create
+ * server connections.
+ * @param options
+ * The LDAP listener options.
+ * @throws IOException
+ * If an error occurred while trying to listen on the provided
+ * address.
+ * @throws NullPointerException
+ * If {@code address}, {code factory}, or {@code options} was
+ * {@code null}.
+ * @return The LDAP listener implementation which will listen for LDAP client connections.
+ */
+ public static LDAPListener newLDAPListener(final InetSocketAddress address,
+ final ServerConnectionFactory<LDAPClientContext, Integer> factory,
+ final LDAPListenerOptions options) throws IOException {
+ return new LDAPListener(address, factory, options);
+ }
+
+ /**
* Creates a new load balancer which will obtain connections using the
* provided load balancing algorithm.
*
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
deleted file mode 100644
index ea590dd..0000000
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactory.java
+++ /dev/null
@@ -1,998 +0,0 @@
-/*
- * 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 2009-2010 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
- */
-package org.forgerock.opendj.ldap;
-
-
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.AbstractQueuedSynchronizer;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.ldap.requests.AbandonRequest;
-import org.forgerock.opendj.ldap.requests.AddRequest;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.CompareRequest;
-import org.forgerock.opendj.ldap.requests.DeleteRequest;
-import org.forgerock.opendj.ldap.requests.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-import org.forgerock.opendj.ldap.requests.Requests;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
-import org.forgerock.opendj.ldap.requests.UnbindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.CompareResult;
-import org.forgerock.opendj.ldap.responses.ExtendedResult;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.opendj.ldap.spi.ConnectionState;
-import org.forgerock.opendj.ldap.spi.LdapPromiseImpl;
-import org.forgerock.util.Reject;
-import org.forgerock.util.promise.AsyncFunction;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.SuccessHandler;
-
-import com.forgerock.opendj.util.ReferenceCountedObject;
-import org.forgerock.util.time.TimeService;
-
-import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
-import static org.forgerock.util.Utils.*;
-
-import static com.forgerock.opendj.ldap.CoreMessages.*;
-import static com.forgerock.opendj.util.StaticUtils.*;
-
-/**
- * An heart beat connection factory can be used to create connections that sends
- * a periodic search request to a Directory Server.
- * <p>
- * Before returning new connections to the application this factory will first
- * send an initial heart beat in order to determine that the remote server is
- * responsive. If the heart beat fails or times out then the connection is
- * closed immediately and an error returned to the client.
- * <p>
- * Once a connection has been established successfully (including the initial
- * heart beat), this factory will periodically send heart beats on the
- * connection based on the configured heart beat interval. If the heart beat
- * times out then the server is assumed to be down and an appropriate
- * {@link ConnectionException} generated and published to any registered
- * {@link ConnectionEventListener}s. Note however, that heart beats will only be
- * sent when the connection is determined to be reasonably idle: there is no
- * point in sending heart beats if the connection has recently received a
- * response. A connection is deemed to be idle if no response has been received
- * during a period equivalent to half the heart beat interval.
- * <p>
- * The LDAP protocol specifically precludes clients from performing operations
- * while bind or startTLS requests are being performed. Likewise, a bind or
- * startTLS request will cause active operations to be aborted. This factory
- * coordinates heart beats with bind or startTLS requests, ensuring that they
- * are not performed concurrently. Specifically, bind and startTLS requests are
- * queued up while a heart beat is pending, and heart beats are not sent at all
- * while there are pending bind or startTLS requests.
- */
-final class HeartBeatConnectionFactory implements ConnectionFactory {
- /**
- * A connection that sends heart beats and supports all operations.
- */
- private final class ConnectionImpl extends AbstractAsynchronousConnection implements ConnectionEventListener {
-
- private class LdapPromiseImplWrapper<R> extends LdapPromiseImpl<R> {
- protected LdapPromiseImplWrapper(final LdapPromise<R> wrappedPromise) {
- super(new PromiseImpl<R, LdapException>() {
- @Override
- protected LdapException tryCancel(boolean mayInterruptIfRunning) {
- /*
- * FIXME: if the inner cancel succeeds then this promise will be
- * completed and we can never indicate that this cancel request
- * has succeeded.
- */
- wrappedPromise.cancel(mayInterruptIfRunning);
- return null;
- }
- }, wrappedPromise.getRequestID());
- }
- }
-
- /** The wrapped connection. */
- private final Connection connection;
-
- /**
- * List of pending Bind or StartTLS requests which must be invoked once
- * the current heart beat completes.
- */
- private final Queue<Runnable> pendingBindOrStartTLSRequests =
- new ConcurrentLinkedQueue<Runnable>();
-
- /**
- * List of pending responses for all active operations. These will be
- * signalled if no heart beat is detected within the permitted timeout
- * period.
- */
- private final Queue<ResultHandler<?>> pendingResults =
- new ConcurrentLinkedQueue<ResultHandler<?>>();
-
- /** Internal connection state. */
- private final ConnectionState state = new ConnectionState();
-
- /** Coordinates heart-beats with Bind and StartTLS requests. */
- private final Sync sync = new Sync();
-
- /**
- * Timestamp of last response received (any response, not just heart
- * beats).
- */
- private volatile long lastResponseTimestamp = timeService.now(); // Assume valid at creation.
-
- private ConnectionImpl(final Connection connection) {
- this.connection = connection;
- connection.addConnectionEventListener(this);
- }
-
- @Override
- public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
- return connection.abandonAsync(request);
- }
-
- @Override
- public LdapPromise<Result> addAsync(final AddRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- return timestampPromise(connection.addAsync(request, intermediateResponseHandler));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public void addConnectionEventListener(final ConnectionEventListener listener) {
- state.addConnectionEventListener(listener);
- }
-
- @Override
- public LdapPromise<BindResult> bindAsync(final BindRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- if (sync.tryLockShared()) {
- // Fast path
- return timestampBindOrStartTLSPromise(connection.bindAsync(request, intermediateResponseHandler));
- } else {
- return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() {
- @Override
- public Promise<BindResult, LdapException> apply(Void value) throws LdapException {
- return timestampBindOrStartTLSPromise(connection.bindAsync(request,
- intermediateResponseHandler));
- }
- });
- }
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public void close() {
- handleConnectionClosed();
- connection.close();
- }
-
- @Override
- public void close(final UnbindRequest request, final String reason) {
- handleConnectionClosed();
- connection.close(request, reason);
- }
-
- @Override
- public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- return timestampPromise(connection.compareAsync(request, intermediateResponseHandler));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public LdapPromise<Result> deleteAsync(final DeleteRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- return timestampPromise(connection.deleteAsync(request, intermediateResponseHandler));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- if (isStartTLSRequest(request)) {
- if (sync.tryLockShared()) {
- // Fast path
- return timestampBindOrStartTLSPromise(connection.extendedRequestAsync(request,
- intermediateResponseHandler));
- } else {
- return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() {
- @Override
- public Promise<R, LdapException> apply(Void value) throws LdapException {
- return timestampBindOrStartTLSPromise(connection.extendedRequestAsync(
- request, intermediateResponseHandler));
- }
- });
- }
- } else {
- return timestampPromise(connection.extendedRequestAsync(request, intermediateResponseHandler));
- }
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public void handleConnectionClosed() {
- if (state.notifyConnectionClosed()) {
- failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED,
- HBCF_CONNECTION_CLOSED_BY_CLIENT.get()));
- synchronized (validConnections) {
- connection.removeConnectionEventListener(this);
- validConnections.remove(this);
- if (validConnections.isEmpty()) {
- // This is the last active connection, so stop the heartbeat.
- heartBeatFuture.cancel(false);
- }
- }
- releaseScheduler();
- }
- }
-
- @Override
- public void handleConnectionError(final boolean isDisconnectNotification, final LdapException error) {
- if (state.notifyConnectionError(isDisconnectNotification, error)) {
- failPendingResults(error);
- }
- }
-
- @Override
- public void handleUnsolicitedNotification(final ExtendedResult notification) {
- timestamp(notification);
- state.notifyUnsolicitedNotification(notification);
- }
-
- @Override
- public boolean isClosed() {
- return state.isClosed();
- }
-
- @Override
- public boolean isValid() {
- return state.isValid() && connection.isValid();
- }
-
- @Override
- public LdapPromise<Result> modifyAsync(final ModifyRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- return timestampPromise(connection.modifyAsync(request, intermediateResponseHandler));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- if (checkState()) {
- return timestampPromise(connection.modifyDNAsync(request, intermediateResponseHandler));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public void removeConnectionEventListener(final ConnectionEventListener listener) {
- state.removeConnectionEventListener(listener);
- }
-
- @Override
- public LdapPromise<Result> searchAsync(final SearchRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final SearchResultHandler searchHandler) {
- if (checkState()) {
- final AtomicBoolean searchDone = new AtomicBoolean();
- final SearchResultHandler entryHandler = new SearchResultHandler() {
- @Override
- public synchronized boolean handleEntry(SearchResultEntry entry) {
- if (!searchDone.get()) {
- timestamp(entry);
- if (searchHandler != null) {
- searchHandler.handleEntry(entry);
- }
- }
- return true;
- }
-
- @Override
- public synchronized boolean handleReference(SearchResultReference reference) {
- if (!searchDone.get()) {
- timestamp(reference);
- if (searchHandler != null) {
- searchHandler.handleReference(reference);
- }
- }
- return true;
- }
- };
-
- return timestampPromise(connection.searchAsync(request,
- intermediateResponseHandler, entryHandler).onSuccessOrFailure(new Runnable() {
- @Override
- public void run() {
- searchDone.getAndSet(true);
- }
- }));
- } else {
- return newConnectionErrorPromise();
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("HeartBeatConnection(");
- builder.append(connection);
- builder.append(')');
- return builder.toString();
- }
-
- private void checkForHeartBeat() {
- if (sync.isHeld()) {
- /*
- * A heart beat or bind/startTLS is still in progress, but it
- * should have completed by now. Let's avoid aggressively
- * terminating the connection, because the heart beat may simply
- * have been delayed by a sudden surge of activity. Therefore,
- * only flag the connection as failed if no activity has been
- * seen on the connection since the heart beat was sent.
- */
- final long currentTimeMillis = timeService.now();
- if (lastResponseTimestamp < (currentTimeMillis - timeoutMS)) {
- logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", connection));
- handleConnectionError(false, newHeartBeatTimeoutError());
- }
- }
- }
-
- private boolean checkState() {
- return state.getConnectionError() == null;
- }
-
- private void failPendingResults(final LdapException error) {
- /*
- * Peek instead of pool because notification is responsible for
- * removing the element from the queue.
- */
- ResultHandler<?> pendingResult;
- while ((pendingResult = pendingResults.peek()) != null) {
- pendingResult.handleError(error);
- }
- }
-
- private void flushPendingBindOrStartTLSRequests() {
- if (!pendingBindOrStartTLSRequests.isEmpty()) {
- /*
- * The pending requests will acquire the shared lock, but we take
- * it here anyway to ensure that pending requests do not get blocked.
- */
- if (sync.tryLockShared()) {
- try {
- Runnable pendingRequest;
- while ((pendingRequest = pendingBindOrStartTLSRequests.poll()) != null) {
- // Dispatch the waiting request. This will not block.
- pendingRequest.run();
- }
- } finally {
- sync.unlockShared();
- }
- }
- }
- }
-
- private boolean isStartTLSRequest(final ExtendedRequest<?> request) {
- return request.getOID().equals(StartTLSExtendedRequest.OID);
- }
-
- private <R> LdapPromise<R> newConnectionErrorPromise() {
- return newFailedLdapPromise(state.getConnectionError());
- }
-
- private void releaseBindOrStartTLSLock() {
- sync.unlockShared();
- }
-
- private void releaseHeartBeatLock() {
- sync.unlockExclusively();
- flushPendingBindOrStartTLSRequests();
- }
-
- /**
- * Sends a heart beat on this connection if required to do so.
- *
- * @return {@code true} if a heart beat was sent, otherwise {@code false}.
- */
- private boolean sendHeartBeat() {
- /*
- * Don't attempt to send a heart beat if the connection has already
- * failed.
- */
- if (!state.isValid()) {
- return false;
- }
-
- /*
- * Only send the heart beat if the connection has been idle for some
- * time.
- */
- final long currentTimeMillis = timeService.now();
- if (currentTimeMillis < (lastResponseTimestamp + minDelayMS)) {
- return false;
- }
-
- /*
- * Don't send a heart beat if there is already a heart beat, bind,
- * or startTLS in progress. Note that the bind/startTLS response
- * will update the lastResponseTimestamp as if it were a heart beat.
- */
- if (sync.tryLockExclusively()) {
- try {
- connection.searchAsync(heartBeatRequest, new SearchResultHandler() {
- @Override
- public boolean handleEntry(final SearchResultEntry entry) {
- timestamp(entry);
- return true;
- }
-
- @Override
- public boolean handleReference(final SearchResultReference reference) {
- timestamp(reference);
- return true;
- }
- }).onSuccess(new SuccessHandler<Result>() {
- @Override
- public void handleResult(Result result) {
- timestamp(result);
- releaseHeartBeatLock();
- }
- }).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(LdapException error) {
- /*
- * Connection failure will be handled by connection
- * event listener. Ignore cancellation errors since
- * these indicate that the heart beat was aborted by
- * a client-side close.
- */
- if (!(error instanceof CancelledResultException)) {
- /*
- * Log at debug level to avoid polluting the
- * logs with benign password policy related
- * errors. See OPENDJ-1168 and OPENDJ-1167.
- */
- logger.debug(LocalizableMessage.raw("Heartbeat failed for connection factory '%s'",
- factory, error));
- timestamp(error);
- }
- releaseHeartBeatLock();
- }
- });
- } catch (final IllegalStateException e) {
- /*
- * This may happen when we attempt to send the heart beat
- * just after the connection is closed but before we are
- * notified.
- */
-
- /*
- * Release the lock because we're never going to get a
- * response.
- */
- releaseHeartBeatLock();
- }
- }
- /*
- * Indicate that a the heartbeat should be checked even if a
- * bind/startTLS is in progress, since these operations will
- * effectively act as the heartbeat.
- */
- return true;
- }
-
- private <R> R timestamp(final R response) {
- if (!(response instanceof ConnectionException)) {
- lastResponseTimestamp = timeService.now();
- }
- return response;
- }
-
- private <R extends Result> LdapPromise<R> enqueueBindOrStartTLSPromise(
- AsyncFunction<Void, R, LdapException> doRequest) {
- /*
- * A heart beat must be in progress so create a runnable task which
- * will be executed when the heart beat completes.
- */
- final LdapPromiseImpl<Void> promise = newLdapPromiseImpl();
- final LdapPromise<R> result = promise.thenAsync(doRequest);
-
- // Enqueue and flush if the heart beat has completed in the mean
- // time.
- pendingBindOrStartTLSRequests.offer(new Runnable() {
- @Override
- public void run() {
- // FIXME: Handle cancel chaining.
- if (!result.isCancelled()) {
- sync.lockShared(); // Will not block.
- promise.handleResult((Void) null);
- }
- }
- });
- flushPendingBindOrStartTLSRequests();
- return result;
- }
-
- private <R extends Result> LdapPromise<R> timestampPromise(LdapPromise<R> wrappedPromise) {
- final LdapPromiseImpl<R> outerPromise = new LdapPromiseImplWrapper<R>(wrappedPromise);
- pendingResults.add(outerPromise);
- wrappedPromise.onSuccess(new SuccessHandler<R>() {
- @Override
- public void handleResult(R result) {
- outerPromise.handleResult(result);
- timestamp(result);
- }
- }).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(LdapException error) {
- outerPromise.handleError(error);
- timestamp(error);
- }
- });
- outerPromise.onSuccessOrFailure(new Runnable() {
- @Override
- public void run() {
- pendingResults.remove(outerPromise);
- }
- });
- if (!checkState()) {
- outerPromise.handleError(state.getConnectionError());
- }
- return outerPromise;
- }
-
- private <R extends Result> LdapPromise<R> timestampBindOrStartTLSPromise(LdapPromise<R> wrappedPromise) {
- return timestampPromise(wrappedPromise).onSuccessOrFailure(new Runnable() {
- @Override
- public void run() {
- releaseBindOrStartTLSLock();
- }
- });
- }
- }
-
- /**
- * This synchronizer prevents Bind or StartTLS operations from being
- * processed concurrently with heart-beats. This is required because the
- * LDAP protocol specifically states that servers receiving a Bind operation
- * should either wait for existing operations to complete or abandon them.
- * The same presumably applies to StartTLS operations. Note that concurrent
- * bind/StartTLS operations are not permitted.
- * <p>
- * This connection factory only coordinates Bind and StartTLS requests with
- * heart-beats. It does not attempt to prevent or control attempts to send
- * multiple concurrent Bind or StartTLS operations, etc.
- * <p>
- * This synchronizer can be thought of as cross between a read-write lock
- * and a semaphore. Unlike a read-write lock there is no requirement that a
- * thread releasing a lock must hold it. In addition, this synchronizer does
- * not support reentrancy. A thread attempting to acquire exclusively more
- * than once will deadlock, and a thread attempting to acquire shared more
- * than once will succeed and be required to release an equivalent number of
- * times.
- * <p>
- * The synchronizer has three states:
- * <ul>
- * <li>UNLOCKED(0) - the synchronizer may be acquired shared or exclusively
- * <li>LOCKED_EXCLUSIVELY(-1) - the synchronizer is held exclusively and
- * cannot be acquired shared or exclusively. An exclusive lock is held while
- * a heart beat is in progress
- * <li>LOCKED_SHARED(>0) - the synchronizer is held shared and cannot be
- * acquired exclusively. N shared locks are held while N Bind or StartTLS
- * operations are in progress.
- * </ul>
- */
- private static final class Sync extends AbstractQueuedSynchronizer {
- private static final int LOCKED_EXCLUSIVELY = -1;
- /** Keep compiler quiet. */
- private static final long serialVersionUID = -3590428415442668336L;
-
- /** Lock states. Positive values indicate that the shared lock is taken. */
- private static final int UNLOCKED = 0; // initial state
-
- @Override
- protected boolean isHeldExclusively() {
- return getState() == LOCKED_EXCLUSIVELY;
- }
-
- boolean isHeld() {
- return getState() != 0;
- }
-
- @Override
- protected boolean tryAcquire(final int ignored) {
- if (compareAndSetState(UNLOCKED, LOCKED_EXCLUSIVELY)) {
- setExclusiveOwnerThread(Thread.currentThread());
- return true;
- }
- return false;
- }
-
- @Override
- protected int tryAcquireShared(final int readers) {
- for (;;) {
- final int state = getState();
- if (state == LOCKED_EXCLUSIVELY) {
- return LOCKED_EXCLUSIVELY; // failed
- }
- final int newState = state + readers;
- if (compareAndSetState(state, newState)) {
- return newState; // succeeded + more readers allowed
- }
- }
- }
-
- @Override
- protected boolean tryRelease(final int ignored) {
- if (getState() != LOCKED_EXCLUSIVELY) {
- throw new IllegalMonitorStateException();
- }
- setExclusiveOwnerThread(null);
- setState(UNLOCKED);
- return true;
- }
-
- @Override
- protected boolean tryReleaseShared(final int ignored) {
- for (;;) {
- final int state = getState();
- if (state == UNLOCKED || state == LOCKED_EXCLUSIVELY) {
- throw new IllegalMonitorStateException();
- }
- final int newState = state - 1;
- if (compareAndSetState(state, newState)) {
- /*
- * We could always return true here, but since there cannot
- * be waiting readers we can specialize for waiting writers.
- */
- return newState == UNLOCKED;
- }
- }
- }
-
- void lockShared() {
- acquireShared(1);
- }
-
- boolean tryLockExclusively() {
- return tryAcquire(0 /* unused */);
- }
-
- boolean tryLockShared() {
- return tryAcquireShared(1) > 0;
- }
-
- void unlockExclusively() {
- release(0 /* unused */);
- }
-
- void unlockShared() {
- releaseShared(0 /* unused */);
- }
-
- }
-
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
- /**
- * Default heart beat which will target the root DSE but not return any
- * results.
- */
- private static final SearchRequest DEFAULT_SEARCH = Requests.newSearchRequest("",
- SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1");
-
- /**
- * This is package private in order to allow unit tests to inject fake time
- * stamps.
- */
- TimeService timeService = TimeService.SYSTEM;
-
- /**
- * Scheduled task which checks that all heart beats have been received
- * within the timeout period.
- */
- private final Runnable checkHeartBeatRunnable = new Runnable() {
- @Override
- public void run() {
- for (final ConnectionImpl connection : getValidConnections()) {
- connection.checkForHeartBeat();
- }
- }
- };
-
- /**
- * Underlying connection factory.
- */
- private final ConnectionFactory factory;
-
- /**
- * The heartbeat scheduled future - which may be null if heartbeats are not
- * being sent (no valid connections).
- */
- private ScheduledFuture<?> heartBeatFuture;
-
- /**
- * The heartbeat search request.
- */
- private final SearchRequest heartBeatRequest;
-
- /**
- * The interval between successive heartbeats.
- */
- private final long interval;
- private final TimeUnit intervalUnit;
-
- /**
- * Flag which indicates whether this factory has been closed.
- */
- private final AtomicBoolean isClosed = new AtomicBoolean();
-
- /**
- * The minimum amount of time the connection should remain idle (no
- * responses) before starting to send heartbeats.
- */
- private final long minDelayMS;
-
- /**
- * Prevents the scheduler being released when there are remaining references
- * (this factory or any connections). It is initially set to 1 because this
- * factory has a reference.
- */
- private final AtomicInteger referenceCount = new AtomicInteger(1);
-
- /**
- * The heartbeat scheduler.
- */
- private final ReferenceCountedObject<ScheduledExecutorService>.Reference scheduler;
-
-
- /**
- * Scheduled task which sends heart beats for all valid idle connections.
- */
- private final Runnable sendHeartBeatRunnable = new Runnable() {
- @Override
- public void run() {
- boolean heartBeatSent = false;
- for (final ConnectionImpl connection : getValidConnections()) {
- heartBeatSent |= connection.sendHeartBeat();
- }
- if (heartBeatSent) {
- scheduler.get().schedule(checkHeartBeatRunnable, timeoutMS, TimeUnit.MILLISECONDS);
- }
- }
- };
-
- /**
- * The heartbeat timeout in milli-seconds. The connection will be marked as
- * failed if no heartbeat response is received within the timeout.
- */
- private final long timeoutMS;
-
- /**
- * List of valid connections to which heartbeats will be sent.
- */
- private final List<ConnectionImpl> validConnections = new LinkedList<ConnectionImpl>();
-
- HeartBeatConnectionFactory(final ConnectionFactory factory, final long interval,
- final long timeout, final TimeUnit unit, final SearchRequest heartBeat,
- final ScheduledExecutorService scheduler) {
- Reject.ifNull(factory, unit);
- Reject.ifFalse(interval >= 0, "negative interval");
- Reject.ifFalse(timeout >= 0, "negative timeout");
-
- this.heartBeatRequest = heartBeat != null ? heartBeat : DEFAULT_SEARCH;
- this.interval = interval;
- this.intervalUnit = unit;
- this.factory = factory;
- this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(scheduler);
- this.timeoutMS = unit.toMillis(timeout);
- this.minDelayMS = unit.toMillis(interval) / 2;
- }
-
- @Override
- public void close() {
- if (isClosed.compareAndSet(false, true)) {
- synchronized (validConnections) {
- if (!validConnections.isEmpty()) {
- logger.debug(LocalizableMessage.raw(
- "HeartbeatConnectionFactory '%s' is closing while %d active connections remain",
- this, validConnections.size()));
- }
- }
- releaseScheduler();
- factory.close();
- }
- }
-
- private void releaseScheduler() {
- if (referenceCount.decrementAndGet() == 0) {
- scheduler.release();
- }
- }
-
- private void acquireScheduler() {
- /*
- * If the factory is not closed then we need to prevent the scheduler
- * from being released while the connection attempt is in progress.
- */
- referenceCount.incrementAndGet();
- if (isClosed.get()) {
- releaseScheduler();
- throw new IllegalStateException("Attempted to get a connection after factory close");
- }
- }
-
- @Override
- public Connection getConnection() throws LdapException {
- /*
- * Immediately send a heart beat in order to determine if the connected
- * server is responding.
- */
- acquireScheduler(); // Protect scheduler.
- Connection connection = null;
- try {
- connection = factory.getConnection();
- connection.searchAsync(heartBeatRequest, null).getOrThrow(timeoutMS, TimeUnit.MILLISECONDS);
- return registerConnection(new ConnectionImpl(connection));
- } catch (final Exception e) {
- closeSilently(connection);
- releaseScheduler();
- throw adaptHeartBeatError(e);
- }
- }
-
- @Override
- public Promise<Connection, LdapException> getConnectionAsync() {
- acquireScheduler(); // Protect scheduler.
-
- final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
- final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
-
- // Request a connection and return the promise representing the heartbeat.
- factory.getConnectionAsync().thenAsync(new AsyncFunction<Connection, Result, LdapException>() {
- @Override
- public Promise<Result, LdapException> apply(final Connection connection) {
- // Save the connection for later once the heart beat completes.
- connectionHolder.set(connection);
- scheduler.get().schedule(new Runnable() {
- @Override
- public void run() {
- if (promise.tryHandleError(newHeartBeatTimeoutError())) {
- closeSilently(connectionHolder.get());
- releaseScheduler();
- }
- }
- }, timeoutMS, TimeUnit.MILLISECONDS);
- return connection.searchAsync(heartBeatRequest, null);
- }
- }).onSuccess(new SuccessHandler<Result>() {
- @Override
- public void handleResult(Result result) {
- final Connection connection = connectionHolder.get();
- final ConnectionImpl connectionImpl = new ConnectionImpl(connection);
- if (!promise.tryHandleResult(registerConnection(connectionImpl))) {
- connectionImpl.close();
- }
- }
- }).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(LdapException error) {
- if (promise.tryHandleError(adaptHeartBeatError(error))) {
- closeSilently(connectionHolder.get());
- releaseScheduler();
- }
- }
- });
-
- return promise;
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("HeartBeatConnectionFactory(");
- builder.append(factory);
- builder.append(')');
- return builder.toString();
- }
-
- private Connection registerConnection(final ConnectionImpl heartBeatConnection) {
- synchronized (validConnections) {
- if (validConnections.isEmpty()) {
- /* This is the first active connection, so start the heart beat. */
- heartBeatFuture =
- scheduler.get().scheduleWithFixedDelay(sendHeartBeatRunnable, 0, interval, intervalUnit);
- }
- validConnections.add(heartBeatConnection);
- return heartBeatConnection;
- }
- }
-
- private LdapException adaptHeartBeatError(final Exception error) {
- if (error instanceof ConnectionException) {
- return (LdapException) error;
- } else if (error instanceof TimeoutResultException || error instanceof TimeoutException) {
- return newHeartBeatTimeoutError();
- } else if (error instanceof InterruptedException) {
- return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, error);
- } else {
- return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_FAILED.get(), error);
- }
- }
-
- private ConnectionImpl[] getValidConnections() {
- final ConnectionImpl[] tmp;
- synchronized (validConnections) {
- tmp = validConnections.toArray(new ConnectionImpl[0]);
- }
- return tmp;
- }
-
- private LdapException newHeartBeatTimeoutError() {
- return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_TIMEOUT
- .get(timeoutMS));
- }
-}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
index 2c9ce2b..427723a 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -59,24 +59,6 @@
* The host name.
* @param port
* The port number.
- * @throws NullPointerException
- * If {@code host} was {@code null}.
- * @throws ProviderNotFoundException if no provider is available or if the
- * provider requested using options is not found.
- */
- public LDAPConnectionFactory(final String host, final int port) {
- this(host, port, new LDAPOptions());
- }
-
- /**
- * Creates a new LDAP connection factory which can be used to create LDAP
- * connections to the Directory Server at the provided host and port
- * number.
- *
- * @param host
- * The host name.
- * @param port
- * The port number.
* @param options
* The LDAP options to use when creating connections.
* @throws NullPointerException
@@ -84,7 +66,7 @@
* @throws ProviderNotFoundException if no provider is available or if the
* provider requested using options is not found.
*/
- public LDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
+ LDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
Reject.ifNull(host, options);
this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
options.getProviderClassLoader());
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
index 736de1d..8e1748d 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
@@ -103,74 +103,7 @@
/**
* Transport provider that provides the implementation of this listener.
*/
- private TransportProvider provider;
-
- /**
- * Creates a new LDAP listener implementation which will listen for LDAP
- * client connections at the provided address.
- *
- * @param port
- * The port to listen on.
- * @param factory
- * The server connection factory which will be used to create
- * server connections.
- * @throws IOException
- * If an error occurred while trying to listen on the provided
- * address.
- * @throws NullPointerException
- * If {code factory} was {@code null}.
- */
- public LDAPListener(final int port,
- final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
- this(port, factory, new LDAPListenerOptions());
- }
-
- /**
- * Creates a new LDAP listener implementation which will listen for LDAP
- * client connections at the provided address.
- *
- * @param port
- * The port to listen on.
- * @param factory
- * The server connection factory which will be used to create
- * server connections.
- * @param options
- * The LDAP listener options.
- * @throws IOException
- * If an error occurred while trying to listen on the provided
- * address.
- * @throws NullPointerException
- * If {code factory} or {@code options} was {@code null}.
- */
- public LDAPListener(final int port,
- final ServerConnectionFactory<LDAPClientContext, Integer> factory,
- final LDAPListenerOptions options) throws IOException {
- Reject.ifNull(factory, options);
- final InetSocketAddress address = new InetSocketAddress(port);
- this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
- options.getProviderClassLoader());
- this.impl = provider.getLDAPListener(address, factory, options);
- }
-
- /**
- * Creates a new LDAP listener implementation which will listen for LDAP
- * client connections at the provided address.
- *
- * @param address
- * The address to listen on.
- * @param factory
- * The server connection factory which will be used to create
- * server connections.
- * @throws IOException
- * If an error occurred while trying to listen on the provided
- * address.
- * @throws NullPointerException
- * If {@code address} or {code factory} was {@code null}.
- */
- public LDAPListener(final InetSocketAddress address,
- final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
- this(address, factory, new LDAPListenerOptions());
- }
+ private final TransportProvider provider;
/**
* Creates a new LDAP listener implementation which will listen for LDAP
@@ -190,7 +123,7 @@
* If {@code address}, {code factory}, or {@code options} was
* {@code null}.
*/
- public LDAPListener(final InetSocketAddress address,
+ LDAPListener(final InetSocketAddress address,
final ServerConnectionFactory<LDAPClientContext, Integer> factory,
final LDAPListenerOptions options) throws IOException {
Reject.ifNull(address, factory, options);
@@ -200,58 +133,6 @@
}
/**
- * Creates a new LDAP listener implementation which will listen for LDAP
- * client connections at the provided address.
- *
- * @param host
- * The address to listen on.
- * @param port
- * The port to listen on.
- * @param factory
- * The server connection factory which will be used to create
- * server connections.
- * @throws IOException
- * If an error occurred while trying to listen on the provided
- * address.
- * @throws NullPointerException
- * If {@code host} or {code factory} was {@code null}.
- */
- public LDAPListener(final String host, final int port,
- final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
- this(host, port, factory, new LDAPListenerOptions());
- }
-
- /**
- * Creates a new LDAP listener implementation which will listen for LDAP
- * client connections at the provided address.
- *
- * @param host
- * The address to listen on.
- * @param port
- * The port to listen on.
- * @param factory
- * The server connection factory which will be used to create
- * server connections.
- * @param options
- * The LDAP listener options.
- * @throws IOException
- * If an error occurred while trying to listen on the provided
- * address.
- * @throws NullPointerException
- * If {@code host}, {code factory}, or {@code options} was
- * {@code null}.
- */
- public LDAPListener(final String host, final int port,
- final ServerConnectionFactory<LDAPClientContext, Integer> factory,
- final LDAPListenerOptions options) throws IOException {
- Reject.ifNull(host, factory, options);
- final InetSocketAddress address = new InetSocketAddress(host, port);
- this.provider = getProvider(TransportProvider.class, options.getTransportProvider(),
- options.getProviderClassLoader());
- this.impl = provider.getLDAPListener(address, factory, options);
- }
-
- /**
* Closes this LDAP connection listener.
*/
@Override
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 a3c5737..5fd488c 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
@@ -22,19 +22,26 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012-2013 ForgeRock AS.
+ * Portions copyright 2012-2014 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.util.Reject;
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+
/**
* Common options for LDAP client connections.
* <p>
@@ -63,6 +70,28 @@
DEFAULT_CONNECT_TIMEOUT = getIntProperty("org.forgerock.opendj.io.connectTimeout", 5000);
}
+ /** Default heart beat search request. */
+ private static final SearchRequest DEFAULT_HEART_BEAT_SEARCH_REQUEST =
+ unmodifiableSearchRequest(newSearchRequest("", SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1"));
+
+ /** The heartbeat search request. Default request will target the root DSE but not return any results. */
+ private SearchRequest heartBeatSearchRequest = DEFAULT_HEART_BEAT_SEARCH_REQUEST;
+
+ /** The heartbeat bind request, if any. Default value is {@code null}. */
+ private BindRequest bindRequest;
+
+ /** The interval between successive heartbeats (milliseconds). Default value is 0 (means heartbeat disabled). */
+ private long heartBeatIntervalInMillis = 0;
+
+ /**
+ * The heartbeat scheduler.
+ * Default value is {@code null}.
+ * If no scheduler is provided while creating new {@link HeartBeatConnectionFactory},
+ * the factory will use his default one.
+ */
+ private ScheduledExecutorService heartBeatScheduler;
+
+ /** General Options. */
private SSLContext sslContext;
private boolean useStartTLS;
private long timeoutInMillis = DEFAULT_TIMEOUT;
@@ -93,6 +122,10 @@
this.enabledCipherSuites.addAll(options.getEnabledCipherSuites());
this.enabledProtocols.addAll(options.getEnabledProtocols());
this.connectTimeoutInMillis = options.connectTimeoutInMillis;
+ this.bindRequest = options.bindRequest;
+ this.heartBeatIntervalInMillis = options.heartBeatIntervalInMillis;
+ this.heartBeatSearchRequest = options.heartBeatSearchRequest;
+ this.heartBeatScheduler = options.heartBeatScheduler;
}
/**
@@ -136,6 +169,21 @@
}
/**
+ * Returns the bind request which will be sent as soon as the connection
+ * attempt succeeds, or null if unauthenticated connections should be
+ * returned.
+ * <p>
+ * By default, the bind request is {@code null}.
+ *
+ * @return The bind request which will be sent as soon as the connection
+ * attempt succeeds, or null if unauthenticated connections should
+ * be returned.
+ */
+ public BindRequest getBindRequest() {
+ return bindRequest;
+ }
+
+ /**
* 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
@@ -150,7 +198,7 @@
* timeout.
*/
public long getConnectTimeout(final TimeUnit unit) {
- return unit.convert(connectTimeoutInMillis, TimeUnit.MILLISECONDS);
+ return unit.convert(connectTimeoutInMillis, MILLISECONDS);
}
/**
@@ -176,6 +224,45 @@
}
/**
+ * Returns the interval between successive heart beat in the specified unit.
+ * <p>
+ * By default, heart beat interval value is 0, indicating that heart beat
+ * support will be disabled in the {@link LDAPConnectionFactory}.
+ *
+ * @param unit
+ * The time unit.
+ * @return The interval between keepalive pings in the specified time unit.
+ */
+ public long getHeartBeatInterval(final TimeUnit unit) {
+ return unit.convert(heartBeatIntervalInMillis, MILLISECONDS);
+ }
+
+ /**
+ * Returns the scheduler which should periodically send keepalive pings.
+ * <p>
+ * By default, the scheduler is {@code null}, indicating that the associated
+ * {@link LDAPConnectionFactory} will use the default one.
+ *
+ * @return Returns the {@link ScheduledExecutorService} instance which
+ * should periodically send keepalive pings.
+ */
+ public ScheduledExecutorService getHeartBeatScheduler() {
+ return heartBeatScheduler;
+ }
+
+ /**
+ * Returns the search request to use for keepalive pings.
+ * <p>
+ * The default {@link SearchRequest} will target the root DSE but not return
+ * any results.
+ *
+ * @return the {@link SearchRequest} to use for keepalive pings.
+ */
+ public SearchRequest getHeartBeatSearchRequest() {
+ return heartBeatSearchRequest;
+ }
+
+ /**
* Returns the SSL context which will be used when initiating connections
* with the Directory Server.
* <p>
@@ -208,7 +295,25 @@
* timeout.
*/
public long getTimeout(final TimeUnit unit) {
- return unit.convert(timeoutInMillis, TimeUnit.MILLISECONDS);
+ return unit.convert(timeoutInMillis, MILLISECONDS);
+ }
+
+ /**
+ * Sets the bind request which will be sent as soon as the
+ * connection attempt succeeds.
+ * <p>
+ * By default, the bind request is {@code null}, indicating that
+ * unauthenticated connections should be returned by the associated
+ * {@link LDAPConnectionFactory}
+ *
+ * @param bindRequest
+ * The bind request which will be sent as soon as the connection
+ * attempt succeeds.
+ * @return A reference to this set of options.
+ */
+ public LDAPOptions setBindRequest(final BindRequest bindRequest) {
+ this.bindRequest = bindRequest;
+ return this;
}
/**
@@ -233,6 +338,55 @@
}
/**
+ * Sets the interval between successive heartbeats in the specified unit.
+ * <p>
+ * By default, heart beat interval value is 0, indicating that heart beat
+ * will be disabled in the {@link LDAPConnectionFactory}.
+ *
+ * @param interval
+ * The interval between keepalive pings in the specified time unit.
+ * @param unit
+ * The time unit of the given interval.
+ * @return A reference to this set of options.
+ */
+ public LDAPOptions setHeartBeatInterval(final long interval, final TimeUnit unit) {
+ Reject.ifTrue(interval < 0, "negative timeout");
+ this.heartBeatIntervalInMillis = MILLISECONDS.convert(interval, unit);
+ return this;
+ }
+
+ /**
+ * The scheduler which should for periodically sending keepalive pings.
+ * <p>
+ * By default, the scheduler is {@code null}, indicating that the associated
+ * {@link LDAPConnectionFactory} will use the default one.
+ *
+ * @param scheduler
+ * The scheduler which should for periodically sending keepalive
+ * pings.
+ * @return A reference to this set of options.
+ */
+ public LDAPOptions setHeartBeatScheduler(final ScheduledExecutorService scheduler) {
+ this.heartBeatScheduler = scheduler;
+ return this;
+ }
+
+ /**
+ * Sets the search request to use for keepalive pings.
+ * <p>
+ * The default {@link SearchRequest} will target the root DSE but not return
+ * any results.
+ *
+ * @param heartBeatRequest
+ * The search request to use for keepalive pings.
+ * @return A reference to this set of options.
+ */
+ public LDAPOptions setHeartBeatSearchRequest(final SearchRequest heartBeatRequest) {
+ this.heartBeatSearchRequest = heartBeatRequest;
+ return this;
+ }
+
+ /**
* Sets the SSL context which will be used when initiating connections with
* the Directory Server.
* <p>
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java
new file mode 100644
index 0000000..def8c35
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionFactoryImpl.java
@@ -0,0 +1,481 @@
+/*
+ * 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.spi;
+
+import java.net.InetSocketAddress;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.ConnectionException;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.util.Reject;
+import org.forgerock.util.promise.AsyncFunction;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.Function;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.PromiseImpl;
+import org.forgerock.util.promise.SuccessHandler;
+import org.forgerock.util.time.TimeService;
+
+import com.forgerock.opendj.util.ReferenceCountedObject;
+
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+import static org.forgerock.util.Utils.*;
+import static org.forgerock.util.promise.Promises.*;
+
+import static com.forgerock.opendj.ldap.CoreMessages.*;
+import static com.forgerock.opendj.util.StaticUtils.*;
+
+/**
+ * A abstract implementation of a LDAP connection factory.
+ * This factory can be used to create connections that send a
+ * periodic search request to a Directory Server, or to create
+ * pre-authenticated secure connections to a Directory Server.
+ * <p>
+ * Before returning new connections to the application this factory will first
+ * send an initial startTLS, bind or search (according to options) request in order to
+ * determine that the remote server is responsive. If the request fails or times
+ * out then the connection is closed immediately and an error returned to the client.
+ * <p>
+ * Once a connection has been established successfully (including the initial request)
+ * and if heart beat is activated (this is done by specifying a interval
+ * greater than 0 in the {@link LDAPOptions} constructor parameter), this
+ * factory will periodically send heart beats on the connection based on the
+ * configured heart beat interval. If the heart beat times out then the server
+ * is assumed to be down and an appropriate {@link ConnectionException}
+ * generated and published to any registered {@link ConnectionEventListener}s.
+ * Note however, that heart beats will only be sent when the connection is
+ * determined to be reasonably idle: there is no point in sending heart beats if
+ * the connection has recently received a response. A connection is deemed to be
+ * idle if no response has been received during a period equivalent to half the
+ * heart beat interval.
+ * <p>
+ * The LDAP protocol specifically precludes clients from performing operations
+ * while bind or startTLS requests are being performed. Likewise, a bind or
+ * startTLS request will cause active operations to be aborted. This factory
+ * coordinates heart beats with bind or startTLS requests, ensuring that they
+ * are not performed concurrently. Specifically, bind and startTLS requests are
+ * queued up while a heart beat is pending, and heart beats are not sent at all
+ * while there are pending bind or startTLS requests. If one bind or startTLS
+ * request has timed out, an error will be thrown after the interval time period.
+ */
+public abstract class AbstractLdapConnectionFactoryImpl implements LDAPConnectionFactoryImpl {
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+ /** This is package private in order to allow unit tests to inject fake timestamps. */
+ TimeService timeService = TimeService.SYSTEM;
+
+ // @Checkstyle:ignore
+ private static <VOUT, VIN extends VOUT, E extends Exception> Function<VIN, VOUT, E> castFunction() {
+ return new Function<VIN, VOUT, E>() {
+ @Override
+ public VOUT apply(VIN value) throws E {
+ return value;
+ }
+ };
+ }
+
+ private final String host;
+
+ private final int port;
+
+ /** The LDAP connection options to use when creating connections. */
+ protected final LDAPOptions options;
+
+ /** Flag which indicates whether this factory has been closed. */
+ private final AtomicBoolean isClosed = new AtomicBoolean();
+
+ /**
+ * Prevents the scheduler being released when there are remaining references (this factory or any connections).
+ * It is initially set to 1 because this factory has a reference.
+ */
+ private final AtomicInteger referenceCount = new AtomicInteger(1);
+
+ /** List of valid connections to which heartbeats will be sent. */
+ private final List<AbstractLdapConnectionImpl<?>> validConnections =
+ new LinkedList<AbstractLdapConnectionImpl<?>>();
+
+ /** The heart beat scheduler. */
+ private final ReferenceCountedObject<ScheduledExecutorService>.Reference scheduler;
+
+ /** The heart beat scheduled future - which may be null if heart beats are not being sent (no valid connections). */
+ private ScheduledFuture<?> heartBeatFuture;
+
+ /** Scheduled task which sends heart beats for all valid idle connections. */
+ private final Runnable sendHeartBeatRunnable = new Runnable() {
+ @Override
+ public void run() {
+ boolean heartBeatSent = false;
+ for (final AbstractLdapConnectionImpl<?> connection : getValidConnections()) {
+ heartBeatSent |= connection.sendHeartBeat();
+ }
+ if (heartBeatSent) {
+ scheduler.get().schedule(checkHeartBeatRunnable, options.getTimeout(MILLISECONDS), MILLISECONDS);
+ }
+ }
+ };
+
+ /** Scheduled task which checks that all heart beats have been received within the timeout period. */
+ private final Runnable checkHeartBeatRunnable = new Runnable() {
+ @Override
+ public void run() {
+ for (final AbstractLdapConnectionImpl<?> connection : getValidConnections()) {
+ connection.checkForHeartBeat();
+ }
+ }
+ };
+
+ /**
+ * Initializes this {@link AbstractLdapConnectionFactoryImpl}.
+ *
+ * @param host
+ * The host name.
+ * @param port
+ * The port number.
+ * @param options
+ * The LDAP options to use when creating connections.
+ */
+ public AbstractLdapConnectionFactoryImpl(final String host, final int port, final LDAPOptions options) {
+ Reject.ifNull(options);
+
+ this.host = host;
+ this.port = port;
+ this.options = new LDAPOptions(options);
+ this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(options.getHeartBeatScheduler());
+ }
+
+ /**
+ * Returns a promise which result is a {@link AbstractLdapConnectionImpl}.
+ *
+ * @return A promise which result is a {@link AbstractLdapConnectionImpl}.
+ */
+ protected abstract Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0();
+
+ /**
+ * Install a secure layer on the given connection. Does nothing by default.
+ * Factories which wants to use secure layer like SSL/TLS should override this method.
+ *
+ * @param connection
+ * The {@link Connection} to secure.
+ * @return A promise which should be completed once the operation is done.
+ */
+ protected Promise<Void, LdapException> installSecureLayer(final Connection connection) {
+ return newFailedPromise(newLdapException(ResultCode.OTHER,
+ LocalizableMessage.raw("The connection should install a secure layer.")));
+ }
+
+ /**
+ * Release all implementation resources.
+ */
+ protected void releaseImplResources() {
+ //does nothing by default.
+ }
+
+ @Override
+ public Connection getConnection() throws LdapException {
+ try {
+ return getConnectionAsync().getOrThrow();
+ } catch (final InterruptedException e) {
+ throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
+ }
+ }
+
+ @Override
+ public Promise<Connection, LdapException> getConnectionAsync() {
+ acquireResource(); // Protect resources.
+
+ final AtomicReference<AbstractLdapConnectionImpl<?>> connectionHolder =
+ new AtomicReference<AbstractLdapConnectionImpl<?>>();
+ final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
+
+ getConnectionAsync0().thenAsync(new AsyncFunction<AbstractLdapConnectionImpl<?>, Void, LdapException>() {
+ @Override
+ public Promise<Void, LdapException> apply(AbstractLdapConnectionImpl<?> connection) throws LdapException {
+ connectionHolder.set(connection);
+ return startSchedulerAndSecureLayerIfNeeded(connection, promise);
+ }
+ }).thenAsync(new AsyncFunction<Void, Result, LdapException>() {
+ @Override
+ public Promise<Result, LdapException> apply(Void v) throws LdapException {
+ return sendInitialRequest(connectionHolder.get());
+ }
+ }).onSuccess(new SuccessHandler<Result>() {
+ @Override
+ public void handleResult(Result voidResult) {
+ final AbstractLdapConnectionImpl<?> connectionImpl = connectionHolder.get();
+ if (!promise.tryHandleResult(registerConnection(connectionImpl))) {
+ connectionImpl.close();
+ }
+ }
+ }).onFailure(new FailureHandler<LdapException>() {
+ @Override
+ public void handleError(LdapException error) {
+ if (promise.tryHandleError(error)) {
+ closeSilently(connectionHolder.get());
+ releaseResources();
+ }
+ }
+ });
+
+ return promise;
+ }
+
+ private Promise<Void, LdapException> startSchedulerAndSecureLayerIfNeeded(
+ final AbstractLdapConnectionImpl<?> connection, final PromiseImpl<Connection, LdapException> promise) {
+ // Start the scheduler to prevents initial request timed out.
+ if (initialRequestEnabled()) {
+ scheduler.get().schedule(new Runnable() {
+ @Override
+ public void run() {
+ if (promise.tryHandleError(newHeartBeatTimeoutError())) {
+ closeSilently(connection);
+ releaseResources();
+ }
+ }
+ }, options.getTimeout(MILLISECONDS), MILLISECONDS);
+ }
+
+ if (!options.useStartTLS() && options.getSSLContext() != null) {
+ return installSecureLayer(connection);
+ }
+
+ return newSuccessfulPromise(null);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private Promise<Result, LdapException> sendInitialRequest(final AbstractLdapConnectionImpl connection) {
+ // Sends the request which will acts as initial heartbeat.
+ if (options.useStartTLS()) {
+ // StartTLS extended request which will act as initial heartbeat.
+ final StartTLSExtendedRequest startTLS = newStartTLSExtendedRequest(options.getSSLContext());
+ startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites().toArray(
+ new String[options.getEnabledCipherSuites().size()]));
+ startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray(
+ new String[options.getEnabledProtocols().size()]));
+
+ return (Promise) connection.extendedRequestAsync(startTLS);
+ } else if (options.getBindRequest() != null) {
+ // FIXME: support multi-stage SASL binds?
+ // The bind request will act as the initial hearbeat.
+ return (Promise) connection.bindAsync(options.getBindRequest());
+ } else if (isHeartBeatEnabled()) {
+ return connection.searchAsync(options.getHeartBeatSearchRequest(), null, null).then(
+ AbstractLdapConnectionFactoryImpl.<Result, Result, LdapException> castFunction(),
+ new Function<LdapException, Result, LdapException>() {
+ @Override
+ public Result apply(LdapException error) throws LdapException {
+ throw adaptHeartBeatError(error);
+ }
+ });
+ }
+
+ return newSuccessfulPromise(null);
+ }
+
+ private boolean initialRequestEnabled() {
+ return isHeartBeatEnabled() || options.useStartTLS()
+ || options.getSSLContext() != null || options.getBindRequest() != null;
+ }
+
+ private void acquireResource() {
+ /*
+ * If the factory is not closed then we need to prevent the scheduler
+ * from being released while the connection attempt is in progress.
+ */
+ referenceCount.incrementAndGet();
+ if (isClosed.get()) {
+ releaseResources();
+ throw new IllegalStateException("Attempted to get a connection after factory close");
+ }
+ }
+
+ private Connection registerConnection(final AbstractLdapConnectionImpl<?> connection) {
+ if (isHeartBeatEnabled()) {
+ synchronized (validConnections) {
+ if (validConnections.isEmpty()) {
+ /* This is the first active connection, so start the heart beat. */
+ heartBeatFuture = scheduler.get().scheduleWithFixedDelay(sendHeartBeatRunnable, 0,
+ options.getHeartBeatInterval(MILLISECONDS), MILLISECONDS);
+ }
+ validConnections.add(connection);
+ }
+ }
+
+ return connection;
+ }
+
+ @Override
+ public void close() {
+ if (isClosed.compareAndSet(false, true)) {
+ releaseResources();
+ if (isHeartBeatEnabled()) {
+ synchronized (validConnections) {
+ if (!validConnections.isEmpty()) {
+ logger.debug(LocalizableMessage.raw(
+ "HeartbeatConnectionFactory '%s' is closing while %d active connections remain", this,
+ validConnections.size()));
+ }
+ }
+ }
+ }
+ }
+
+ void releaseResources() {
+ if (referenceCount.decrementAndGet() == 0) {
+ scheduler.release();
+ releaseImplResources();
+ }
+ }
+
+ /**
+ * Returns {@link LDAPOptions} of this factory.
+ *
+ * @return {@link LDAPOptions} of this factory.
+ */
+ public LDAPOptions getLdapOptions() {
+ return options;
+ }
+
+ @Override
+ public InetSocketAddress getSocketAddress() {
+ return new InetSocketAddress(host, port);
+ }
+
+ @Override
+ public String getHostName() {
+ return host;
+ }
+
+ @Override
+ public int getPort() {
+ return port;
+ }
+
+ boolean isHeartBeatEnabled() {
+ return options.getHeartBeatInterval(MILLISECONDS) > 0L;
+ }
+
+ private AbstractLdapConnectionImpl<?>[] getValidConnections() {
+ synchronized (validConnections) {
+ return validConnections.toArray(new AbstractLdapConnectionImpl<?>[0]);
+ }
+ }
+
+ void deregisterConnection(AbstractLdapConnectionImpl<?> connection) {
+ if (isHeartBeatEnabled()) {
+ synchronized (validConnections) {
+ if (validConnections.remove(connection) && validConnections.isEmpty()) {
+ // This is the last active connection, so stop the heartbeat.
+ heartBeatFuture.cancel(false);
+ }
+ }
+ }
+ }
+
+ private LdapException adaptHeartBeatError(final Exception error) {
+ if (error instanceof ConnectionException) {
+ return (LdapException) error;
+ } else if (error instanceof TimeoutResultException || error instanceof TimeoutException) {
+ return newHeartBeatTimeoutError();
+ } else if (error instanceof InterruptedException) {
+ return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, error);
+ } else {
+ return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_FAILED.get(), error);
+ }
+ }
+
+ LdapException newHeartBeatTimeoutError() {
+ return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
+ HBCF_HEARTBEAT_TIMEOUT.get(options.getTimeout(MILLISECONDS)));
+ }
+
+ TimeService getTimeService() {
+ return timeService;
+ }
+
+ /**
+ * Returns the minimum amount of time the connection should remain idle (no responses)
+ * before starting to send heartbeats.
+ *
+ * @param unit
+ * The {@link TimeUnit} of the response
+ * @return
+ * A long which represents the minimum amount of time the connection should remain idle (no responses)
+ * before starting to send heartbeats.
+ */
+ long getMinimumDelay(TimeUnit unit) {
+ return MILLISECONDS.convert(options.getHeartBeatInterval(MILLISECONDS) / 2, unit);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("(").append(getClass().getSimpleName())
+ .append("(").append(host).append(":").append(port).append("), ")
+ .append("HeartBeat: ").append(isHeartBeatEnabled() ? "Enabled" : "Disabled").append(", ");
+
+ if (isHeartBeatEnabled()) {
+ builder.append("HeartBeatSearchRequest: ").append(options.getHeartBeatSearchRequest()).append(", ");
+ }
+
+ final boolean isSecured = options.getSSLContext() != null || options.useStartTLS();
+ builder.append("IsSecured: ").append(isSecured ? "Yes" : "No").append(", ")
+ .append("InitialRequest: ").append(initialRequestEnabled() ? "Enabled" : "Disabled");
+ if (options.useStartTLS() || options.getBindRequest() != null || options.getHeartBeatSearchRequest() != null) {
+ builder.append(", ");
+ if (options.useStartTLS()) {
+ builder.append("startTLS");
+ } else if (options.getBindRequest() != null) {
+ builder.append(options.getBindRequest());
+ } else if (options.getHeartBeatSearchRequest() != null) {
+ builder.append(options.getHeartBeatSearchRequest());
+ }
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+
+}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java
new file mode 100644
index 0000000..5ab5e2f
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/AbstractLdapConnectionImpl.java
@@ -0,0 +1,1124 @@
+/*
+ * 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.spi;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.io.LDAPWriter;
+import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
+import org.forgerock.opendj.ldap.CancelledResultException;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.ConnectionException;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.TimeoutEventListener;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindClient;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Request;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+import org.forgerock.util.promise.AsyncFunction;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.SuccessHandler;
+
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.forgerock.opendj.ldap.Connections.*;
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.ResultCode.*;
+import static org.forgerock.opendj.ldap.responses.Responses.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
+import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
+
+import static com.forgerock.opendj.ldap.CoreMessages.*;
+
+
+
+/**
+ * An abstract implementation of a LDAP {@link AbstractAsynchronousConnection}.
+ *
+ * @param <F>
+ * The associated connection factory.
+ */
+public abstract class AbstractLdapConnectionImpl<F extends AbstractLdapConnectionFactoryImpl>
+ extends AbstractAsynchronousConnection implements TimeoutEventListener {
+ /**
+ * This synchronizer prevents Bind or StartTLS operations from being
+ * processed concurrently with heart-beats. This is required because the
+ * LDAP protocol specifically states that servers receiving a Bind operation
+ * should either wait for existing operations to complete or abandon them.
+ * The same presumably applies to StartTLS operations. Note that concurrent
+ * bind/StartTLS operations are not permitted.
+ * <p>
+ * This connection factory only coordinates Bind and StartTLS requests with
+ * heart-beats. It does not attempt to prevent or control attempts to send
+ * multiple concurrent Bind or StartTLS operations, etc.
+ * <p>
+ * This synchronizer can be thought of as cross between a read-write lock
+ * and a semaphore. Unlike a read-write lock there is no requirement that a
+ * thread releasing a lock must hold it. In addition, this synchronizer does
+ * not support reentrancy. A thread attempting to acquire exclusively more
+ * than once will deadlock, and a thread attempting to acquire shared more
+ * than once will succeed and be required to release an equivalent number of
+ * times.
+ * <p>
+ * The synchronizer has three states:
+ * <ul>
+ * <li>UNLOCKED(0) - the synchronizer may be acquired shared or exclusively
+ * <li>LOCKED_EXCLUSIVELY(-1) - the synchronizer is held exclusively and
+ * cannot be acquired shared or exclusively. An exclusive lock is held while
+ * a heart beat is in progress
+ * <li>LOCKED_SHARED(>0) - the synchronizer is held shared and cannot be
+ * acquired exclusively. N shared locks are held while N Bind or StartTLS
+ * operations are in progress.
+ * </ul>
+ */
+ private static final class HeartBeatSynchronizer extends AbstractQueuedSynchronizer {
+ /** Keep compiler quiet. */
+ private static final long serialVersionUID = -3590428415442668336L;
+
+ /** Lock states. Positive values indicate that the shared lock is taken. */
+ private static final int LOCKED_EXCLUSIVELY = -1;
+ private static final int UNLOCKED = 0; // initial state
+
+ @Override
+ protected boolean isHeldExclusively() {
+ return getState() == LOCKED_EXCLUSIVELY;
+ }
+
+ boolean isHeld() {
+ return getState() != 0;
+ }
+
+ @Override
+ protected boolean tryAcquire(final int ignored) {
+ if (compareAndSetState(UNLOCKED, LOCKED_EXCLUSIVELY)) {
+ setExclusiveOwnerThread(Thread.currentThread());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected int tryAcquireShared(final int readers) {
+ for (;;) {
+ final int state = getState();
+ if (state == LOCKED_EXCLUSIVELY) {
+ return LOCKED_EXCLUSIVELY; // failed
+ }
+ final int newState = state + readers;
+ if (compareAndSetState(state, newState)) {
+ return newState; // succeeded + more readers allowed
+ }
+ }
+ }
+
+ @Override
+ protected boolean tryRelease(final int ignored) {
+ if (getState() != LOCKED_EXCLUSIVELY) {
+ throw new IllegalMonitorStateException();
+ }
+ setExclusiveOwnerThread(null);
+ setState(UNLOCKED);
+ return true;
+ }
+
+ @Override
+ protected boolean tryReleaseShared(final int ignored) {
+ for (;;) {
+ final int state = getState();
+ if (state == UNLOCKED || state == LOCKED_EXCLUSIVELY) {
+ throw new IllegalMonitorStateException();
+ }
+ final int newState = state - 1;
+ if (compareAndSetState(state, newState)) {
+ /*
+ * We could always return true here, but since there cannot
+ * be waiting readers we can specialize for waiting writers.
+ */
+ return newState == UNLOCKED;
+ }
+ }
+ }
+
+ void lockShared() {
+ acquireShared(1);
+ }
+
+ boolean tryLockExclusively() {
+ return tryAcquire(0 /* unused */);
+ }
+
+ boolean tryLockShared() {
+ return tryAcquireShared(1) > 0;
+ }
+
+ void unlockExclusively() {
+ release(0 /* unused */);
+ }
+
+ void unlockShared() {
+ releaseShared(0 /* unused */);
+ }
+
+ }
+
+ /**
+ * A request writer which is use to write different kind of {@link Request}.
+ *
+ * @param <R> The {@link Request} to write.
+ */
+ protected static abstract interface RequestWriter<R extends Request> {
+ /**
+ * Use the given {@link LDAPWriter} to write the given {@link Request}.
+ *
+ * @param messageID
+ * Identifier of the request.
+ * @param writer
+ * {@link LDAPWriter} associated to the request.
+ * @param request
+ * The request to send to the server.
+ * @throws IOException
+ * If any writes problems occurs.
+ */
+ void writeRequest(int messageID, LDAPWriter<?> writer, R request) throws IOException;
+ };
+
+ private static final RequestWriter<AddRequest> ADD_WRITER = new RequestWriter<AddRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, AddRequest request) throws IOException {
+ writer.writeAddRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<CompareRequest> COMPARE_WRITER = new RequestWriter<CompareRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, CompareRequest request) throws IOException {
+ writer.writeCompareRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<DeleteRequest> DELETE_WRITER = new RequestWriter<DeleteRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, DeleteRequest request) throws IOException {
+ writer.writeDeleteRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<ExtendedRequest<?>> EXTENDED_WRITER = new RequestWriter<ExtendedRequest<?>>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, ExtendedRequest<?> request) throws IOException {
+ writer.writeExtendedRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<ModifyRequest> MODIFY_WRITER = new RequestWriter<ModifyRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, ModifyRequest request) throws IOException {
+ writer.writeModifyRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<ModifyDNRequest> MODIFY_DN_WRITER = new RequestWriter<ModifyDNRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, ModifyDNRequest request) throws IOException {
+ writer.writeModifyDNRequest(messageID, request);
+ };
+ };
+
+ private static final RequestWriter<SearchRequest> SEARCH_WRITER = new RequestWriter<SearchRequest>() {
+ @Override
+ public void writeRequest(int messageID, LDAPWriter<?> writer, SearchRequest request) throws IOException {
+ writer.writeSearchRequest(messageID, request);
+ };
+ };
+
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+ /** List of pending Bind or StartTLS requests which must be invoked once the current heart beat completes. */
+ private final Queue<Runnable> pendingBindOrStartTLSRequests = new ConcurrentLinkedQueue<Runnable>();
+
+ /** Coordinates heart-beats with Bind and StartTLS requests. */
+ private final HeartBeatSynchronizer sync = new HeartBeatSynchronizer();
+
+ /** Timestamp of last response received (any response, not just heart beats). */
+ private volatile long lastResponseTimestamp;
+
+ private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
+
+ private final AtomicInteger msgIDGenerator = new AtomicInteger(1);
+
+ /** Subclasses are responsible for adding and removing pending results. */
+ private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingResults =
+ new ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>>();
+
+ private final F factory;
+
+ /** Internal connection state. */
+ protected final ConnectionState state = new ConnectionState();
+
+ /**
+ * Creates a connection which was produced by the given factory.
+ *
+ * @param attachedFactory
+ * The factory of this connection.
+ */
+ protected AbstractLdapConnectionImpl(F attachedFactory) {
+ this.factory = attachedFactory;
+ this.lastResponseTimestamp = attachedFactory.getTimeService().now(); // Assume valid at creation.
+ }
+
+ /**
+ * Returns {@link LDAPOptions} associated to the factory which has produced
+ * this connection.
+ *
+ * @return {@link LDAPOptions} associated to the factory which has produced
+ * this connection.
+ */
+ public LDAPOptions getLdapOptions() {
+ return factory.getLdapOptions();
+ }
+
+ @Override
+ public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
+ /*
+ * Need to be careful here since both abandonAsync and Promise.cancel can
+ * be called separately by the client application. Therefore
+ * promise.cancel() should abandon the request, and abandonAsync should
+ * cancel the promise. In addition, bind or StartTLS requests cannot be
+ * abandoned.
+ */
+ try {
+ checkConnectionIsValid();
+ /*
+ * If there is a bind or startTLS in progress then it must be
+ * this request which is being abandoned. The following check
+ * will prevent it from happening.
+ */
+ checkBindOrStartTLSInProgress();
+ } catch (final LdapException e) {
+ return newFailedLdapPromise(e);
+ }
+
+ // Remove the promise associated with the request to be abandoned.
+ final LdapPromiseImpl<?> pendingRequest = removePendingResult(request.getRequestID());
+ if (pendingRequest == null) {
+ /*
+ * There has never been a request with the specified message ID or
+ * the response has already been received and handled. We can ignore
+ * this abandon request.
+ */
+ return newSuccessfulLdapPromise((Void) null);
+ }
+
+ /*
+ * This will cancel the promise, but will also recursively invoke this
+ * method. Since the pending request has been removed, there is no risk
+ * of an infinite loop.
+ */
+ pendingRequest.cancel(false);
+
+ /*
+ * FIXME: there's a potential race condition here if a bind or startTLS
+ * is initiated just after we removed the pending request.
+ */
+ return abandonAsync0(newMessageID(), request);
+ }
+
+ @Override
+ public LdapPromise<Result> addAsync(final AddRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ return sendRequest(request, intermediateResponseHandler, ADD_WRITER);
+ }
+
+ @Override
+ public LdapPromise<BindResult> bindAsync(final BindRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ try {
+ checkConnectionIsValid();
+ if (!isHeartBeatConnection() || sync.tryLockShared()) {
+ // Fast path
+ return performBindRequest(request, intermediateResponseHandler);
+ } else {
+ return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() {
+ @Override
+ public Promise<BindResult, LdapException> apply(Void value) throws LdapException {
+ return performBindRequest(request, intermediateResponseHandler);
+ }
+ });
+ }
+ } catch (final LdapException e) {
+ return newFailedLdapPromise(e);
+ }
+ }
+
+ @Override
+ public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ return sendRequest(request, intermediateResponseHandler, COMPARE_WRITER);
+ }
+
+ @Override
+ public LdapPromise<Result> deleteAsync(final DeleteRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ return sendRequest(request, intermediateResponseHandler, DELETE_WRITER);
+ }
+
+ @Override
+ public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ try {
+ checkConnectionIsValid();
+ if (isStartTLSRequest(request)) {
+ if (!isHeartBeatConnection() && sync.tryLockShared()) {
+ // Fast path
+ return performStartTLSRequest(request, intermediateResponseHandler);
+ } else {
+ return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() {
+ @Override
+ public Promise<R, LdapException> apply(Void value) throws LdapException {
+ startBindOrStartTLS();
+ return performStartTLSRequest(request, intermediateResponseHandler);
+ }
+ });
+ }
+ } else {
+ return sendExtendedRequest(request, intermediateResponseHandler);
+ }
+ } catch (LdapException e) {
+ return newFailedLdapPromise(e);
+ }
+ }
+
+ @Override
+ public LdapPromise<Result> modifyAsync(final ModifyRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ return sendRequest(request, intermediateResponseHandler, MODIFY_WRITER);
+ }
+
+ @Override
+ public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) {
+ return sendRequest(request, intermediateResponseHandler, MODIFY_DN_WRITER);
+ }
+
+ @Override
+ public LdapPromise<Result> searchAsync(final SearchRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
+ return sendRequest(request, intermediateResponseHandler, entryHandler, SEARCH_WRITER);
+ }
+
+ @Override
+ public long handleTimeout(final long currentTime) {
+ final long timeout = getLdapOptions().getTimeout(MILLISECONDS);
+ if (timeout <= 0) {
+ return 0;
+ }
+
+ long delay = timeout;
+ for (final ResultLdapPromiseImpl<?, ?> promise : pendingResults.values()) {
+ if (promise == null || !promise.checkForTimeout()) {
+ continue;
+ }
+ final long diff = (promise.getTimestamp() + timeout) - currentTime;
+ if (diff > 0) {
+ // Will expire in diff milliseconds.
+ delay = Math.min(delay, diff);
+ } else if (removePendingResult(promise.getRequestID()) == null) {
+ // Result arrived at the same time.
+ continue;
+ } else if (promise.isBindOrStartTLS()) {
+ //TODO: set diagnostic messages for timeout (from opendj-grizzly)
+ /*
+ * No other operations can be performed while a bind or StartTLS
+ * request is active, so we cannot time out the request. We
+ * therefore have a choice: either ignore timeouts for these
+ * operations, or enforce them but doing so requires
+ * invalidating the connection. We'll do the latter, since
+ * ignoring timeouts could cause the application to hang.
+ */
+ logger.debug(LocalizableMessage.raw("Failing bind or StartTLS request due to timeout %s"
+ + "(connection will be invalidated): ", promise));
+ final Result result = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+ LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout).toString());
+ promise.adaptErrorResult(result);
+
+ // Fail the connection.
+ final Result errorResult = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+ LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout).toString());
+ connectionErrorOccurred(false, errorResult);
+ } else {
+ logger.debug(LocalizableMessage.raw("Failing request due to timeout: %s", promise));
+ final Result result = newResult(CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
+ LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
+
+ promise.adaptErrorResult(result);
+
+ /*
+ * FIXME: there's a potential race condition here if a bind or
+ * startTLS is initiated just after we check the boolean. It
+ * seems potentially even more dangerous to send the abandon
+ * request while holding the state lock, since a blocking write
+ * could hang the application.
+ */
+ // if (!bindOrStartTLSInProgress.get()) {
+ // sendAbandonRequest(newAbandonRequest(promise.getRequestID()));
+ // }
+ }
+ }
+ return delay;
+ }
+
+ @Override
+ public long getTimeout() {
+ return getLdapOptions().getTimeout(MILLISECONDS);
+ }
+
+ @Override
+ public void addConnectionEventListener(final ConnectionEventListener listener) {
+ state.addConnectionEventListener(listener);
+ }
+
+ @Override
+ public void removeConnectionEventListener(final ConnectionEventListener listener) {
+ state.removeConnectionEventListener(listener);
+ }
+
+ @Override
+ public boolean isClosed() {
+ return state.isClosed();
+ }
+
+ @Override
+ public boolean isValid() {
+ return state.isValid();
+ }
+
+ /**
+ * Set this connection state as performing a bind or a startTLS request
+ * according to the given boolean.
+ *
+ * @param state
+ * true if this connection is performing a bind or a startTLS
+ * request.
+ */
+ public void setBindOrStartTLSInProgress(final boolean state) {
+ bindOrStartTLSInProgress.set(state);
+ }
+
+ @Override
+ public void close(final UnbindRequest request, final String reason) {
+ if (state.notifyConnectionClosed()) {
+ failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED,
+ HBCF_CONNECTION_CLOSED_BY_CLIENT.get()));
+ factory.deregisterConnection(this);
+ close0(newMessageID(), request, reason);
+ factory.releaseResources();
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("(").append(getClass().getSimpleName()).append(", ")
+ .append("attachedFactory: ").append(factory).append(")");
+ return builder.toString();
+ };
+
+ /**
+ * Returns the factory which has produced this connection.
+ *
+ * @return The factory which has produced this connection.
+ */
+ protected F getFactory() {
+ return factory;
+ }
+
+ /**
+ * Sends the given {@link AbandonRequest} to the server.
+ *
+ * @param messageID
+ * Identifier of the request.
+ * @param request
+ * The request identifying the operation to be abandoned.
+ * @return A promise whose result is Void.
+ */
+ protected abstract LdapPromise<Void> abandonAsync0(final int messageID, final AbandonRequest request);
+
+ /**
+ * Sends the given {@link BindRequest} to the server.
+ *
+ * @param messageID
+ * Identifier of the request.
+ * @param request
+ * The bind request.
+ * @param bindClient
+ * Bind client which can be used to perform the authentication
+ * process.
+ * @param intermediateResponseHandler
+ * An intermediate response handler which can be used to process
+ * any intermediate responses as they are received, may be
+ * {@code null}.
+ * @throws LdapException
+ * If a LDAP error occurred.
+ */
+ protected abstract void bindAsync0(final int messageID, final BindRequest request, final BindClient bindClient,
+ final IntermediateResponseHandler intermediateResponseHandler) throws LdapException;
+
+ /**
+ * Sends the given {@link UnbindRequest} and closes the underlying socket.
+ *
+ * @param messageID
+ * Identifier of the request.
+ * @param request
+ * The unbind request to use in the case where a physical
+ * connection is closed.
+ * @param reason
+ * A reason describing why the connection was closed.
+ */
+ protected abstract void close0(final int messageID, UnbindRequest request, String reason);
+
+ /**
+ * Indicates whether or not TLS is enabled on this connection.
+ *
+ * @return {@code true} if TLS is enabled on this connection, otherwise {@code false}.
+ */
+ protected abstract boolean isTLSEnabled();
+
+ /**
+ * Sends the given request to the server.
+ *
+ * @param <R> The request type.
+ *
+ * @param messageID
+ * Identifier of the request.
+ * @param request
+ * The request to send.
+ * @param intermediateResponseHandler
+ * An intermediate response handler which can be used to process
+ * any intermediate responses as they are received, may be
+ * {@code null}.
+ * @param requestWriter
+ * The writer associated to the request.
+ * @throws LdapException
+ * If a LDAP error occurred.
+ */
+ protected abstract <R extends Request> void writeRequest(final int messageID, final R request,
+ final IntermediateResponseHandler intermediateResponseHandler, final RequestWriter<R> requestWriter)
+ throws LdapException;
+
+ /**
+ * Mark this connection as failed, invoking event listeners as needed.
+ *
+ * @param isDisconnectNotification
+ * {@code true} if this is a connection failure signaled by a
+ * server disconnect notification.
+ * @param reason
+ * The result indicating why the connection has failed.
+ */
+ protected void connectionErrorOccurred(final boolean isDisconnectNotification, final Result reason) {
+ LdapException error = newLdapException(reason);
+ if (state.notifyConnectionError(isDisconnectNotification, error)) {
+ failPendingResults(error);
+ }
+ }
+
+ /**
+ * Checks the connection state. If the connection is no longer valid, throws
+ * the connection {@link LdapException}.
+ *
+ * @throws LdapException
+ * The connection error.
+ */
+ protected void checkConnectionIsValid() throws LdapException {
+ if (!state.isValid()) {
+ /*
+ * Connection termination was triggered remotely. We don't want to
+ * blindly pass on the result code to requests since it could be
+ * confused for a genuine response. For example, if the disconnect
+ * contained the invalidCredentials result code then this could be
+ * misinterpreted as a genuine authentication failure for subsequent
+ * bind requests.
+ */
+ throw state.getConnectionError() != null ? state.getConnectionError() : newLdapException(
+ ResultCode.CLIENT_SIDE_SERVER_DOWN, "Connection closed by server");
+ }
+ }
+
+ /**
+ * Returns true of the given {@link ExtendedRequest} is a startTLS request.
+ *
+ * @param request
+ * The extended request to test.
+ * @return true of the given {@link ExtendedRequest} is a startTLS request.
+ */
+ protected boolean isStartTLSRequest(final ExtendedRequest<?> request) {
+ return request.getOID().equals(StartTLSExtendedRequest.OID);
+ }
+
+ /**
+ * Returns true if heart beat is enabled on this connection.
+ *
+ * @return true if heart beat is enabled on this connection.
+ */
+ protected boolean isHeartBeatConnection() {
+ return factory.isHeartBeatEnabled();
+ }
+
+ /**
+ * Returns the next pending result identifier available for this connection.
+ *
+ * @return The next pending result identifier available for this connection.
+ */
+ protected int newMessageID() {
+ return msgIDGenerator.getAndIncrement();
+ }
+
+ /**
+ * Adds the given pending result to the pending results of this
+ * connection.
+ *
+ * @param messageID
+ * The pending result identifier.
+ * @param promise
+ * The pending results.
+ */
+ protected void addPendingResult(final int messageID, final ResultLdapPromiseImpl<?, ?> promise) {
+ if (pendingResults.put(messageID, promise) != null) {
+ throw new IllegalStateException("There is already a pending request with ID: " + messageID);
+ }
+
+ promise.onSuccessOrFailure(new Runnable() {
+ @Override
+ public void run() {
+ removePendingResult(messageID);
+ }
+ });
+ }
+
+ /**
+ * Returns the pending results of this connection.
+ *
+ * @return A {@link Map} which represents the pending results of this connection.
+ */
+ protected Map<Integer, ResultLdapPromiseImpl<?, ?>> getPendingResults() {
+ return pendingResults;
+ }
+
+ /**
+ * Returns the pending result associated to the given identifier, or
+ * {@code null} if there is no pending result mapped for the given
+ * identifier.
+ *
+ * @param messageID
+ * The request identifier.
+ * @return The pending result associated to the given identifier, or
+ * {@code null} if there is no pending result mapped for the given
+ * identifier.
+ */
+ protected ResultLdapPromiseImpl<?, ?> getPendingResult(int messageID) {
+ return pendingResults.get(messageID);
+ }
+
+ /**
+ * Removes the pending result for the given identifier, if present.
+ *
+ * @param messageID
+ * The pending result identifier.
+ * @param <R>
+ * The associated request type.
+ * @param <S>
+ * The pending result type.
+ * @return The pending result associated with the specified identifier, or
+ * {@code null} if absent.
+ */
+ @SuppressWarnings("unchecked")
+ protected <R extends Request, S extends Result> ResultLdapPromiseImpl<R, S> removePendingResult(
+ final Integer messageID) {
+ return (ResultLdapPromiseImpl<R, S>) pendingResults.remove(messageID);
+ }
+
+ /**
+ * Notifies connection event listeners that this connection has just
+ * received the provided unsolicited notification from the server.
+ *
+ * @param notification
+ * The unsolicited notification.
+ */
+ protected void handleUnsolicitedNotification(ExtendedResult notification) {
+ if (isHeartBeatConnection()) {
+ timestamp(notification);
+ }
+ state.notifyUnsolicitedNotification(notification);
+ }
+
+ /**
+ * Sends a heart beat on this connection if required to do so.
+ *
+ * @return {@code true} if a heart beat was sent, otherwise {@code false}.
+ */
+ boolean sendHeartBeat() {
+ // Don't attempt to send a heart beat if the connection has already failed.
+ if (!state.isValid()) {
+ return false;
+ }
+
+ // Only send the heart beat if the connection has been idle for some time.
+ final long currentTimeMillis = factory.getTimeService().now();
+ if (currentTimeMillis < (lastResponseTimestamp + factory.getMinimumDelay(MILLISECONDS))) {
+ return false;
+ }
+
+ /*
+ * Don't send a heart beat if there is already a heart beat, bind,
+ * or startTLS in progress. Note that the bind/startTLS response
+ * will update the lastResponseTimestamp as if it were a heart beat.
+ */
+ if (sync.tryLockExclusively()) {
+ try {
+ searchAsync(factory.getLdapOptions().getHeartBeatSearchRequest(), null,
+ new SearchResultHandler() {
+ @Override
+ public boolean handleEntry(final SearchResultEntry entry) {
+ timestamp(entry);
+ return true;
+ }
+
+ @Override
+ public boolean handleReference(final SearchResultReference reference) {
+ timestamp(reference);
+ return true;
+ }
+ }
+ ).onSuccess(new SuccessHandler<Result>() {
+ @Override
+ public void handleResult(final Result result) {
+ timestamp(result);
+ releaseHeartBeatLock();
+ }
+ }).onFailure(new FailureHandler<LdapException>() {
+ @Override
+ public void handleError(final LdapException error) {
+ /*
+ * Connection failure will be handled by connection
+ * event listener. Ignore cancellation errors since
+ * these indicate that the heart beat was aborted by a
+ * client-side close.
+ */
+ if (!(error instanceof CancelledResultException)) {
+ /*
+ * Log at debug level to avoid polluting the logs
+ * with benign password policy related errors. See
+ * OPENDJ-1168 and OPENDJ-1167.
+ */
+ logger.debug(LocalizableMessage.raw(
+ "Heartbeat failed for connection factory '%s'", factory, error));
+ timestamp(error);
+ }
+ releaseHeartBeatLock();
+ }
+ });
+ } catch (final IllegalStateException e) {
+ /*
+ * This may happen when we attempt to send the heart beat
+ * just after the connection is closed but before we are
+ * notified.
+ */
+
+ /*
+ * Release the lock because we're never going to get a
+ * response.
+ */
+ releaseHeartBeatLock();
+ }
+ }
+ /*
+ * Indicate that a the heartbeat should be checked even if a
+ * bind/startTLS is in progress, since these operations will
+ * effectively act as the heartbeat.
+ */
+ return true;
+ }
+
+ void checkForHeartBeat() {
+ if (sync.isHeld()) {
+ /*
+ * A heart beat or bind/startTLS is still in progress, but it
+ * should have completed by now. Let's avoid aggressively
+ * terminating the connection, because the heart beat may simply
+ * have been delayed by a sudden surge of activity. Therefore,
+ * only flag the connection as failed if no activity has been
+ * seen on the connection since the heart beat was sent.
+ */
+ final long currentTimeMillis = factory.getTimeService().now();
+ final long timeoutMS = factory.getLdapOptions().getTimeout(MILLISECONDS);
+ if (lastResponseTimestamp < (currentTimeMillis - timeoutMS)) {
+ logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", this));
+ connectionErrorOccurred(false, factory.newHeartBeatTimeoutError().getResult());
+ }
+ }
+ }
+
+ private void releaseHeartBeatLock() {
+ sync.unlockExclusively();
+ flushPendingBindOrStartTLSRequests();
+ }
+
+ private LdapPromise<BindResult> performBindRequest(final BindRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
+ startBindOrStartTLS();
+ int messageID = newMessageID();
+ BindClient context = null;
+ try {
+ InetSocketAddress socketAddress = factory.getSocketAddress();
+ if (socketAddress != null) {
+ context = request.createBindClient(getHostString(socketAddress));
+ }
+ } catch (final LdapException e) {
+ releaseBindOrStartTLSLock();
+ setBindOrStartTLSInProgress(false);
+ return newFailedLdapPromise(e, messageID);
+ }
+
+ final BindResultLdapPromiseImpl bindPromise =
+ newBindLdapPromise(messageID, request, context, intermediateResponseHandler, this);
+ addPendingResult(messageID, bindPromise);
+ try {
+ bindAsync0(messageID, request, context, intermediateResponseHandler);
+ return timestampBindOrStartTLSPromise(bindPromise);
+ } catch (LdapException e) {
+ removePendingResult(messageID).setResultOrError(e.getResult());
+ setBindOrStartTLSInProgress(false);
+ releaseBindOrStartTLSLock();
+ return bindPromise;
+ }
+ }
+
+ private <R extends ExtendedResult> LdapPromise<R> performStartTLSRequest(final ExtendedRequest<R> request,
+ final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
+ if (isTLSEnabled()) {
+ return newFailedLdapPromise(newLdapException(request.getResultDecoder().newExtendedErrorResult(
+ ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled")));
+ }
+ startBindOrStartTLS();
+ return sendExtendedRequest(request, intermediateResponseHandler);
+ }
+
+ private <R extends ExtendedResult> LdapPromise<R> sendExtendedRequest(final ExtendedRequest<R> request,
+ final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
+ final int messageID = msgIDGenerator.getAndIncrement();
+ final ExtendedResultLdapPromiseImpl<R> promise =
+ newExtendedLdapPromise(messageID, request, intermediateResponseHandler, this);
+ addPendingResult(messageID, promise);
+ writeRequest(messageID, request, intermediateResponseHandler, EXTENDED_WRITER);
+ return isStartTLSRequest(request) ? timestampBindOrStartTLSPromise(promise) : timestampPromise(promise);
+ }
+
+ private <R extends Result, Q extends Request> LdapPromise<R> sendRequest(Q request,
+ IntermediateResponseHandler intermediateResponseHandler, RequestWriter<Q> requestWriter) {
+ return sendRequest(request, intermediateResponseHandler, null, requestWriter);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <R extends Result, Q extends Request> LdapPromise<R> sendRequest(Q request,
+ IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler,
+ RequestWriter<Q> requestWriter) {
+ final int messageID = newMessageID();
+ final ResultLdapPromiseImpl<?, ?> promise =
+ createResultPromise(messageID, request, intermediateResponseHandler, entryHandler);
+ try {
+ checkConnectionIsValid();
+ checkBindOrStartTLSInProgress();
+ addPendingResult(messageID, promise);
+ // Will rollback if the pending request was missed during the close then terminate is here.
+ checkConnectionIsValid();
+
+ writeRequest(messageID, request, intermediateResponseHandler, requestWriter);
+ return (LdapPromise<R>) timestampPromise(promise);
+ } catch (final LdapException e) {
+ removePendingResult(messageID);
+ return newFailedLdapPromise(e);
+ }
+ }
+
+ private ResultLdapPromiseImpl<?, ?> createResultPromise(final int messageID, final Request request,
+ final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
+ if (request instanceof CompareRequest) {
+ return newCompareLdapPromise(messageID, (CompareRequest) request, intermediateResponseHandler, this);
+ } else if (request instanceof SearchRequest) {
+ return newSearchLdapPromise(messageID, (SearchRequest) request, entryHandler,
+ intermediateResponseHandler, this);
+ }
+
+ return newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
+ }
+
+ private <R> R timestamp(final R response) {
+ if (!(response instanceof ConnectionException)) {
+ lastResponseTimestamp = factory.getTimeService().now();
+ }
+ return response;
+ }
+
+ private <R extends Result> LdapPromise<R> timestampPromise(final LdapPromise<R> promise) {
+ if (!isHeartBeatConnection()) {
+ //Fast path.
+ return promise;
+ }
+
+ return promise.onSuccess(new SuccessHandler<R>() {
+ @Override
+ public void handleResult(R result) {
+ if (!(result instanceof ConnectionException)) {
+ lastResponseTimestamp = factory.getTimeService().now();
+ }
+ }
+ }).onFailure(new FailureHandler<LdapException>() {
+ @Override
+ public void handleError(LdapException error) {
+ if (!(error instanceof ConnectionException)) {
+ lastResponseTimestamp = factory.getTimeService().now();
+ }
+ }
+ });
+ }
+
+ private <R extends Result> LdapPromise<R> timestampBindOrStartTLSPromise(final LdapPromise<R> wrappedPromise) {
+ return timestampPromise(wrappedPromise).onSuccessOrFailure(new Runnable() {
+ @Override
+ public void run() {
+ setBindOrStartTLSInProgress(false);
+ releaseBindOrStartTLSLock();
+ }
+ });
+ }
+
+ private void failPendingResults(final LdapException error) {
+ // Notification is responsible for removing the element from the map.
+ while (!pendingResults.isEmpty()) {
+ pendingResults.values().iterator().next().handleError(error);
+ }
+ }
+
+ private void startBindOrStartTLS() throws LdapException {
+ if (!pendingResults.isEmpty()) {
+ throw newLdapException(newBindResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("There are other operations pending on this connection"));
+ }
+
+ if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
+ throw newLdapException(newBindResult(ResultCode.OPERATIONS_ERROR)
+ .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+ }
+ }
+
+ private void checkBindOrStartTLSInProgress() throws LdapException {
+ if (bindOrStartTLSInProgress.get()) {
+ throw newLdapException(ResultCode.OPERATIONS_ERROR, "Bind or Start TLS operation in progress");
+ }
+ }
+
+ private <R extends Result> LdapPromise<R> enqueueBindOrStartTLSPromise(
+ final AsyncFunction<Void, R, LdapException> doRequest) {
+ /*
+ * A heart beat must be in progress so create a runnable task which
+ * will be executed when the heart beat completes.
+ */
+ final LdapPromiseImpl<Void> promise = newLdapPromiseImpl();
+ final LdapPromise<R> result = promise.thenAsync(doRequest);
+
+ // Enqueue and flush if the heart beat has completed in the mean time.
+ pendingBindOrStartTLSRequests.offer(new Runnable() {
+ @Override
+ public void run() {
+ // FIXME: Handle cancel chaining.
+ if (!result.isCancelled()) {
+ sync.lockShared(); // Will not block.
+ promise.handleResult((Void) null);
+ }
+ }
+ });
+ flushPendingBindOrStartTLSRequests();
+
+ return result;
+ }
+
+ private void flushPendingBindOrStartTLSRequests() {
+ if (!pendingBindOrStartTLSRequests.isEmpty()) {
+ /*
+ * The pending requests will acquire the shared lock, but we take
+ * it here anyway to ensure that pending requests do not get blocked.
+ */
+ if (sync.tryLockShared()) {
+ try {
+ Runnable pendingRequest;
+ while ((pendingRequest = pendingBindOrStartTLSRequests.poll()) != null) {
+ // Dispatch the waiting request. This will not block.
+ pendingRequest.run();
+ }
+ } finally {
+ sync.unlockShared();
+ }
+ }
+ }
+ }
+
+ private void releaseBindOrStartTLSLock() {
+ if (isHeartBeatConnection()) {
+ sync.unlockShared();
+ }
+ }
+
+}
diff --git a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 835d19b..7f8fb15 100755
--- a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1641,4 +1641,16 @@
WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT=The provided \
value "%s" could not be parsed as a valid assertion value because the \
character '%c' is not allowed. The acceptable values are s (second), \
- m (minute), h (hour), D (date), M (month) and Y (year)
\ No newline at end of file
+ m (minute), h (hour), D (date), M (month) and Y (year)
+#
+# Connection Timeout Messages
+#
+LDAP_CONNECTION_REQUEST_TIMEOUT=The request has failed because no response \
+ was received from the server within the %d ms timeout
+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
+LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT=The LDAP connection has \
+ failed because no bind or StartTLS response was received from the server \
+ within the %d ms timeout
+
\ No newline at end of file
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
deleted file mode 100644
index e650e16..0000000
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/HeartBeatConnectionFactoryTestCase.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- * Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- * Copyright 2013-2014 ForgeRock AS.
- */
-
-package org.forgerock.opendj.ldap;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl;
-import org.forgerock.util.promise.FailureHandler;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.SuccessHandler;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import static org.fest.assertions.Assertions.*;
-import static org.fest.assertions.Fail.*;
-import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.opendj.ldap.ResultCode.*;
-import static org.forgerock.opendj.ldap.SearchScope.*;
-import static org.forgerock.opendj.ldap.TestCaseUtils.*;
-import static org.forgerock.opendj.ldap.requests.Requests.*;
-import static org.forgerock.opendj.ldap.responses.Responses.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
-import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
-import static org.mockito.Matchers.*;
-import static org.mockito.Mockito.*;
-
-/**
- * Tests the connection pool implementation..
- */
-@SuppressWarnings("javadoc")
-public class HeartBeatConnectionFactoryTestCase extends SdkTestCase {
-
- // @formatter:off
-
- /*
- * Key features which need testing:
- *
- * - lazy scheduler registration and deregistration
- * - scheduled task only sends heart-beat when connection is open
- * - connection remains valid when any response is received
- * - connection remains valid when a heart beat response is received
- * - connection becomes invalid if no response is received during timeout
- * - heart beat only sent when connection is idle
- * - slow bind / startTLS prevents heart beat
- * - slow heart beat prevents bind / start TLS
- * - support concurrent bind / start TLS
- */
-
- // @formatter:on
-
- private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT,
- "(objectclass=*)", "1.1");
-
- private Connection connection;
- private ConnectionFactory factory;
- private Connection hbc;
- private HeartBeatConnectionFactory hbcf;
- private List<ConnectionEventListener> listeners;
- private MockScheduler scheduler;
-
- /**
- * Disables logging before the tests.
- */
- @BeforeClass
- public void disableLogging() {
- TestCaseUtils.setDefaultLogLevel(Level.SEVERE);
- }
-
- /**
- * Re-enable logging after the tests.
- */
- @AfterClass
- public void enableLogging() {
- TestCaseUtils.setDefaultLogLevel(Level.INFO);
- }
-
- @AfterMethod(alwaysRun = true)
- public void tearDown() {
- try {
- if (hbc != null) {
- hbc.close();
- assertThat(hbc.isValid()).isFalse();
- assertThat(hbc.isClosed()).isTrue();
- }
- scheduler.runAllTasks(); // Flush any remaining timeout tasks.
- assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check.
- hbcf.close();
- } finally {
- connection = null;
- factory = null;
- hbcf = null;
- listeners = null;
- scheduler = null;
- hbc = null;
- }
- }
-
- @Test
- public void testBindWhileHeartBeatInProgress() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- mockBindAsyncResponse();
- hbc = hbcf.getConnection();
-
- /*
- * Send a heartbeat, trapping the search call-back so that we can send
- * the response once we have attempted a bind.
- */
- when(connection.searchAsync(any(SearchRequest.class), any(SearchResultHandler.class))).thenReturn(
- newSuccessfulLdapPromise(newResult(SUCCESS)));
- when(hbcf.timeService.now()).thenReturn(11000L);
- scheduler.runAllTasks(); // Send the heartbeat.
-
- // Capture the heartbeat search result handler.
- verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
- assertThat(hbc.isValid()).isTrue(); // Not checked yet.
-
- /*
- * Now attempt a bind request, which should be held in a queue until the
- * heart beat completes.
- */
- hbc.bindAsync(newSimpleBindRequest());
- verify(connection, times(0)).bindAsync(any(BindRequest.class));
-
- // Send fake heartbeat response, releasing the bind request.
- verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
- }
-
- @Test
- public void testGetConnection() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- hbc = hbcf.getConnection();
- assertThat(hbc).isNotNull();
- assertThat(hbc.isValid()).isTrue();
- }
-
- @Test
- public void testGetConnectionAsync() throws Exception {
- @SuppressWarnings("unchecked")
- final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
-
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- hbc = hbcf.getConnectionAsync().onSuccess(mockSuccessHandler).getOrThrow();
- assertThat(hbc).isNotNull();
- assertThat(hbc.isValid()).isTrue();
-
- verify(mockSuccessHandler).handleResult(any(Connection.class));
- verifyNoMoreInteractions(mockSuccessHandler);
- }
-
- @Test
- public void testGetConnectionAsyncWithInitialHeartBeatError() throws Exception {
- @SuppressWarnings("unchecked")
- final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
- final PromiseImpl<LdapException, NeverThrowsException> promisedError = PromiseImpl.create();
-
- mockConnectionWithInitialHeartbeatResult(ResultCode.BUSY);
- Promise<? extends Connection, LdapException> promise = hbcf.getConnectionAsync();
- promise.onSuccess(mockSuccessHandler).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(LdapException error) {
- promisedError.handleResult(error);
- }
- });
-
- checkInitialHeartBeatFailure(promisedError.getOrThrow());
-
- try {
- promise.getOrThrow();
- fail("Unexpectedly obtained a connection");
- } catch (final LdapException e) {
- checkInitialHeartBeatFailure(e);
- }
-
- verifyNoMoreInteractions(mockSuccessHandler);
- }
-
- @Test
- public void testGetConnectionWithInitialHeartBeatError() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.BUSY);
- try {
- hbcf.getConnection();
- fail("Unexpectedly obtained a connection");
- } catch (final LdapException e) {
- checkInitialHeartBeatFailure(e);
- }
- }
-
- @Test
- public void testHeartBeatSucceedsThenFails() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
-
- // Get a connection and check that it was pinged.
- hbc = hbcf.getConnection();
-
- verifyHeartBeatSent(connection, 1);
- assertThat(scheduler.isScheduled()).isTrue(); // heartbeater
- assertThat(listeners).hasSize(1);
- assertThat(hbc.isValid()).isTrue();
-
- // Invoke heartbeat before the connection is considered idle.
- scheduler.runAllTasks();
- verifyHeartBeatSent(connection, 1); // No heartbeat sent - not idle yet.
- assertThat(hbc.isValid()).isTrue();
-
- // Invoke heartbeat after the connection is considered idle.
- when(hbcf.timeService.now()).thenReturn(6000L);
- scheduler.runAllTasks();
- verifyHeartBeatSent(connection, 2); // Heartbeat sent.
- assertThat(hbc.isValid()).isTrue();
-
- // Now force the heartbeat to fail.
- mockHeartBeatResponse(connection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN);
- when(hbcf.timeService.now()).thenReturn(11000L);
- scheduler.runAllTasks();
- verifyHeartBeatSent(connection, 3);
- assertThat(hbc.isValid()).isFalse();
-
- // Flush redundant timeout tasks.
- scheduler.runAllTasks();
-
- // Attempt to send a new request: it should fail immediately.
- @SuppressWarnings("unchecked")
- final FailureHandler<LdapException> mockHandler = mock(FailureHandler.class);
- hbc.modifyAsync(newModifyRequest(DN.rootDN())).onFailure(mockHandler);
- final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class);
- verify(mockHandler).handleError(arg.capture());
- assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(
- ResultCode.CLIENT_SIDE_SERVER_DOWN);
-
- assertThat(hbc.isValid()).isFalse();
- assertThat(hbc.isClosed()).isFalse();
- }
-
- @Test
- public void testHeartBeatTimeout() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
-
- // Get a connection and check that it was pinged.
- hbc = hbcf.getConnection();
-
- // Now force the heartbeat to fail due to timeout.
- mockHeartBeatResponse(connection, listeners, null /* no response */);
- when(hbcf.timeService.now()).thenReturn(11000L);
- scheduler.runAllTasks(); // Send the heartbeat.
- verifyHeartBeatSent(connection, 2);
- assertThat(hbc.isValid()).isTrue(); // Not checked yet.
- when(hbcf.timeService.now()).thenReturn(12000L);
- scheduler.runAllTasks(); // Check for heartbeat.
- assertThat(hbc.isValid()).isFalse(); // Now invalid.
- assertThat(hbc.isClosed()).isFalse();
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- @Test(description = "OPENDJ-1348")
- public void testBindPreventsHeartBeatTimeout() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- BindResultLdapPromiseImpl promise = mockBindAsyncResponse();
- hbc = hbcf.getConnection();
-
- /*
- * Send a bind request, trapping the bind call-back so that we can send
- * the response once we have attempted a heartbeat.
- */
- when(hbcf.timeService.now()).thenReturn(11000L);
- hbc.bindAsync(newSimpleBindRequest());
-
- verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
-
- // Verify no heartbeat is sent because there is a bind in progress.
- when(hbcf.timeService.now()).thenReturn(11001L);
- scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
- verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
-
- // Send fake bind response, releasing the heartbeat.
- when(hbcf.timeService.now()).thenReturn(11099L);
- ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
-
- // Check that bind response acts as heartbeat.
- assertThat(hbc.isValid()).isTrue();
- when(hbcf.timeService.now()).thenReturn(11100L);
- scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
- assertThat(hbc.isValid()).isTrue();
- }
-
- @Test(description = "OPENDJ-1348")
- public void testBindTriggersHeartBeatTimeoutWhenTooSlow() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- mockBindAsyncResponse();
- hbc = hbcf.getConnection();
-
- // Send another bind request which will timeout.
- when(hbcf.timeService.now()).thenReturn(20000L);
- hbc.bindAsync(newSimpleBindRequest());
- verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
-
- // Verify no heartbeat is sent because there is a bind in progress.
- when(hbcf.timeService.now()).thenReturn(20001L);
- scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
- verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
-
- // Check that lack of bind response acts as heartbeat timeout.
- assertThat(hbc.isValid()).isTrue();
- when(hbcf.timeService.now()).thenReturn(20100L);
- scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
- assertThat(hbc.isValid()).isFalse();
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Test
- public void testHeartBeatWhileBindInProgress() throws Exception {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- //Trapping the callback of mockedConnection.bindAsync
- BindResultLdapPromiseImpl promise = mockBindAsyncResponse();
- hbc = hbcf.getConnection();
- hbc.bindAsync(newSimpleBindRequest());
-
- verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
-
- /*
- * Now attempt the heartbeat which should not happen because there is a
- * bind in progress.
- */
- when(hbcf.timeService.now()).thenReturn(11000L);
- // Attempt to send the heartbeat.
- scheduler.runAllTasks();
- verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
-
- // Send fake bind response, releasing the heartbeat.
- ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS));
-
- // Attempt to send a heartbeat again.
- when(hbcf.timeService.now()).thenReturn(16000L);
- // Attempt to send the heartbeat.
- scheduler.runAllTasks();
- verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
- }
-
- @Test
- public void testToString() {
- mockConnectionWithInitialHeartbeatResult(ResultCode.SUCCESS);
- assertThat(hbcf.toString()).isNotNull();
- }
-
- private void checkInitialHeartBeatFailure(final LdapException e) {
- /*
- * Initial heartbeat failure should trigger connection exception with
- * heartbeat cause.
- */
- assertThat(e).isInstanceOf(ConnectionException.class);
- assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
- assertThat(e.getCause()).isInstanceOf(LdapException.class);
- assertThat(((LdapException) e.getCause()).getResult().getResultCode()).isEqualTo(ResultCode.BUSY);
- }
-
- private void mockConnectionWithInitialHeartbeatResult(final ResultCode initialHeartBeatResult) {
- listeners = new LinkedList<ConnectionEventListener>();
- connection = mockConnection(listeners);
- when(connection.isValid()).thenReturn(true);
- mockHeartBeatResponse(connection, listeners, initialHeartBeatResult);
-
- // Underlying connection factory.
- factory = mockConnectionFactory(connection);
-
- // Create heart beat connection factory.
- scheduler = new MockScheduler();
- hbcf = new HeartBeatConnectionFactory(factory, 10000, 100, TimeUnit.MILLISECONDS, HEARTBEAT, scheduler);
-
- // Set initial time stamp.
- hbcf.timeService = mockTimeService(0);
- }
-
- private BindResultLdapPromiseImpl mockBindAsyncResponse() {
- final BindResultLdapPromiseImpl bindPromise = newBindLdapPromise(-1, null, null, null, connection);
- doAnswer(new Answer<LdapPromise<BindResult>>() {
- @Override
- public LdapPromise<BindResult> answer(final InvocationOnMock invocation) throws Throwable {
- return bindPromise;
- }
- }).when(connection).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class));
-
- return bindPromise;
- }
-
- private Connection mockHeartBeatResponse(final Connection mockConnection,
- final List<ConnectionEventListener> listeners, final ResultCode resultCode) {
- Answer<LdapPromise<Result>> answer = new Answer<LdapPromise<Result>>() {
- @Override
- public LdapPromise<Result> answer(final InvocationOnMock invocation) throws Throwable {
- if (resultCode == null) {
- return newLdapPromiseImpl();
- }
-
- if (resultCode.isExceptional()) {
- final LdapException error = newLdapException(resultCode);
- if (error instanceof ConnectionException) {
- for (final ConnectionEventListener listener : listeners) {
- listener.handleConnectionError(false, error);
- }
- }
- return newFailedLdapPromise(error);
- } else {
- return newSuccessfulLdapPromise(newResult(resultCode));
- }
- }
- };
-
- doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class), any(SearchResultHandler.class));
- doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class),
- any(IntermediateResponseHandler.class), any(SearchResultHandler.class));
-
- return mockConnection;
- }
-
- private void verifyHeartBeatSent(final Connection connection, final int times) {
- verify(connection, times(times)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class));
- }
-}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
index a52dfdb..de84880 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -76,6 +76,7 @@
import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl;
+import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
@@ -548,9 +549,7 @@
return;
}
sslContext = new SSLContextBuilder().getSSLContext();
- listener =
- new LDAPListener(findFreeSocketAddress(), getInstance(),
- new LDAPListenerOptions().setBacklog(4096));
+ listener = newLDAPListener(findFreeSocketAddress(), getInstance(), new LDAPListenerOptions().setBacklog(4096));
isRunning = true;
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
index 7f7cbea..ebf8b1e 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/MockScheduler.java
@@ -45,7 +45,7 @@
* A mock scheduled executor which allows unit tests to directly invoke
* scheduled tasks without needing to wait.
*/
-final class MockScheduler implements ScheduledExecutorService {
+public final class MockScheduler implements ScheduledExecutorService {
private final class ScheduledCallableFuture<T> implements ScheduledFuture<T>, Callable<T> {
private final Callable<T> callable;
@@ -116,7 +116,7 @@
/** Saved scheduled tasks. */
private final List<Callable<?>> tasks = new CopyOnWriteArrayList<Callable<?>>();
- MockScheduler() {
+ public MockScheduler() {
// Nothing to do.
}
@@ -230,11 +230,11 @@
return tasks.get(0);
}
- boolean isScheduled() {
+ public boolean isScheduled() {
return !tasks.isEmpty();
}
- void runAllTasks() {
+ public void runAllTasks() {
for (final Callable<?> task : tasks) {
runTask0(task);
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java
new file mode 100644
index 0000000..f061b80
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/HeartBeatConnectionFactoryTestCase.java
@@ -0,0 +1,563 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013-2014 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap.spi;
+
+import java.util.logging.Level;
+
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionException;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.MockScheduler;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SdkTestCase;
+import org.forgerock.opendj.ldap.TestCaseUtils;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.BindClient;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.Request;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.PromiseImpl;
+import org.forgerock.util.promise.SuccessHandler;
+import org.mockito.ArgumentCaptor;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.fest.assertions.Assertions.*;
+import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.ResultCode.*;
+import static org.forgerock.opendj.ldap.SearchScope.*;
+import static org.forgerock.opendj.ldap.TestCaseUtils.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+import static org.forgerock.opendj.ldap.responses.Responses.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests the connection pool implementation..
+ */
+@SuppressWarnings("javadoc")
+public class HeartBeatConnectionFactoryTestCase extends SdkTestCase {
+ /** Test timeout in ms for tests which need to wait for simulated network events. */
+ private static final long TEST_TIMEOUT_MS = 30L * 1000L;
+
+ // @formatter:off
+
+ /*
+ * Key features which need testing:
+ *
+ * - lazy scheduler registration and deregistration
+ * - scheduled task only sends heart-beat when connection is open
+ * - connection remains valid when any response is received
+ * - connection remains valid when a heart beat response is received
+ * - connection becomes invalid if no response is received during timeout
+ * - heart beat only sent when connection is idle
+ * - slow bind / startTLS prevents heart beat
+ * - slow heart beat prevents bind / start TLS
+ * - support concurrent bind / start TLS
+ */
+
+ // @formatter:on
+
+ private final class TestHeartBeatConnectionFactory extends AbstractLdapConnectionFactoryImpl {
+
+ public TestHeartBeatConnectionFactory(String host, int port, LDAPOptions options) {
+ super(host, port, options);
+ }
+
+ @Override
+ protected Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0() {
+ PromiseImpl<AbstractLdapConnectionImpl<?>, LdapException> result = PromiseImpl.create();
+ result.handleResult(connection);
+ return result;
+ }
+
+ }
+
+ /**
+ * This class defines a test connection which simulate interactions with
+ * LDAP server by returning promises for asynchronous bind and search
+ * requests.
+ * The promises state depends on the associated ResultCode object.
+ * If a result code is not null, a completed promise is returned.
+ * If a result code is null, the returned promise is pending and could be completed
+ * by a call to the complete promise methods.
+ */
+ private final class MockHeartBeatConnectionImpl extends AbstractLdapConnectionImpl<TestHeartBeatConnectionFactory> {
+
+ private ResultCode heartBeatRC;
+ private ResultCode bindRC;
+ private ResultLdapPromiseImpl<BindRequest, BindResult> bindPromise;
+ private ResultLdapPromiseImpl<SearchRequest, Result> searchPromise;
+
+ private int bindAsyncCalls;
+ private int searchAsyncCalls;
+
+ protected MockHeartBeatConnectionImpl(TestHeartBeatConnectionFactory attachedFactory,
+ ResultCode initialHeartBeatResult) {
+ super(attachedFactory);
+ heartBeatRC = initialHeartBeatResult;
+ }
+
+ @Override
+ protected LdapPromise<Void> abandonAsync0(int messageID, AbandonRequest request) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void bindAsync0(int messageID, BindRequest request, BindClient bindClient,
+ IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
+ bindAsyncCalls++;
+ if (bindRC == null) {
+ bindPromise = (ResultLdapPromiseImpl<BindRequest, BindResult>) getPendingResult(messageID);
+ } else {
+ ResultLdapPromiseImpl<BindRequest, BindResult> promise = removePendingResult(messageID);
+ if (bindRC.isExceptional()) {
+ final LdapException error = newLdapException(bindRC);
+ if (error instanceof ConnectionException) {
+ connectionErrorOccurred(false, error.getResult());
+ }
+ }
+
+ promise.setResultOrError(newBindResult(bindRC));
+ }
+ }
+
+ private void completeBindPromise(ResultCode result) {
+ ResultLdapPromiseImpl<BindRequest, BindResult> promise = removePendingResult(bindPromise.getRequestID());
+ promise.setResultOrError(newBindResult(result));
+ bindPromise = null;
+ }
+
+ @Override
+ protected void close0(int messageID, UnbindRequest request, String reason) {
+ //noting to do.
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected <R extends Request> void writeRequest(int messageID, R request,
+ IntermediateResponseHandler intermediateResponseHandler, RequestWriter<R> requestWriter)
+ throws LdapException {
+ if (request instanceof SearchRequest) {
+ searchAsyncCalls++;
+ if (heartBeatRC == null) {
+ searchPromise = (ResultLdapPromiseImpl<SearchRequest, Result>) getPendingResult(messageID);
+ } else {
+ ResultLdapPromiseImpl<SearchRequest, Result> promise = removePendingResult(messageID);
+ if (heartBeatRC.isExceptional()) {
+ final LdapException error = newLdapException(heartBeatRC);
+ if (error instanceof ConnectionException) {
+ connectionErrorOccurred(false, error.getResult());
+ }
+ }
+
+ promise.setResultOrError(newResult(heartBeatRC));
+ }
+ }
+ }
+
+ public void completeSearchPromise(ResultCode resultCode) {
+ ResultLdapPromiseImpl<SearchRequest, Result> promise = removePendingResult(searchPromise.getRequestID());
+ promise.setResultOrError(newResult(resultCode));
+ searchPromise = null;
+ }
+
+ @Override
+ protected boolean isTLSEnabled() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ }
+
+ private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT, "(objectclass=*)", "1.1");
+ private static final BindRequest BIND_REQUEST = newSimpleBindRequest();
+
+ private MockHeartBeatConnectionImpl connection;
+ private Connection hbc;
+ private TestHeartBeatConnectionFactory hbcf;
+ private MockScheduler scheduler;
+
+ /**
+ * Disables logging before the tests.
+ */
+ @BeforeClass
+ public void disableLogging() {
+ TestCaseUtils.setDefaultLogLevel(Level.SEVERE);
+ }
+
+ /**
+ * Re-enable logging after the tests.
+ */
+ @AfterClass
+ public void enableLogging() {
+ TestCaseUtils.setDefaultLogLevel(Level.INFO);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() {
+ try {
+ if (hbc != null) {
+ hbc.close();
+ assertThat(hbc.isValid()).isFalse();
+ assertThat(hbc.isClosed()).isTrue();
+ }
+ scheduler.runAllTasks(); // Flush any remaining timeout tasks.
+ assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check.
+ hbcf.close();
+ } finally {
+ connection = null;
+ hbcf = null;
+ scheduler = null;
+ hbc = null;
+ }
+ }
+
+ @Test
+ public void testBindWhileHeartBeatInProgress() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ hbc = hbcf.getConnection();
+
+ connection.heartBeatRC = null;
+ when(hbcf.timeService.now()).thenReturn(11000L);
+ scheduler.runAllTasks(); // Send the heartbeat.
+
+ // Check that connection was used for both initial request and first heart beat request.
+ verifySearchAsyncCalled(2);
+ assertThat(hbc.isValid()).isTrue(); // Not checked yet.
+
+ // Now attempt a bind request, which should be held in a queue until the heart beat completes.
+ hbc.bindAsync(newSimpleBindRequest());
+ verifyBindAsyncCalled(0);
+
+ // Send fake heartbeat response, releasing the bind request.
+ connection.completeSearchPromise(ResultCode.SUCCESS);
+ verifyBindAsyncCalled(1);
+ }
+
+ @Test
+ public void testGetConnection() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ hbc = hbcf.getConnection();
+ assertThat(hbc).isNotNull();
+ assertThat(hbc.isValid()).isTrue();
+ }
+
+ @Test
+ public void testGetConnectionAsync() throws Exception {
+ @SuppressWarnings("unchecked")
+ final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
+
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ hbc = hbcf.getConnectionAsync().onSuccess(mockSuccessHandler).getOrThrow();
+ assertThat(hbc).isNotNull();
+ assertThat(hbc.isValid()).isTrue();
+
+ verify(mockSuccessHandler).handleResult(any(Connection.class));
+ verifyNoMoreInteractions(mockSuccessHandler);
+ }
+
+ @Test
+ public void testGetConnectionAsyncWithInitialHeartBeatError() throws Exception {
+ @SuppressWarnings("unchecked")
+ final SuccessHandler<Connection> mockSuccessHandler = mock(SuccessHandler.class);
+ final PromiseImpl<LdapException, NeverThrowsException> promisedError = PromiseImpl.create();
+
+ mockConnectionWithInitialHeartBeatRC(ResultCode.BUSY);
+ Promise<? extends Connection, LdapException> promise = hbcf.getConnectionAsync();
+ promise.onSuccess(mockSuccessHandler).onFailure(new FailureHandler<LdapException>() {
+ @Override
+ public void handleError(LdapException error) {
+ promisedError.handleResult(error);
+ }
+ });
+
+ checkInitialHeartBeatFailure(promisedError.getOrThrow());
+
+ try {
+ promise.getOrThrow();
+ fail("Unexpectedly obtained a connection");
+ } catch (final LdapException e) {
+ checkInitialHeartBeatFailure(e);
+ }
+
+ verifyNoMoreInteractions(mockSuccessHandler);
+ }
+
+ @Test
+ public void testGetConnectionWithInitialHeartBeatError() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.BUSY);
+ try {
+ hbcf.getConnection();
+ fail("Unexpectedly obtained a connection");
+ } catch (final LdapException e) {
+ checkInitialHeartBeatFailure(e);
+ }
+ }
+
+ @Test
+ public void testHeartBeatSucceedsThenFails() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+
+ // Get a connection and check that it was pinged.
+ hbc = hbcf.getConnection();
+
+ verifySearchAsyncCalled(1);
+ assertThat(scheduler.isScheduled()).isTrue(); // heartbeater
+ assertThat(hbc.isValid()).isTrue();
+
+ // Invoke heartbeat before the connection is considered idle.
+ scheduler.runAllTasks();
+ verifySearchAsyncCalled(1); // No heartbeat sent - not idle yet.
+ assertThat(hbc.isValid()).isTrue();
+
+ // Invoke heartbeat after the connection is considered idle.
+ when(hbcf.timeService.now()).thenReturn(6000L);
+ scheduler.runAllTasks();
+ verifySearchAsyncCalled(2); // Heartbeat sent.
+ assertThat(hbc.isValid()).isTrue();
+
+ // Now force the heartbeat to fail.
+ connection.heartBeatRC = ResultCode.CLIENT_SIDE_SERVER_DOWN;
+ when(hbcf.timeService.now()).thenReturn(11000L);
+ scheduler.runAllTasks();
+ verifySearchAsyncCalled(3);
+ assertThat(hbc.isValid()).isFalse();
+
+ // Flush redundant timeout tasks.
+ scheduler.runAllTasks();
+
+ // Attempt to send a new request: it should fail immediately.
+ @SuppressWarnings("unchecked")
+ final FailureHandler<LdapException> mockHandler = mock(FailureHandler.class);
+ hbc.modifyAsync(newModifyRequest(DN.rootDN())).onFailure(mockHandler);
+ final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class);
+ verify(mockHandler).handleError(arg.capture());
+ assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
+
+ assertThat(hbc.isValid()).isFalse();
+ assertThat(hbc.isClosed()).isFalse();
+ }
+
+ @Test
+ public void testHeartBeatTimeout() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+
+ // Get a connection and check that it was pinged.
+ hbc = hbcf.getConnection();
+
+ // Now force the heartbeat to fail due to timeout.
+ connection.heartBeatRC = null; /* no response */
+ when(hbcf.timeService.now()).thenReturn(11000L);
+ scheduler.runAllTasks(); // Send the heartbeat.
+ verifySearchAsyncCalled(2);
+ assertThat(hbc.isValid()).isTrue(); // Not checked yet.
+ when(hbcf.timeService.now()).thenReturn(12000L);
+ scheduler.runAllTasks(); // Check for heartbeat.
+ assertThat(hbc.isValid()).isFalse(); // Now invalid.
+ assertThat(hbc.isClosed()).isFalse();
+ }
+
+ @Test(description = "OPENDJ-1348")
+ public void testBindPreventsHeartBeatTimeout() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ mockNoBindAsyncResponse();
+ hbc = hbcf.getConnection();
+
+ /*
+ * Send a bind request, trapping the bind call-back so that we can send
+ * the response once we have attempted a heartbeat.
+ */
+ when(hbcf.timeService.now()).thenReturn(11000L);
+ hbc.bindAsync(newSimpleBindRequest());
+ verifyBindAsyncCalled(1);
+
+ // Verify no heartbeat is sent because there is a bind in progress.
+ when(hbcf.timeService.now()).thenReturn(11001L);
+ scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
+ verifySearchAsyncCalled(1);
+
+ // Send fake bind response, releasing the heartbeat.
+ when(hbcf.timeService.now()).thenReturn(11099L);
+ connection.completeBindPromise(SUCCESS);
+
+ // Check that bind response acts as heartbeat.
+ assertThat(hbc.isValid()).isTrue();
+ when(hbcf.timeService.now()).thenReturn(11100L);
+ scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
+ assertThat(hbc.isValid()).isTrue();
+ }
+
+ @Test(description = "OPENDJ-1348")
+ public void testBindTriggersHeartBeatTimeoutWhenTooSlow() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ mockNoBindAsyncResponse();
+ hbc = hbcf.getConnection();
+
+ // Send another bind request which will timeout.
+ when(hbcf.timeService.now()).thenReturn(20000L);
+ hbc.bindAsync(newSimpleBindRequest());
+ verifyBindAsyncCalled(1);
+
+ // Verify no heartbeat is sent because there is a bind in progress.
+ when(hbcf.timeService.now()).thenReturn(20001L);
+ scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat()
+ verifySearchAsyncCalled(1);
+
+ // Check that lack of bind response acts as heartbeat timeout.
+ assertThat(hbc.isValid()).isTrue();
+ when(hbcf.timeService.now()).thenReturn(20100L);
+ scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.checkForHeartBeat()
+ assertThat(hbc.isValid()).isFalse();
+ }
+
+ @Test
+ public void testHeartBeatWhileBindInProgress() throws Exception {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ //Trapping the callback of mockedConnection.bindAsync
+ mockNoBindAsyncResponse();
+ hbc = hbcf.getConnection();
+ hbc.bindAsync(newSimpleBindRequest());
+
+ verifyBindAsyncCalled(1);
+
+ //Now attempt the heartbeat which should not happen because there is a bind in progress.
+ when(hbcf.timeService.now()).thenReturn(11000L);
+ // Attempt to send the heartbeat.
+ scheduler.runAllTasks();
+ verifySearchAsyncCalled(1);
+
+ // Send fake bind response, releasing the heartbeat.
+ connection.completeBindPromise(SUCCESS);
+
+ // Attempt to send a heartbeat again.
+ when(hbcf.timeService.now()).thenReturn(16000L);
+ // Attempt to send the heartbeat.
+ scheduler.runAllTasks();
+ verifySearchAsyncCalled(2);
+ }
+
+ @Test(description = "OPENDJ-1607", timeOut = TEST_TIMEOUT_MS)
+ public void testAuthenticatedTimeout() throws Exception {
+ // Check that we detect timeout if server does not reply.
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS,
+ new LDAPOptions().setBindRequest(BIND_REQUEST));
+ mockNoBindAsyncResponse();
+ try {
+ // Attempt to send a bind request.
+ hbcf.getConnectionAsync().getOrThrow();
+ fail("Unexpectedly obtained a connection");
+ } catch (LdapException e) {
+ assertThat(e).isInstanceOf(ConnectionException.class);
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
+ }
+ }
+
+ @Test(description = "OPENDJ-1607")
+ public void testHearBeatAgainstSecureServer() throws Exception {
+ // Check that the initial request is a bind request.
+ mockConnectionWithInitialHeartBeatRC(ResultCode.AUTHORIZATION_DENIED,
+ new LDAPOptions().setBindRequest(BIND_REQUEST));
+ mockBindAsyncResponse(ResultCode.SUCCESS);
+ hbc = hbcf.getConnection();
+ verifyBindAsyncCalled(1);
+ }
+
+ @Test
+ public void testToString() {
+ mockConnectionWithInitialHeartBeatRC(ResultCode.SUCCESS);
+ assertThat(hbcf.toString()).isNotNull();
+ }
+
+ private void checkInitialHeartBeatFailure(final LdapException e) {
+ // Initial heartbeat failure should trigger connection exception with heartbeat cause.
+ assertThat(e).isInstanceOf(ConnectionException.class);
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN);
+ assertThat(e.getCause()).isInstanceOf(LdapException.class);
+ assertThat(((LdapException) e.getCause()).getResult().getResultCode()).isEqualTo(ResultCode.BUSY);
+ }
+
+ private void mockConnectionWithInitialHeartBeatRC(final ResultCode initialHeartBeatRC) {
+ mockConnectionWithInitialHeartBeatRC(
+ initialHeartBeatRC,
+ new LDAPOptions().setHeartBeatInterval(10, SECONDS)
+ .setHeartBeatSearchRequest(HEARTBEAT)
+ .setTimeout(100, MILLISECONDS));
+ }
+
+ private void mockConnectionWithInitialHeartBeatRC(final ResultCode initialHeartBeatRC, LDAPOptions options) {
+ // Create heart beat connection factory.
+ scheduler = new MockScheduler();
+ if (options.getBindRequest() == null) {
+ options.setHeartBeatInterval(10000, MILLISECONDS).setHeartBeatSearchRequest(HEARTBEAT)
+ .setHeartBeatScheduler(scheduler);
+ }
+
+ options.setTimeout(100, MILLISECONDS);
+ hbcf = new TestHeartBeatConnectionFactory("localhost", 1111, options);
+ // Set initial time stamp.
+ hbcf.timeService = mockTimeService(0);
+
+ connection = new MockHeartBeatConnectionImpl(hbcf, initialHeartBeatRC);
+ }
+
+ private void mockNoBindAsyncResponse() {
+ connection.bindRC = null;
+ }
+
+ private void mockBindAsyncResponse(final ResultCode resultCode) {
+ connection.bindRC = resultCode;
+ }
+
+ private void verifyBindAsyncCalled(int times) {
+ assertThat(connection.bindAsyncCalls).isEqualTo(times);
+ }
+
+ private void verifySearchAsyncCalled(final int times) {
+ assertThat(connection.searchAsyncCalls).isEqualTo(times);
+ }
+}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
index e332639..b59f093 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -21,60 +21,37 @@
* CDDL HEADER END
*
*
- * Copyright 2010 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS
+ * Copyright 2014 ForgeRock AS
*/
+
package org.forgerock.opendj.grizzly;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.LDAPWriter;
-import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
-import org.forgerock.opendj.ldap.ConnectionEventListener;
-import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
-import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SSLContextBuilder;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.TimeoutEventListener;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
-import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.BindClient;
import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.CompareRequest;
-import org.forgerock.opendj.ldap.requests.DeleteRequest;
-import org.forgerock.opendj.ldap.requests.ExtendedRequest;
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
+import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionImpl;
import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl;
-import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl;
import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl;
-import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl;
import org.forgerock.util.Reject;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.filterchain.Filter;
@@ -85,12 +62,7 @@
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
-import static com.forgerock.opendj.grizzly.GrizzlyMessages.*;
-
-/**
- * LDAP connection implementation.
- */
-final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements TimeoutEventListener {
+final class GrizzlyLDAPConnection extends AbstractLdapConnectionImpl<GrizzlyLDAPConnectionFactory> {
/**
* A dummy SSL client engine configurator as SSLFilter only needs client
* config. This prevents Grizzly from needlessly using JVM defaults which
@@ -99,637 +71,87 @@
private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR;
static {
try {
- DUMMY_SSL_ENGINE_CONFIGURATOR =
- new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager(
- TrustManagers.distrustAll()).getSSLContext());
+ DUMMY_SSL_ENGINE_CONFIGURATOR = new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager(
+ TrustManagers.distrustAll()).getSSLContext());
} catch (GeneralSecurityException e) {
// This should never happen.
throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e);
}
}
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
- private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
private final org.glassfish.grizzly.Connection<?> connection;
- private final AtomicInteger nextMsgID = new AtomicInteger(1);
- private final GrizzlyLDAPConnectionFactory factory;
- private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingRequests =
- new ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>>();
- private final Object stateLock = new Object();
- /** Guarded by stateLock. */
- private Result connectionInvalidReason;
- private boolean failedDueToDisconnect;
- private boolean isClosed;
- private boolean isFailed;
- private List<ConnectionEventListener> listeners;
- /**
- * Create a LDAP Connection with provided Grizzly connection and LDAP
- * connection factory.
- *
- * @param connection
- * actual connection
- * @param factory
- * factory that provides LDAP connections
- */
- GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection<?> connection,
- final GrizzlyLDAPConnectionFactory factory) {
+ @SuppressWarnings("rawtypes")
+ public GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection connection,
+ final GrizzlyLDAPConnectionFactory attachedFactory) {
+ super(attachedFactory);
this.connection = connection;
- this.factory = factory;
}
@Override
- public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
- /*
- * Need to be careful here since both abandonAsync and Promise.cancel can
- * be called separately by the client application. Therefore
- * promise.cancel() should abandon the request, and abandonAsync should
- * cancel the promise. In addition, bind or StartTLS requests cannot be
- * abandoned.
- */
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- /*
- * If there is a bind or startTLS in progress then it must be
- * this request which is being abandoned. The following check
- * will prevent it from happening.
- */
- checkBindOrStartTLSInProgress();
- }
- } catch (final LdapException e) {
- return newFailedLdapPromise(e);
- }
-
- // Remove the promise associated with the request to be abandoned.
- final ResultLdapPromiseImpl<?, ?> pendingRequest = pendingRequests.remove(request.getRequestID());
- if (pendingRequest == null) {
- /*
- * There has never been a request with the specified message ID or
- * the response has already been received and handled. We can ignore
- * this abandon request.
- */
- return newSuccessfulLdapPromise((Void) null);
- }
-
- /*
- * This will cancel the promise, but will also recursively invoke this
- * method. Since the pending request has been removed, there is no risk
- * of an infinite loop.
- */
- pendingRequest.cancel(false);
-
- /*
- * FIXME: there's a potential race condition here if a bind or startTLS
- * is initiated just after we removed the pending request.
- */
- return sendAbandonRequest(request);
- }
-
- private LdapPromise<Void> sendAbandonRequest(final AbandonRequest request) {
+ protected LdapPromise<Void> abandonAsync0(final int messageID, final AbandonRequest request) {
final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
try {
- final int messageID = nextMsgID.getAndIncrement();
writer.writeAbandonRequest(messageID, request);
connection.write(writer.getASN1Writer().getBuffer(), null);
return newSuccessfulLdapPromise((Void) null, messageID);
} catch (final IOException e) {
- return newFailedLdapPromise(adaptRequestIOException(e));
+ return newFailedLdapPromise(newLdapException(adaptRequestIOException(e)));
} finally {
GrizzlyUtils.recycleWriter(writer);
}
}
@Override
- public LdapPromise<Result> addAsync(final AddRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ResultLdapPromiseImpl<AddRequest, Result> promise =
- newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
+ protected void bindAsync0(final int messageID, final BindRequest request, final BindClient bindClient,
+ final IntermediateResponseHandler intermediateResponseHandler) throws LdapException {
+ checkConnectionIsValid();
+ final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeAddRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public void addConnectionEventListener(final ConnectionEventListener listener) {
- Reject.ifNull(listener);
- final boolean notifyClose;
- final boolean notifyErrorOccurred;
- synchronized (stateLock) {
- notifyClose = isClosed;
- notifyErrorOccurred = isFailed;
- if (!isClosed) {
- if (listeners == null) {
- listeners = new CopyOnWriteArrayList<ConnectionEventListener>();
- }
- listeners.add(listener);
- }
- }
- if (notifyErrorOccurred) {
- // Use the reason provided in the disconnect notification.
- listener.handleConnectionError(failedDueToDisconnect,
- newLdapException(connectionInvalidReason));
- }
- if (notifyClose) {
- listener.handleConnectionClosed();
+ // Use the bind client to get the initial request instead of
+ // using the bind request passed to this method.
+ final GenericBindRequest initialRequest = bindClient.nextBindRequest();
+ writer.writeBindRequest(messageID, 3, initialRequest);
+ connection.write(writer.getASN1Writer().getBuffer(), null);
+ } catch (final IOException e) {
+ throw newLdapException(adaptRequestIOException(e));
+ } finally {
+ GrizzlyUtils.recycleWriter(writer);
}
}
@Override
- public LdapPromise<BindResult> bindAsync(final BindRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final BindClient context;
+ protected void close0(final int messageID, UnbindRequest unbindRequest, String reason) {
+ Reject.ifNull(unbindRequest);
+ // This is the final client initiated close then release the connection
+ // and release resources.
+ final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
try {
- context = request.createBindClient(Connections.getHostString(factory.getSocketAddress()));
- } catch (final LdapException e) {
- return newFailedLdapPromise(e, messageID);
+ writer.writeUnbindRequest(messageID, unbindRequest);
+ connection.write(writer.getASN1Writer().getBuffer(), null);
+ } catch (final Exception ignore) {
+ /*
+ * Underlying channel probably blown up. Ignore all errors,
+ * including possibly runtime exceptions (see OPENDJ-672).
+ */
+ } finally {
+ GrizzlyUtils.recycleWriter(writer);
}
+ getFactory().getTimeoutChecker().removeListener(this);
+ connection.closeSilently();
+ }
- final BindResultLdapPromiseImpl promise =
- newBindLdapPromise(messageID, request, context, intermediateResponseHandler, this);
-
+ @Override
+ protected <R extends Request> void writeRequest(final int messageID, final R request,
+ final IntermediateResponseHandler intermediateResponseHandler, final RequestWriter<R> requestWriter) {
+ final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- if (!pendingRequests.isEmpty()) {
- promise.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
- "There are other operations pending on this connection"));
- return promise;
- }
- if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
- promise.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage(
- "Bind or Start TLS operation in progress"));
- return promise;
- }
- pendingRequests.put(messageID, promise);
- }
-
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- // Use the bind client to get the initial request instead of
- // using the bind request passed to this method.
- final GenericBindRequest initialRequest = context.nextBindRequest();
- writer.writeBindRequest(messageID, 3, initialRequest);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- bindOrStartTLSInProgress.set(false);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
-
- return promise;
- }
-
- @Override
- public void close(final UnbindRequest request, final String reason) {
- // FIXME: I18N need to internationalize this message.
- Reject.ifNull(request);
- close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
- .setDiagnosticMessage(reason != null ? reason : "Connection closed by client"));
- }
-
- @Override
- public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ResultLdapPromiseImpl<CompareRequest, CompareResult> promise =
- newCompareLdapPromise(messageID, request, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeCompareRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public LdapPromise<Result> deleteAsync(final DeleteRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ResultLdapPromiseImpl<DeleteRequest, Result> promise =
- newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeDeleteRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ExtendedResultLdapPromiseImpl<R> promise =
- newExtendedLdapPromise(messageID, request, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- if (StartTLSExtendedRequest.OID.equals(request.getOID())) {
- if (!pendingRequests.isEmpty()) {
- promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
- ResultCode.OPERATIONS_ERROR, "", "There are pending operations on this connection"));
- return promise;
- } else if (isTLSEnabled()) {
- promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
- ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled"));
- return promise;
- } else if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
- promise.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
- ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress"));
- return promise;
- }
- } else {
- checkBindOrStartTLSInProgress();
- }
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeExtendedRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- bindOrStartTLSInProgress.set(false);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public boolean isClosed() {
- synchronized (stateLock) {
- return isClosed;
- }
- }
-
- @Override
- public boolean isValid() {
- synchronized (stateLock) {
- return isValid0();
- }
- }
-
- @Override
- public LdapPromise<Result> modifyAsync(final ModifyRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ResultLdapPromiseImpl<ModifyRequest, Result> promise =
- newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeModifyRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
- final IntermediateResponseHandler intermediateResponseHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final ResultLdapPromiseImpl<ModifyDNRequest, Result> promise =
- newResultLdapPromise(messageID, request, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeModifyDNRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public void removeConnectionEventListener(final ConnectionEventListener listener) {
- Reject.ifNull(listener);
- synchronized (stateLock) {
- if (listeners != null) {
- listeners.remove(listener);
- }
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public LdapPromise<Result> searchAsync(final SearchRequest request,
- final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
- final int messageID = nextMsgID.getAndIncrement();
- final SearchResultLdapPromiseImpl promise =
- newSearchLdapPromise(messageID, request, entryHandler, intermediateResponseHandler, this);
- try {
- synchronized (stateLock) {
- checkConnectionIsValid();
- checkBindOrStartTLSInProgress();
- pendingRequests.put(messageID, promise);
- }
- try {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeSearchRequest(messageID, request);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- } catch (final IOException e) {
- pendingRequests.remove(messageID);
- throw adaptRequestIOException(e);
- }
- } catch (final LdapException e) {
- promise.adaptErrorResult(e.getResult());
- }
- return promise;
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("LDAPConnection(");
- builder.append(connection.getLocalAddress());
- builder.append(',');
- builder.append(connection.getPeerAddress());
- builder.append(')');
- return builder.toString();
- }
-
- @Override
- public long handleTimeout(final long currentTime) {
- final long timeout = factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
- if (timeout <= 0) {
- return 0;
- }
-
- long delay = timeout;
- for (final ResultLdapPromiseImpl<?, ?> promise : pendingRequests.values()) {
- if (promise == null || !promise.checkForTimeout()) {
- continue;
- }
- final long diff = (promise.getTimestamp() + timeout) - currentTime;
- if (diff > 0) {
- // Will expire in diff milliseconds.
- delay = Math.min(delay, diff);
- } else if (pendingRequests.remove(promise.getRequestID()) == null) {
- // Result arrived at the same time.
- continue;
- } else if (promise.isBindOrStartTLS()) {
- /*
- * No other operations can be performed while a bind or StartTLS
- * request is active, so we cannot time out the request. We
- * therefore have a choice: either ignore timeouts for these
- * operations, or enforce them but doing so requires
- * invalidating the connection. We'll do the latter, since
- * ignoring timeouts could cause the application to hang.
- */
- logger.debug(LocalizableMessage.raw("Failing bind or StartTLS request due to timeout %s"
- + "(connection will be invalidated): ", promise));
- final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
- LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(timeout).toString());
- promise.adaptErrorResult(result);
-
- // Fail the connection.
- final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
- LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(timeout).toString());
- connectionErrorOccurred(errorResult);
- } else {
- logger.debug(LocalizableMessage.raw("Failing request due to timeout: %s", promise));
- final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
- LDAP_CONNECTION_REQUEST_TIMEOUT.get(timeout).toString());
- promise.adaptErrorResult(result);
-
- /*
- * FIXME: there's a potential race condition here if a bind or
- * startTLS is initiated just after we check the boolean. It
- * seems potentially even more dangerous to send the abandon
- * request while holding the state lock, since a blocking write
- * could hang the application.
- */
- // if (!bindOrStartTLSInProgress.get()) {
- // sendAbandonRequest(newAbandonRequest(promise.getRequestID()));
- // }
- }
- }
- return delay;
- }
-
- @Override
- public long getTimeout() {
- return factory.getLDAPOptions().getTimeout(TimeUnit.MILLISECONDS);
- }
-
- /**
- * Closes this connection, invoking event listeners as needed.
- *
- * @param unbindRequest
- * The client provided unbind request if this is a client
- * initiated close, or {@code null} if the connection has failed.
- * @param isDisconnectNotification
- * {@code true} if this is a connection failure signalled by a
- * server disconnect notification.
- * @param reason
- * The result indicating why the connection was closed.
- */
- void close(final UnbindRequest unbindRequest, final boolean isDisconnectNotification,
- final Result reason) {
- final boolean notifyClose;
- final boolean notifyErrorOccurred;
- final List<ConnectionEventListener> tmpListeners;
- synchronized (stateLock) {
- if (isClosed) {
- // Already closed locally.
- return;
- } else if (unbindRequest != null) {
- // Local close.
- notifyClose = true;
- notifyErrorOccurred = false;
- isClosed = true;
- tmpListeners = listeners;
- listeners = null; // Prevent future invocations.
- if (connectionInvalidReason == null) {
- connectionInvalidReason = reason;
- }
- } else if (isFailed) {
- // Already failed.
- return;
- } else {
- // Connection has failed and this is the first indication.
- notifyClose = false;
- notifyErrorOccurred = true;
- isFailed = true;
- failedDueToDisconnect = isDisconnectNotification;
- connectionInvalidReason = reason;
- tmpListeners = listeners; // Keep list for client close.
- }
- }
-
- // First abort all outstanding requests.
- for (final int requestID : pendingRequests.keySet()) {
- final ResultLdapPromiseImpl<?, ?> promise = pendingRequests.remove(requestID);
- if (promise != null) {
- promise.adaptErrorResult(connectionInvalidReason);
- }
- }
-
- /*
- * If this is the final client initiated close then release close the
- * connection and release resources.
- */
- if (notifyClose) {
- final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter();
- try {
- writer.writeUnbindRequest(nextMsgID.getAndIncrement(), unbindRequest);
- connection.write(writer.getASN1Writer().getBuffer(), null);
- } catch (final Exception ignore) {
- /*
- * Underlying channel probably blown up. Ignore all errors,
- * including possibly runtime exceptions (see OPENDJ-672).
- */
- } finally {
- GrizzlyUtils.recycleWriter(writer);
- }
- factory.getTimeoutChecker().removeListener(this);
- connection.closeSilently();
- factory.releaseTransportAndTimeoutChecker();
- }
-
- // Notify listeners.
- if (tmpListeners != null) {
- if (notifyErrorOccurred) {
- for (final ConnectionEventListener listener : tmpListeners) {
- // Use the reason provided in the disconnect notification.
- listener.handleConnectionError(isDisconnectNotification, newLdapException(reason));
- }
- }
- if (notifyClose) {
- for (final ConnectionEventListener listener : tmpListeners) {
- listener.handleConnectionClosed();
- }
- }
- }
- }
-
- int continuePendingBindRequest(final BindResultLdapPromiseImpl promise) throws LdapException {
- final int newMsgID = nextMsgID.getAndIncrement();
- synchronized (stateLock) {
- checkConnectionIsValid();
- pendingRequests.put(newMsgID, promise);
- }
- return newMsgID;
- }
-
- LDAPOptions getLDAPOptions() {
- return factory.getLDAPOptions();
- }
-
- ResultLdapPromiseImpl<?, ?> getPendingRequest(final Integer messageID) {
- return pendingRequests.get(messageID);
- }
-
- void handleUnsolicitedNotification(final ExtendedResult result) {
- final List<ConnectionEventListener> tmpListeners;
- synchronized (stateLock) {
- tmpListeners = listeners;
- }
- if (tmpListeners != null) {
- for (final ConnectionEventListener listener : tmpListeners) {
- listener.handleUnsolicitedNotification(result);
- }
+ requestWriter.writeRequest(messageID, writer, request);
+ connection.write(writer.getASN1Writer().getBuffer(), null);
+ } catch (final IOException e) {
+ removePendingResult(messageID).setResultOrError(adaptRequestIOException(e));
+ } finally {
+ GrizzlyUtils.recycleWriter(writer);
}
}
@@ -741,19 +163,14 @@
* The filter to be installed.
*/
void installFilter(final Filter filter) {
- synchronized (stateLock) {
+ synchronized (state) {
GrizzlyUtils.addFilterToConnection(filter, connection);
}
}
- /**
- * Indicates whether or not TLS is enabled on this connection.
- *
- * @return {@code true} if TLS is enabled on this connection, otherwise
- * {@code false}.
- */
- boolean isTLSEnabled() {
- synchronized (stateLock) {
+ @Override
+ protected boolean isTLSEnabled() {
+ synchronized (state) {
final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
for (final Filter filter : currentFilterChain) {
if (filter instanceof SSLFilter) {
@@ -764,17 +181,9 @@
}
}
- ResultLdapPromiseImpl<?, ?> removePendingRequest(final Integer messageID) {
- return pendingRequests.remove(messageID);
- }
-
- void setBindOrStartTLSInProgress(final boolean state) {
- bindOrStartTLSInProgress.set(state);
- }
-
void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites,
final CompletionHandler<SSLEngine> completionHandler) throws IOException {
- synchronized (stateLock) {
+ synchronized (state) {
if (isTLSEnabled()) {
throw new IllegalStateException("TLS already enabled");
}
@@ -791,43 +200,41 @@
}
}
- private LdapException adaptRequestIOException(final IOException e) {
+ private Result adaptRequestIOException(final IOException e) {
// FIXME: what other sort of IOExceptions can be thrown?
// FIXME: Is this the best result code?
final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
- connectionErrorOccurred(errorResult);
- return newLdapException(errorResult);
+ connectionErrorOccurred(false, errorResult);
+ return errorResult;
}
- private void checkBindOrStartTLSInProgress() throws LdapException {
- if (bindOrStartTLSInProgress.get()) {
- throw newLdapException(ResultCode.OPERATIONS_ERROR, "Bind or Start TLS operation in progress");
- }
+ @Override
+ protected ResultLdapPromiseImpl<?, ?> getPendingResult(int messageID) {
+ return super.getPendingResult(messageID);
+ };
+
+ @Override
+ protected <R extends Request, S extends Result> ResultLdapPromiseImpl<R, S> removePendingResult(
+ final Integer messageID) {
+ return super.removePendingResult(messageID);
}
- private void checkConnectionIsValid() throws LdapException {
- if (!isValid0()) {
- if (failedDueToDisconnect) {
- /*
- * Connection termination was triggered remotely. We don't want
- * to blindly pass on the result code to requests since it could
- * be confused for a genuine response. For example, if the
- * disconnect contained the invalidCredentials result code then
- * this could be misinterpreted as a genuine authentication
- * failure for subsequent bind requests.
- */
- throw newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, "Connection closed by server");
- } else {
- throw newLdapException(connectionInvalidReason);
- }
- }
+ @Override
+ protected void connectionErrorOccurred(boolean isDisconnectNotification, Result reason) {
+ super.connectionErrorOccurred(isDisconnectNotification, reason);
}
- private void connectionErrorOccurred(final Result reason) {
- close(null, false, reason);
+ @Override
+ protected void handleUnsolicitedNotification(final ExtendedResult notification) {
+ super.handleUnsolicitedNotification(notification);
}
- private boolean isValid0() {
- return !isFailed && !isClosed;
+ int continuePendingBindRequest(BindResultLdapPromiseImpl promise)
+ throws LdapException {
+ final int newMsgID = newMessageID();
+ checkConnectionIsValid();
+ addPendingResult(newMsgID, promise);
+
+ return newMsgID;
}
}
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 8df6801..9a1f278 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
@@ -28,11 +28,8 @@
package org.forgerock.opendj.grizzly;
import java.io.IOException;
-import java.net.InetSocketAddress;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLEngine;
@@ -43,15 +40,12 @@
import org.forgerock.opendj.ldap.ResultCode;
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;
+import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionFactoryImpl;
+import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionImpl;
import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl;
-import org.forgerock.util.promise.FailureHandler;
+import org.forgerock.util.promise.Function;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.SuccessHandler;
-import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.SocketConnectorHandler;
import org.glassfish.grizzly.filterchain.FilterChain;
@@ -70,19 +64,21 @@
/**
* LDAP connection factory implementation using Grizzly for transport.
*/
-public final class GrizzlyLDAPConnectionFactory implements LDAPConnectionFactoryImpl {
+public final class GrizzlyLDAPConnectionFactory extends AbstractLdapConnectionFactoryImpl implements
+ LDAPConnectionFactoryImpl {
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
- * Adapts a Grizzly connection completion handler to an LDAP connection promise.
+ * Adapts a Grizzly connection completion handler to an LDAP connection
+ * promise.
*/
@SuppressWarnings("rawtypes")
- private final class CompletionHandlerAdapter implements
- CompletionHandler<org.glassfish.grizzly.Connection>, TimeoutEventListener {
- private final PromiseImpl<Connection, LdapException> promise;
+ private final class CompletionHandlerAdapter extends EmptyCompletionHandler<org.glassfish.grizzly.Connection>
+ implements TimeoutEventListener {
+ private final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise;
private final long timeoutEndTime;
- private CompletionHandlerAdapter(final PromiseImpl<Connection, LdapException> promise) {
+ private CompletionHandlerAdapter(final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise) {
this.promise = promise;
final long timeoutMS = getTimeout();
this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0;
@@ -90,68 +86,11 @@
}
@Override
- public void cancelled() {
- // Ignore this.
- }
-
- @Override
- public void completed(final org.glassfish.grizzly.Connection result) {
- // Adapt the connection.
- final GrizzlyLDAPConnection connection = adaptConnection(result);
-
- // Plain connection.
- if (options.getSSLContext() == null) {
- onSuccess(connection);
- return;
- }
-
- // Start TLS or install SSL layer asynchronously.
-
- // Give up immediately if the promise has been cancelled or timed out.
- if (promise.isDone()) {
- timeoutChecker.get().removeListener(this);
+ public void completed(final org.glassfish.grizzly.Connection connection) {
+ timeoutChecker.get().removeListener(this);
+ if (!promise.tryHandleResult(connection)) {
+ // The connection has been either cancelled or it has timed out.
connection.close();
- return;
- }
-
- if (options.useStartTLS()) {
- // Chain StartTLS extended request.
- final StartTLSExtendedRequest startTLS =
- Requests.newStartTLSExtendedRequest(options.getSSLContext());
- startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites().toArray(
- new String[options.getEnabledCipherSuites().size()]));
- startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray(
- new String[options.getEnabledProtocols().size()]));
-
- connection.extendedRequestAsync(startTLS).onSuccess(new SuccessHandler<ExtendedResult>() {
- @Override
- public void handleResult(final ExtendedResult result) {
- onSuccess(connection);
- }
- }).onFailure(new FailureHandler<LdapException>() {
- @Override
- public void handleError(final LdapException error) {
- onFailure(connection, error);
- }
- });
- } else {
- // Install SSL/TLS layer.
- try {
- connection.startTLS(options.getSSLContext(), options.getEnabledProtocols(),
- options.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
- @Override
- public void completed(final SSLEngine result) {
- onSuccess(connection);
- }
-
- @Override
- public void failed(final Throwable throwable) {
- onFailure(connection, throwable);
- }
- });
- } catch (final IOException e) {
- onFailure(connection, e);
- }
}
}
@@ -160,51 +99,6 @@
// Adapt and forward.
timeoutChecker.get().removeListener(this);
promise.handleError(adaptConnectionException(throwable));
- releaseTransportAndTimeoutChecker();
- }
-
- @Override
- public void updated(final org.glassfish.grizzly.Connection result) {
- // Ignore this.
- }
-
- 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);
- timeoutChecker.get().addListener(ldapConnection);
- clientFilter.registerConnection(connection, ldapConnection);
- return ldapConnection;
- }
-
- private LdapException adaptConnectionException(Throwable t) {
- if (!(t instanceof LdapException) && t instanceof ExecutionException) {
- t = t.getCause() != null ? t.getCause() : t;
- }
-
- if (t instanceof LdapException) {
- return (LdapException) t;
- } else {
- return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), t);
- }
- }
-
- private void onFailure(final GrizzlyLDAPConnection connection, final Throwable t) {
- // Abort connection attempt due to error.
- timeoutChecker.get().removeListener(this);
- promise.handleError(adaptConnectionException(t));
- connection.close();
- }
-
- private void onSuccess(final GrizzlyLDAPConnection connection) {
- timeoutChecker.get().removeListener(this);
- if (!promise.tryHandleResult(connection)) {
- // The connection has been either cancelled or it has timed out.
- connection.close();
- }
}
@Override
@@ -228,25 +122,24 @@
private final LDAPClientFilter clientFilter;
private final FilterChain defaultFilterChain;
- private final LDAPOptions options;
- private final String host;
- private final int port;
-
- /**
- * Prevents the transport and timeoutChecker being released when there are
- * remaining references (this factory or any connections). It is initially
- * set to 1 because this factory has a reference.
- */
- private final AtomicInteger referenceCount = new AtomicInteger(1);
-
- /**
- * Indicates whether this factory has been closed or not.
- */
- private final AtomicBoolean isClosed = new AtomicBoolean();
-
private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
- private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER
- .acquire();
+ private final ReferenceCountedObject<TimeoutChecker>.Reference timeoutChecker = TIMEOUT_CHECKER.acquire();
+
+ @SuppressWarnings("rawtypes")
+ private final Function<org.glassfish.grizzly.Connection, AbstractLdapConnectionImpl<?>, LdapException>
+ convertToLDAPConnection =
+ new Function<org.glassfish.grizzly.Connection, AbstractLdapConnectionImpl<?>, LdapException>() {
+ @Override
+ public GrizzlyLDAPConnection apply(org.glassfish.grizzly.Connection connection) throws LdapException {
+ configureConnection(connection, options.isTCPNoDelay(), options.isKeepAlive(),
+ options.isReuseAddress(), options.getLinger(), logger);
+ final GrizzlyLDAPConnection ldapConnection =
+ new GrizzlyLDAPConnection(connection, GrizzlyLDAPConnectionFactory.this);
+ timeoutChecker.get().addListener(ldapConnection);
+ clientFilter.registerConnection(connection, ldapConnection);
+ return ldapConnection;
+ }
+ };
/**
* Creates a new LDAP connection factory based on Grizzly which can be used
@@ -262,6 +155,15 @@
*/
public GrizzlyLDAPConnectionFactory(final String host, final int port, final LDAPOptions options) {
this(host, port, options, null);
+
+ }
+
+ private LdapException adaptConnectionException(Throwable t) {
+ if (t instanceof LdapException) {
+ return (LdapException) t;
+ }
+ t = t instanceof ExecutionException && t.getCause() != null ? t.getCause() : t;
+ return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), t);
}
/**
@@ -281,88 +183,60 @@
* connections. If {@code null}, default transport will be used.
*/
public GrizzlyLDAPConnectionFactory(final String host, final int port, final LDAPOptions options,
- TCPNIOTransport transport) {
+ final TCPNIOTransport transport) {
+ super(host, port, options);
this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport);
- this.host = host;
- this.port = port;
- this.options = new LDAPOptions(options);
- this.clientFilter = new LDAPClientFilter(this.options.getDecodeOptions(), 0);
- this.defaultFilterChain =
- buildFilterChain(this.transport.get().getProcessor(), clientFilter);
- }
-
- @Override
- public void close() {
- if (isClosed.compareAndSet(false, true)) {
- releaseTransportAndTimeoutChecker();
- }
- }
-
- @Override
- public Connection getConnection() throws LdapException {
- try {
- return getConnectionAsync().getOrThrow();
- } catch (final InterruptedException e) {
- throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e);
- }
- }
-
- @Override
- public Promise<Connection, LdapException> getConnectionAsync() {
- acquireTransportAndTimeoutChecker(); // Protect resources.
- final SocketConnectorHandler connectorHandler =
- TCPNIOConnectorHandler.builder(transport.get()).processor(defaultFilterChain)
- .build();
- final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
- connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise));
- return promise;
- }
-
- @Override
- public InetSocketAddress getSocketAddress() {
- return new InetSocketAddress(host, port);
- }
-
- @Override
- public String getHostName() {
- return host;
- }
-
- @Override
- public int getPort() {
- return port;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "(" + host + ':' + port + ')';
+ this.clientFilter = new LDAPClientFilter(options.getDecodeOptions(), 0);
+ this.defaultFilterChain = buildFilterChain(this.transport.get().getProcessor(), clientFilter);
}
TimeoutChecker getTimeoutChecker() {
return timeoutChecker.get();
}
- LDAPOptions getLDAPOptions() {
- return options;
+ @Override
+ @SuppressWarnings("rawtypes")
+ protected Promise<AbstractLdapConnectionImpl<?>, LdapException> getConnectionAsync0() {
+ final SocketConnectorHandler connectorHandler = TCPNIOConnectorHandler.builder(transport.get())
+ .processor(defaultFilterChain).build();
+ final PromiseImpl<org.glassfish.grizzly.Connection, LdapException> promise = PromiseImpl.create();
+ connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise));
+
+ return promise.then(convertToLDAPConnection);
}
- void releaseTransportAndTimeoutChecker() {
- if (referenceCount.decrementAndGet() == 0) {
- transport.release();
- timeoutChecker.release();
+ @Override
+ protected Promise<Void, LdapException> installSecureLayer(final Connection connection) {
+ final PromiseImpl<Void, LdapException> sslHandshakePromise = PromiseImpl.create();
+ try {
+ final GrizzlyLDAPConnection grizzlyConnection = (GrizzlyLDAPConnection) connection;
+ grizzlyConnection.startTLS(options.getSSLContext(), options.getEnabledProtocols(),
+ options.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
+ @Override
+ public void completed(final SSLEngine result) {
+ if (!sslHandshakePromise.tryHandleResult(null)) {
+ // The connection has been either cancelled or
+ // it has timed out.
+ connection.close();
+ }
+ }
+
+ @Override
+ public void failed(final Throwable throwable) {
+ sslHandshakePromise.handleError(adaptConnectionException(throwable));
+ }
+ });
+ } catch (final IOException e) {
+ sslHandshakePromise.handleError(adaptConnectionException(e));
}
+
+ return sslHandshakePromise;
}
- private void acquireTransportAndTimeoutChecker() {
- /*
- * If the factory is not closed then we need to prevent the resources
- * (transport, timeout checker) from being released while the connection
- * attempt is in progress.
- */
- referenceCount.incrementAndGet();
- if (isClosed.get()) {
- releaseTransportAndTimeoutChecker();
- throw new IllegalStateException("Attempted to get a connection after factory close");
- }
+ @Override
+ protected void releaseImplResources() {
+ transport.release();
+ timeoutChecker.release();
}
+
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
index bf98a32..251ec93 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -79,7 +79,7 @@
*/
final class LDAPClientFilter extends LDAPBaseFilter {
private static final Attribute<GrizzlyLDAPConnection> LDAP_CONNECTION_ATTR =
- Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPClientConnection");
+ Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("GrizzlyLdapClientConnection");
private static final Attribute<ClientResponseHandler> RESPONSE_HANDLER_ATTR =
Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ClientResponseHandler");
@@ -109,6 +109,7 @@
*
* @return the reader to read incoming LDAP messages
*/
+ @Override
public LDAPReader<ASN1BufferReader> getReader() {
return this.reader;
}
@@ -119,7 +120,7 @@
IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof AddRequest) {
pendingRequest.setResultOrError(result);
@@ -135,7 +136,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest instanceof BindResultLdapPromiseImpl) {
final BindResultLdapPromiseImpl promise = (BindResultLdapPromiseImpl) pendingRequest;
@@ -143,8 +144,7 @@
try {
if (!bindClient.evaluateResult(result)) {
- // The server is expecting a multi stage
- // bind response.
+ // The server is expecting a multi stage bind response.
final int msgID = ldapConnection.continuePendingBindRequest(promise);
LDAPWriter<ASN1BufferWriter> ldapWriter = GrizzlyUtils.getWriter();
@@ -202,7 +202,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof CompareRequest) {
((ResultLdapPromiseImpl<CompareRequest, CompareResult>) pendingRequest)
@@ -220,7 +220,7 @@
IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof DeleteRequest) {
((ResultLdapPromiseImpl<DeleteRequest, Result>) pendingRequest).setResultOrError(result);
@@ -242,12 +242,12 @@
// Treat this as a connection error.
final Result errorResult = newResult(result.getResultCode()).setDiagnosticMessage(
result.getDiagnosticMessage());
- ldapConnection.close(null, true, errorResult);
+ ldapConnection.connectionErrorOccurred(true, errorResult);
} else {
ldapConnection.handleUnsolicitedNotification(result);
}
} else {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof ExtendedRequest) {
final ExtendedResultLdapPromiseImpl<?> extendedPromise =
@@ -274,7 +274,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
if (pendingRequest != null) {
pendingRequest.handleIntermediateResponse(response);
}
@@ -287,7 +287,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof ModifyDNRequest) {
((ResultLdapPromiseImpl<ModifyDNRequest, Result>) pendingRequest).setResultOrError(result);
@@ -303,7 +303,7 @@
public void modifyResult(final int messageID, final Result result) throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof ModifyRequest) {
((ResultLdapPromiseImpl<ModifyRequest, Result>) pendingRequest).setResultOrError(result);
@@ -320,7 +320,7 @@
IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.removePendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest.getRequest() instanceof SearchRequest) {
((ResultLdapPromiseImpl<SearchRequest, Result>) pendingRequest).setResultOrError(result);
@@ -336,7 +336,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest instanceof SearchResultLdapPromiseImpl) {
((SearchResultLdapPromiseImpl) pendingRequest).handleEntry(entry);
@@ -352,7 +352,7 @@
throws DecodeException, IOException {
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(context.getConnection());
if (ldapConnection != null) {
- final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingRequest(messageID);
+ final ResultLdapPromiseImpl<?, ?> pendingRequest = ldapConnection.getPendingResult(messageID);
if (pendingRequest != null) {
if (pendingRequest instanceof SearchResultLdapPromiseImpl) {
((SearchResultLdapPromiseImpl) pendingRequest).handleReference(reference);
@@ -367,15 +367,14 @@
private <R extends ExtendedResult> void handleExtendedResult0(
final GrizzlyLDAPConnection conn, final ExtendedResultLdapPromiseImpl<R> promise,
final ExtendedResult result) throws DecodeException {
- final R decodedResponse = promise.decodeResult(result, conn.getLDAPOptions().getDecodeOptions());
+ final R decodedResponse = promise.decodeResult(result, conn.getLdapOptions().getDecodeOptions());
if (result.getResultCode() == ResultCode.SUCCESS
&& promise.getRequest() instanceof StartTLSExtendedRequest) {
try {
final StartTLSExtendedRequest request = (StartTLSExtendedRequest) promise.getRequest();
conn.startTLS(request.getSSLContext(), request.getEnabledProtocols(),
- request.getEnabledCipherSuites(),
- new EmptyCompletionHandler<SSLEngine>() {
+ request.getEnabledCipherSuites(), new EmptyCompletionHandler<SSLEngine>() {
@Override
public void completed(final SSLEngine result) {
conn.setBindOrStartTLSInProgress(false);
@@ -384,19 +383,19 @@
@Override
public void failed(final Throwable throwable) {
- final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR)
- .setCause(throwable).setDiagnosticMessage("SSL handshake failed");
+ final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(throwable)
+ .setDiagnosticMessage("SSL handshake failed");
conn.setBindOrStartTLSInProgress(false);
- conn.close(null, false, errorResult);
+ conn.connectionErrorOccurred(false, errorResult);
promise.adaptErrorResult(errorResult);
}
});
return;
} catch (final IOException e) {
- final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(e)
- .setDiagnosticMessage(e.getMessage());
+ final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR).setCause(e).setDiagnosticMessage(
+ e.getMessage());
promise.adaptErrorResult(errorResult);
- conn.close(null, false, errorResult);
+ conn.connectionErrorOccurred(false, errorResult);
return;
}
}
@@ -430,16 +429,11 @@
}
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(connection);
- Result errorResult;
- if (error instanceof EOFException) {
- // FIXME: Is this the best result code?
- errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN).setCause(error);
- } else {
- // FIXME: what other sort of IOExceptions can be thrown?
- // FIXME: Is this the best result code?
- errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setCause(error);
- }
- ldapConnection.close(null, false, errorResult);
+ // FIXME: what other sort of IOExceptions can be thrown?
+ // FIXME: Are they the best result codes?
+ Result errorResult = newResult(error instanceof EOFException ? ResultCode.CLIENT_SIDE_SERVER_DOWN
+ : ResultCode.CLIENT_SIDE_LOCAL_ERROR);
+ ldapConnection.connectionErrorOccurred(false, errorResult.setCause(error));
}
@Override
@@ -447,8 +441,7 @@
final Connection<?> connection = ctx.getConnection();
final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.remove(connection);
if (ldapConnection != null) {
- final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN);
- ldapConnection.close(null, false, errorResult);
+ ldapConnection.connectionErrorOccurred(false, newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN));
}
return ctx.getInvokeAction();
}
@@ -459,7 +452,7 @@
final Result errorResult =
Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e)
.setDiagnosticMessage(e.getMessage());
- ldapConnection.close(null, false, errorResult);
+ ldapConnection.connectionErrorOccurred(false, errorResult);
}
/**
@@ -497,8 +490,7 @@
* @param ldapConnection
* LDAP connection
*/
- void registerConnection(final Connection<?> connection,
- final GrizzlyLDAPConnection ldapConnection) {
+ void registerConnection(final Connection<?> connection, final GrizzlyLDAPConnection ldapConnection) {
LDAP_CONNECTION_ATTR.set(connection, ldapConnection);
}
}
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 c0b3cbb..440500a 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
@@ -23,13 +23,5 @@
#
# 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
-LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT=The LDAP connection has \
- failed because no bind or StartTLS response was received from the server \
- within the %d ms timeout
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
index f5351cd..5b268e8 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -45,15 +45,15 @@
import org.forgerock.opendj.ldap.ConnectionPool;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
-import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LDAPServer;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.MockConnectionEventListener;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
@@ -85,10 +85,14 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import static java.util.concurrent.TimeUnit.*;
+
import static org.fest.assertions.Assertions.*;
import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+import static org.forgerock.opendj.ldap.responses.Responses.*;
import static org.forgerock.opendj.ldap.spi.LdapPromises.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
@@ -103,6 +107,12 @@
private static final long TEST_TIMEOUT = 30L;
private static final long TEST_TIMEOUT_MS = TEST_TIMEOUT * 1000L;
+ /** The heart-beat timeout after which a connection will be marked as failed. */
+ private static final long OPERATION_TIMEOUT_SEC = 3;
+
+ /** The time unit for the interval between keepalive pings. */
+ private static final long HEART_BEAT_INTERVAL_SEC = 10;
+
/**
* Ensures that the LDAP Server is running.
*
@@ -145,33 +155,30 @@
"uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "(objectclass=*)", "cn");
InetSocketAddress serverAddress = getServerSocketAddress();
- factories[0][0] =
- Connections.newHeartBeatConnectionFactory(new LDAPConnectionFactory(
- serverAddress.getHostName(), serverAddress.getPort()),
- 1000, 500, TimeUnit.MILLISECONDS, request);
+ final LDAPOptions commonsOptions = new LDAPOptions().setTimeout(OPERATION_TIMEOUT_SEC, SECONDS);
+ factories[0][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
+ new LDAPOptions().setHeartBeatSearchRequest(request).setTimeout(500, MILLISECONDS)
+ .setHeartBeatInterval(1000, MILLISECONDS));
// InternalConnectionFactory
- factories[1][0] = Connections.newInternalConnectionFactory(LDAPServer.getInstance(), null);
+ factories[1][0] = newInternalConnectionFactory(LDAPServer.getInstance(), null);
// AuthenticatedConnectionFactory
- factories[2][0] =
- Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
- serverAddress.getHostName(), serverAddress.getPort()),
- Requests.newSimpleBindRequest("", new char[0]));
+ factories[2][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
+ new LDAPOptions(commonsOptions).setBindRequest(newSimpleBindRequest("", new char[0])));
// AuthenticatedConnectionFactory with multi-stage SASL
- factories[3][0] =
- Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
- serverAddress.getHostName(), serverAddress.getPort()),
- Requests.newCRAMMD5SASLBindRequest("id:user", "password".toCharArray()));
+ factories[3][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
+ new LDAPOptions(commonsOptions).setBindRequest(
+ newCRAMMD5SASLBindRequest("id:user", "password".toCharArray())));
// LDAPConnectionFactory with default options
- factories[4][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort());
+ factories[4][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort());
// LDAPConnectionFactory with startTLS
SSLContext sslContext =
new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext();
- LDAPOptions options = new LDAPOptions().setSSLContext(sslContext).setUseStartTLS(true)
+ LDAPOptions options = new LDAPOptions(commonsOptions).setSSLContext(sslContext).setUseStartTLS(true)
.addEnabledCipherSuite(
new String[] { "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
@@ -179,34 +186,29 @@
"SSL_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_WITH_RC4_128_MD5",
"TLS_DH_anon_WITH_AES_128_CBC_SHA",
"TLS_DH_anon_WITH_AES_256_CBC_SHA" });
- factories[5][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(), options);
+ factories[5][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(), options);
// startTLS + SASL confidentiality
// Use IP address here so that DIGEST-MD5 host verification works if
// local host name is not localhost (e.g. on some machines it might be
// localhost.localdomain).
// FIXME: enable QOP once OPENDJ-514 is fixed.
- factories[6][0] =
- Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
- serverAddress.getHostName(), serverAddress.getPort(), options),
- Requests.newDigestMD5SASLBindRequest(
- "id:user", "password".toCharArray()).setCipher(
- DigestMD5SASLBindRequest.CIPHER_LOW));
+ factories[6][0] = newLDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort(),
+ options.setBindRequest(newDigestMD5SASLBindRequest("id:user", "password".toCharArray())
+ .setCipher(DigestMD5SASLBindRequest.CIPHER_LOW)));
// Connection pool and load balancing tests.
InetSocketAddress offlineSocketAddress1 = findFreeSocketAddress();
- ConnectionFactory offlineServer1 =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(offlineSocketAddress1.getHostName(),
+ ConnectionFactory offlineServer1 = newNamedConnectionFactory(
+ newLDAPConnectionFactory(offlineSocketAddress1.getHostName(),
offlineSocketAddress1.getPort()), "offline1");
+
InetSocketAddress offlineSocketAddress2 = findFreeSocketAddress();
- ConnectionFactory offlineServer2 =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(offlineSocketAddress2.getHostName(),
+ ConnectionFactory offlineServer2 = newNamedConnectionFactory(
+ newLDAPConnectionFactory(offlineSocketAddress2.getHostName(),
offlineSocketAddress2.getPort()), "offline2");
- ConnectionFactory onlineServer =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(serverAddress.getHostName(),
+ ConnectionFactory onlineServer = newNamedConnectionFactory(
+ newLDAPConnectionFactory(serverAddress.getHostName(),
serverAddress.getPort()), "online");
// Connection pools.
@@ -327,7 +329,7 @@
// Create a connection factory: this should always use the default
// schema, even if it is updated.
InetSocketAddress socketAddress = getServerSocketAddress();
- final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
+ final ConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
socketAddress.getPort());
final Schema defaultSchema = Schema.getDefaultSchema();
@@ -520,17 +522,13 @@
@Test(dataProvider = "closeNotifyConfig")
public void testCloseNotify(final CloseNotify config) throws Exception {
final CountDownLatch connectLatch = new CountDownLatch(1);
- final AtomicReference<LDAPClientContext> contextHolder =
- new AtomicReference<LDAPClientContext>();
+ final AtomicReference<LDAPClientContext> contextHolder = new AtomicReference<LDAPClientContext>();
- final ServerConnectionFactory<LDAPClientContext, Integer> mockServer =
- mock(ServerConnectionFactory.class);
+ final ServerConnectionFactory<LDAPClientContext, Integer> mockServer = mock(ServerConnectionFactory.class);
when(mockServer.handleAccept(any(LDAPClientContext.class))).thenAnswer(
new Answer<ServerConnection<Integer>>() {
-
@Override
- public ServerConnection<Integer> answer(InvocationOnMock invocation)
- throws Throwable {
+ public ServerConnection<Integer> answer(InvocationOnMock invocation) throws Throwable {
// Allow the context to be accessed from outside the mock.
contextHolder.set((LDAPClientContext) invocation.getArguments()[0]);
connectLatch.countDown(); /* is this needed? */
@@ -543,10 +541,8 @@
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
ResultHandler<? super BindResult> resultHandler =
- (ResultHandler<? super BindResult>) invocation
- .getArguments()[4];
- resultHandler.handleResult(Responses
- .newBindResult(ResultCode.SUCCESS));
+ (ResultHandler<? super BindResult>) invocation.getArguments()[4];
+ resultHandler.handleResult(newBindResult(ResultCode.SUCCESS));
return null;
}
}).when(mockConnection).handleBind(anyInt(), anyInt(),
@@ -557,20 +553,19 @@
}
});
- LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
+ LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mockServer);
try {
- LDAPConnectionFactory clientFactory =
- new LDAPConnectionFactory(listener.getHostName(), listener.getPort());
- final Connection client = clientFactory.getConnection();
- connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
+ LDAPConnectionFactory clientFactory = newLDAPConnectionFactory(listener.getHostName(), listener.getPort());
+ final Connection clientConnection = clientFactory.getConnection();
+ connectLatch.await(TEST_TIMEOUT, SECONDS);
MockConnectionEventListener mockListener = null;
try {
if (config.useEventListener) {
mockListener = new MockConnectionEventListener();
- client.addConnectionEventListener(mockListener);
+ clientConnection.addConnectionEventListener(mockListener);
}
if (config.doBindFirst) {
- client.bind("cn=test", "password".toCharArray());
+ clientConnection.bind("cn=test", "password".toCharArray());
}
if (!config.closeOnAccept) {
// Disconnect using client context.
@@ -587,30 +582,29 @@
// Block until remote close is signalled.
if (mockListener != null) {
// Block using listener.
- mockListener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
+ mockListener.awaitError(TEST_TIMEOUT, SECONDS);
assertThat(mockListener.getInvocationCount()).isEqualTo(1);
- assertThat(mockListener.isDisconnectNotification()).isEqualTo(
- config.sendDisconnectNotification);
+ assertThat(mockListener.isDisconnectNotification()).isEqualTo(config.sendDisconnectNotification);
assertThat(mockListener.getError()).isNotNull();
} else {
// Block by spinning on isValid.
waitForCondition(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
- return !client.isValid();
+ return !clientConnection.isValid();
}
});
}
- assertThat(client.isValid()).isFalse();
- assertThat(client.isClosed()).isFalse();
+ assertThat(clientConnection.isValid()).isFalse();
+ assertThat(clientConnection.isClosed()).isFalse();
} finally {
- client.close();
+ clientConnection.close();
}
// Check state after remote close and local close.
- assertThat(client.isValid()).isFalse();
- assertThat(client.isClosed()).isTrue();
+ assertThat(clientConnection.isValid()).isFalse();
+ assertThat(clientConnection.isClosed()).isTrue();
if (mockListener != null) {
- mockListener.awaitClose(TEST_TIMEOUT, TimeUnit.SECONDS);
+ mockListener.awaitClose(TEST_TIMEOUT, SECONDS);
assertThat(mockListener.getInvocationCount()).isEqualTo(2);
}
} finally {
@@ -640,11 +634,9 @@
}
});
- LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer);
+ LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mockServer);
try {
- LDAPConnectionFactory clientFactory =
- new LDAPConnectionFactory(listener.getHostName(),
- listener.getPort());
+ LDAPConnectionFactory clientFactory = newLDAPConnectionFactory(listener.getHostName(), listener.getPort());
final Connection client = clientFactory.getConnection();
connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
try {
@@ -678,11 +670,12 @@
@Test(description = "Test for OPENDJ-1121: Closing a connection after "
+ "closing the connection factory causes NPE")
public void testFactoryCloseBeforeConnectionClose() throws Exception {
+ LDAPOptions heartBeatOptions = new LDAPOptions().setHeartBeatInterval(HEART_BEAT_INTERVAL_SEC, SECONDS)
+ .setTimeout(OPERATION_TIMEOUT_SEC, SECONDS);
InetSocketAddress socketAddress = getServerSocketAddress();
- final ConnectionFactory factory =
- newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(newFixedConnectionPool(
- newHeartBeatConnectionFactory(new LDAPConnectionFactory(
- socketAddress.getHostName(), socketAddress.getPort())), 2))));
+ final ConnectionFactory factory = newLoadBalancer(new FailoverLoadBalancingAlgorithm(
+ Arrays.asList(newFixedConnectionPool(newLDAPConnectionFactory(
+ socketAddress.getHostName(), socketAddress.getPort(), heartBeatOptions), 2))));
Connection conn = null;
try {
conn = factory.getConnection();
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 8886b23..fc8b88b 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
@@ -37,13 +37,13 @@
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
import org.forgerock.opendj.ldap.MockConnectionEventListener;
import org.forgerock.opendj.ldap.ProviderNotFoundException;
import org.forgerock.opendj.ldap.ResultCode;
@@ -71,6 +71,7 @@
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.mockito.Matchers.*;
@@ -107,7 +108,7 @@
new AtomicReference<LDAPClientContext>();
private final LDAPListener server = createServer();
private final InetSocketAddress socketAddress = server.getSocketAddress();
- private final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
+ private final ConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
socketAddress.getPort(), new LDAPOptions().setTimeout(1, TimeUnit.MILLISECONDS));
private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10);
private volatile ServerConnection<Integer> serverConnection;
@@ -122,7 +123,7 @@
@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,
+ final ConnectionFactory factory = newLDAPConnectionFactory("10.20.30.40", 1389,
new LDAPOptions().setConnectTimeout(1, TimeUnit.MILLISECONDS));
try {
for (int i = 0; i < ITERATIONS; i++) {
@@ -300,7 +301,7 @@
public void testCreateLDAPConnectionFactory() throws Exception {
// test no exception is thrown, which means transport provider is correctly loaded
InetSocketAddress socketAddress = findFreeSocketAddress();
- LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), socketAddress.getPort());
+ LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(), socketAddress.getPort());
factory.close();
}
@@ -309,7 +310,7 @@
public void testCreateLDAPConnectionFactoryFailureProviderNotFound() throws Exception {
LDAPOptions options = new LDAPOptions().setTransportProvider("unknown");
InetSocketAddress socketAddress = findFreeSocketAddress();
- LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
+ LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
socketAddress.getPort(), options);
factory.close();
}
@@ -319,7 +320,7 @@
// test no exception is thrown, which means transport provider is correctly loaded
LDAPOptions options = new LDAPOptions().setProviderClassLoader(Thread.currentThread().getContextClassLoader());
InetSocketAddress socketAddress = findFreeSocketAddress();
- LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(),
+ LDAPConnectionFactory factory = newLDAPConnectionFactory(socketAddress.getHostName(),
socketAddress.getPort(), options);
factory.close();
}
@@ -352,7 +353,7 @@
private LDAPListener createServer() {
try {
- return new LDAPListener(findFreeSocketAddress(),
+ return newLDAPListener(findFreeSocketAddress(),
new ServerConnectionFactory<LDAPClientContext, Integer>() {
@Override
public ServerConnection<Integer> handleAccept(
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
index 9dab53b..ec61b86 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionTestCase.java
@@ -26,19 +26,13 @@
package org.forgerock.opendj.grizzly;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
-import org.forgerock.opendj.ldap.Connections;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.RequestHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SdkTestCase;
@@ -53,6 +47,10 @@
import org.mockito.ArgumentCaptor;
import org.testng.annotations.Test;
+import static org.fest.assertions.Assertions.*;
+import static org.forgerock.opendj.ldap.Connections.*;
+import static org.mockito.Mockito.*;
+
/**
* Tests LDAP connection implementation class.
*/
@@ -84,15 +82,13 @@
* and leave the client waiting forever for a response.
*/
@SuppressWarnings("unchecked")
- LDAPListener listener =
- new LDAPListener(address, Connections
- .newServerConnectionFactory(mock(RequestHandler.class)));
+ LDAPListener listener = newLDAPListener(address, newServerConnectionFactory(mock(RequestHandler.class)));
/*
* Use a very long time out in order to prevent the timeout thread from
* triggering the timeout.
*/
- LDAPConnectionFactory factory = new LDAPConnectionFactory(address.getHostName(),
+ LDAPConnectionFactory factory = newLDAPConnectionFactory(address.getHostName(),
address.getPort(), new LDAPOptions().setTimeout(100, TimeUnit.SECONDS));
GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnection();
try {
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
index 4bc9740..e70d05d 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -74,6 +74,7 @@
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.ldap.Connections.*;
import static org.forgerock.opendj.ldap.LdapException.*;
import static org.forgerock.opendj.ldap.TestCaseUtils.*;
import static org.mockito.Mockito.*;
@@ -230,7 +231,7 @@
public void testCreateLDAPListener() throws Exception {
// test no exception is thrown, which means transport provider is
// correctly loaded
- LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class));
+ LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class));
listener.close();
}
@@ -244,7 +245,7 @@
// correctly loaded
LDAPListenerOptions options = new LDAPListenerOptions().
setProviderClassLoader(Thread.currentThread().getContextClassLoader());
- LDAPListener listener = new LDAPListener(findFreeSocketAddress(),
+ LDAPListener listener = newLDAPListener(findFreeSocketAddress(),
mock(ServerConnectionFactory.class), options);
listener.close();
}
@@ -257,7 +258,7 @@
expectedExceptionsMessageRegExp = "^The requested provider 'unknown' .*")
public void testCreateLDAPListenerFailureProviderNotFound() throws Exception {
LDAPListenerOptions options = new LDAPListenerOptions().setTransportProvider("unknown");
- LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options);
+ LDAPListener listener = newLDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options);
listener.close();
}
@@ -272,13 +273,11 @@
final MockServerConnection serverConnection = new MockServerConnection();
final MockServerConnectionFactory serverConnectionFactory =
new MockServerConnectionFactory(serverConnection);
- final LDAPListener listener =
- new LDAPListener(new InetSocketAddress(0), serverConnectionFactory);
+ final LDAPListener listener = newLDAPListener(new InetSocketAddress(0), serverConnectionFactory);
try {
// Connect and close.
- final Connection connection =
- new LDAPConnectionFactory(listener.getHostName(),
- listener.getPort()).getConnection();
+ final Connection connection = newLDAPConnectionFactory(listener.getHostName(), listener.getPort())
+ .getConnection();
assertThat(serverConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
assertThat(serverConnection.isClosed.getCount()).isEqualTo(1);
connection.close();
@@ -303,22 +302,22 @@
final MockServerConnectionFactory onlineServerConnectionFactory =
new MockServerConnectionFactory(onlineServerConnection);
final LDAPListener onlineServerListener =
- new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+ newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
try {
// Connection pool and load balancing tests.
InetSocketAddress offlineAddress1 = findFreeSocketAddress();
final ConnectionFactory offlineServer1 =
- Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
+ newNamedConnectionFactory(newLDAPConnectionFactory(
offlineAddress1.getHostName(),
offlineAddress1.getPort()), "offline1");
InetSocketAddress offlineAddress2 = findFreeSocketAddress();
final ConnectionFactory offlineServer2 =
- Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
+ newNamedConnectionFactory(newLDAPConnectionFactory(
offlineAddress2.getHostName(),
offlineAddress2.getPort()), "offline2");
final ConnectionFactory onlineServer =
- Connections.newNamedConnectionFactory(new LDAPConnectionFactory(
+ newNamedConnectionFactory(newLDAPConnectionFactory(
onlineServerListener.getHostName(),
onlineServerListener.getPort()), "online");
@@ -346,13 +345,11 @@
};
- final LDAPListener proxyListener =
- new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+ final LDAPListener proxyListener = newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
try {
// Connect and close.
- final Connection connection =
- new LDAPConnectionFactory(proxyListener.getHostName(),
- proxyListener.getPort()).getConnection();
+ final Connection connection = newLDAPConnectionFactory(
+ proxyListener.getHostName(), proxyListener.getPort()).getConnection();
assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -383,24 +380,18 @@
final MockServerConnectionFactory onlineServerConnectionFactory =
new MockServerConnectionFactory(onlineServerConnection);
final LDAPListener onlineServerListener =
- new LDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory);
+ newLDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory);
try {
// Connection pool and load balancing tests.
InetSocketAddress offlineAddress1 = findFreeSocketAddress();
- final ConnectionFactory offlineServer1 =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(offlineAddress1.getHostName(),
- offlineAddress1.getPort()), "offline1");
+ final ConnectionFactory offlineServer1 = newNamedConnectionFactory(newLDAPConnectionFactory(
+ offlineAddress1.getHostName(), offlineAddress1.getPort()), "offline1");
InetSocketAddress offlineAddress2 = findFreeSocketAddress();
- final ConnectionFactory offlineServer2 =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(offlineAddress2.getHostName(),
- offlineAddress2.getPort()), "offline2");
- final ConnectionFactory onlineServer =
- Connections.newNamedConnectionFactory(
- new LDAPConnectionFactory(onlineServerListener.getHostName(),
- onlineServerListener.getPort()), "online");
+ final ConnectionFactory offlineServer2 = newNamedConnectionFactory(newLDAPConnectionFactory(
+ offlineAddress2.getHostName(), offlineAddress2.getPort()), "offline2");
+ final ConnectionFactory onlineServer = newNamedConnectionFactory(newLDAPConnectionFactory(
+ onlineServerListener.getHostName(), onlineServerListener.getPort()), "online");
// Round robin.
final ConnectionFactory loadBalancer =
@@ -436,12 +427,11 @@
new MockServerConnectionFactory(proxyServerConnection);
final LDAPListener proxyListener =
- new LDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory);
+ newLDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory);
try {
// Connect, bind, and close.
final Connection connection =
- new LDAPConnectionFactory(proxyListener.getHostName(),
- proxyListener.getPort()).getConnection();
+ newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
try {
connection.bind("cn=test", "password".toCharArray());
@@ -476,7 +466,7 @@
final MockServerConnectionFactory onlineServerConnectionFactory =
new MockServerConnectionFactory(onlineServerConnection);
final LDAPListener onlineServerListener =
- new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+ newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
try {
final MockServerConnection proxyServerConnection = new MockServerConnection();
@@ -489,8 +479,7 @@
// First attempt offline server.
InetSocketAddress offlineAddress = findFreeSocketAddress();
LDAPConnectionFactory lcf =
- new LDAPConnectionFactory(offlineAddress.getHostName(),
- offlineAddress.getPort());
+ newLDAPConnectionFactory(offlineAddress.getHostName(), offlineAddress.getPort());
try {
// This is expected to fail.
lcf.getConnection().close();
@@ -499,8 +488,7 @@
} catch (final ConnectionException ce) {
// This is expected - so go to online server.
try {
- lcf =
- new LDAPConnectionFactory(
+ lcf = newLDAPConnectionFactory(
onlineServerListener.getHostName(),
onlineServerListener.getPort());
lcf.getConnection().close();
@@ -523,13 +511,11 @@
}
};
- final LDAPListener proxyListener =
- new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+ final LDAPListener proxyListener = newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
try {
// Connect and close.
final Connection connection =
- new LDAPConnectionFactory(proxyListener.getHostName(),
- proxyListener.getPort()).getConnection();
+ newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull();
@@ -559,7 +545,7 @@
final MockServerConnectionFactory onlineServerConnectionFactory =
new MockServerConnectionFactory(onlineServerConnection);
final LDAPListener onlineServerListener =
- new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
+ newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory);
try {
final MockServerConnection proxyServerConnection = new MockServerConnection() {
@@ -573,8 +559,8 @@
throws UnsupportedOperationException {
// First attempt offline server.
InetSocketAddress offlineAddress = findFreeSocketAddress();
- LDAPConnectionFactory lcf = new LDAPConnectionFactory(offlineAddress.getHostName(),
- offlineAddress.getPort());
+ LDAPConnectionFactory lcf =
+ newLDAPConnectionFactory(offlineAddress.getHostName(), offlineAddress.getPort());
try {
// This is expected to fail.
lcf.getConnection().close();
@@ -584,7 +570,7 @@
} catch (final ConnectionException ce) {
// This is expected - so go to online server.
try {
- lcf = new LDAPConnectionFactory(onlineServerListener.getHostName(),
+ lcf = newLDAPConnectionFactory(onlineServerListener.getHostName(),
onlineServerListener.getPort());
lcf.getConnection().close();
resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
@@ -606,12 +592,11 @@
final MockServerConnectionFactory proxyServerConnectionFactory =
new MockServerConnectionFactory(proxyServerConnection);
final LDAPListener proxyListener =
- new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
+ newLDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory);
try {
// Connect, bind, and close.
final Connection connection =
- new LDAPConnectionFactory(proxyListener.getHostName(),
- proxyListener.getPort()).getConnection();
+ newLDAPConnectionFactory(proxyListener.getHostName(), proxyListener.getPort()).getConnection();
try {
connection.bind("cn=test", "password".toCharArray());
@@ -645,12 +630,12 @@
final MockServerConnectionFactory factory =
new MockServerConnectionFactory(serverConnection);
final LDAPListenerOptions options = new LDAPListenerOptions().setMaxRequestSize(2048);
- final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options);
+ final LDAPListener listener = newLDAPListener(findFreeSocketAddress(), factory, options);
Connection connection = null;
try {
- connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).
- getConnection();
+ connection =
+ newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
// Small request
connection.bind("cn=test", "password".toCharArray());
@@ -702,12 +687,12 @@
final MockServerConnection serverConnection = new MockServerConnection();
final MockServerConnectionFactory factory =
new MockServerConnectionFactory(serverConnection);
- final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory);
+ final LDAPListener listener = newLDAPListener(findFreeSocketAddress(), factory);
final Connection connection;
try {
// Connect and bind.
- connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
+ connection = newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
try {
connection.bind("cn=test", "password".toCharArray());
} catch (final LdapException e) {
@@ -722,8 +707,7 @@
try {
// Connect and bind.
final Connection failedConnection =
- new LDAPConnectionFactory(listener.getHostName(),
- listener.getPort()).getConnection();
+ newLDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection();
failedConnection.close();
connection.close();
fail("Connection attempt to closed listener succeeded unexpectedly");
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
index ce72211..13d941e 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Controls.java
@@ -34,9 +34,9 @@
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RootDSE;
@@ -83,6 +83,8 @@
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* This command-line client demonstrates use of LDAP controls. The client takes
* as arguments the host and port for the directory server, and expects to find
@@ -108,10 +110,10 @@
System.err.println("For example: localhost 1389");
System.exit(1);
}
- final String host = args[0];
+ final String hostName = args[0];
final int port = Integer.parseInt(args[1]);
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
connection = factory.getConnection();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
index 6f36b3a..a0ace4e 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ExtendedOperations.java
@@ -29,8 +29,8 @@
import java.util.Collection;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.RootDSE;
import org.forgerock.opendj.ldap.requests.PasswordModifyExtendedRequest;
import org.forgerock.opendj.ldap.requests.Requests;
@@ -39,6 +39,8 @@
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.WhoAmIExtendedResult;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* This command-line client demonstrates use of LDAP extended operations. The
* client takes as arguments the host and port for the directory server, and
@@ -64,10 +66,10 @@
System.err.println("For example: localhost 1389");
System.exit(1);
}
- final String host = args[0];
+ final String hostName = args[0];
final int port = Integer.parseInt(args[1]);
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
index 215e805..49df6c2 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetADChangeNotifications.java
@@ -30,8 +30,8 @@
import java.io.IOException;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.controls.GenericControl;
@@ -42,6 +42,8 @@
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which searches Microsoft Active Directory
* synchronously, using {@link GenericControl} to pass the <a
@@ -93,8 +95,7 @@
final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
// Connect and bind to the server.
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
index a47400a..c45578b 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/GetInfo.java
@@ -28,19 +28,21 @@
import java.io.IOException;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* Demonstrates accessing server information about capabilities and schema.
*/
public final class GetInfo {
/** Connection information. */
- private static String host;
+ private static String hostName;
private static int port;
/** The kind of server information to request (all, controls, extops). */
private static String infoType;
@@ -62,7 +64,7 @@
*/
private static void connect() {
// --- JCite ---
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
@@ -85,7 +87,7 @@
attributeList); // Return these requested attributes.
final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
- writer.writeComment("Root DSE for LDAP server at " + host + ":" + port);
+ writer.writeComment("Root DSE for LDAP server at " + hostName + ":" + port);
if (entry != null) {
writer.writeEntry(entry);
}
@@ -122,7 +124,7 @@
giveUp();
}
- host = args[0];
+ hostName = args[0];
port = Integer.parseInt(args[1]);
infoType = args[2];
final String infoTypeLc = infoType.toLowerCase();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
index 35adca5..8720559 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Modify.java
@@ -33,13 +33,15 @@
import java.io.InputStream;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldif.ChangeRecord;
import org.forgerock.opendj.ldif.ConnectionChangeRecordWriter;
import org.forgerock.opendj.ldif.LDIFChangeRecordReader;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which applies update operations to a Directory
* Server. The update operations will be read from an LDIF file, or stdin if no
@@ -89,7 +91,7 @@
final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldif);
// Connect and bind to the server.
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
index d723b84..04c3b0e 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ParseAttributes.java
@@ -34,8 +34,8 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
@@ -45,6 +45,8 @@
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* This command-line client demonstrates parsing entry attribute values to
* objects. The client takes as arguments the host and port for the directory
@@ -66,11 +68,11 @@
System.err.println("For example: localhost 1389");
System.exit(1);
}
- final String host = args[0];
+ final String hostName = args[0];
final int port = Integer.parseInt(args[1]);
// --- JCite ---
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
connection = factory.getConnection();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
index 6e7a79d..1b68bcd 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
@@ -26,11 +26,16 @@
package org.forgerock.opendj.examples;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+
+import javax.net.ssl.SSLContext;
+
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SSLContextBuilder;
@@ -38,9 +43,7 @@
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
-import javax.net.ssl.SSLContext;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
+import static org.forgerock.opendj.ldap.Connections.*;
/**
* This command-line client demonstrates how to reset a user password in
@@ -84,8 +87,7 @@
Connection connection = null;
try {
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(host, port, getTrustAllOptions());
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(host, port, getTrustAllOptions());
connection = factory.getConnection();
connection.bind(bindDN, bindPassword.toCharArray());
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
index f2be0b8..3d39544 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Proxy.java
@@ -30,20 +30,24 @@
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPClientContext;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.RequestContext;
import org.forgerock.opendj.ldap.RequestHandlerFactory;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
+import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An LDAP load balancing proxy which forwards requests to one or more remote
* Directory Servers. This is implementation is very simple and is only intended
@@ -62,6 +66,12 @@
* </pre>
*/
public final class Proxy {
+ /** The timeout after which a connection will be marked as failed. */
+ private static final long TIMEOUT_SECONDS = 3;
+
+ /** The interval between keepalive pings. */
+ private static final long HEARTBEAT_INTERVAL_SECONDS = 10;
+
/**
* Main method.
*
@@ -92,15 +102,16 @@
for (int i = 4; i < args.length; i += 2) {
final String remoteAddress = args[i];
final int remotePort = Integer.parseInt(args[i + 1]);
+ final LDAPOptions commonOptions = new LDAPOptions().setTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- factories.add(Connections.newCachedConnectionPool(Connections
- .newAuthenticatedConnectionFactory(Connections
- .newHeartBeatConnectionFactory(new LDAPConnectionFactory(remoteAddress,
- remotePort)), Requests.newSimpleBindRequest(proxyDN,
- proxyPassword.toCharArray()))));
- bindFactories.add(Connections.newCachedConnectionPool(Connections
- .newHeartBeatConnectionFactory(new LDAPConnectionFactory(remoteAddress,
- remotePort))));
+ final BindRequest bindRequest = Requests.newSimpleBindRequest(proxyDN, proxyPassword.toCharArray());
+ final LDAPOptions options = new LDAPOptions(commonOptions).setBindRequest(bindRequest);
+ factories.add(newCachedConnectionPool(newLDAPConnectionFactory(remoteAddress, remotePort, options)));
+
+ final LDAPOptions heartBeatOptions =
+ new LDAPOptions(commonOptions).setHeartBeatInterval(HEARTBEAT_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ bindFactories.add(newCachedConnectionPool(
+ newLDAPConnectionFactory(remoteAddress, remotePort, heartBeatOptions)));
}
// --- JCite pools ---
@@ -136,7 +147,7 @@
final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
LDAPListener listener = null;
try {
- listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
+ listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
System.out.println("Press any key to stop the server...");
System.in.read();
} catch (final IOException e) {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
index a3c29f9..18f4729 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ReadSchema.java
@@ -30,14 +30,16 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.ObjectClass;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.Syntax;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which prints a summary of the schema on the
* named server as well as any warnings encountered while parsing the schema.
@@ -68,7 +70,7 @@
// --- JCite ---
// Connect and bind to the server.
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
index 5f78d81..c0c3f8b 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
@@ -31,6 +31,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
@@ -38,13 +39,13 @@
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.Modification;
import org.forgerock.opendj.ldap.RequestContext;
import org.forgerock.opendj.ldap.RequestHandler;
@@ -70,6 +71,8 @@
import org.forgerock.opendj.ldap.responses.SearchResultReference;
import org.forgerock.opendj.ldap.schema.AttributeType;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* This example is based on the {@link Proxy}. This example does no load
* balancing, but instead rewrites attribute descriptions and DN suffixes in
@@ -107,6 +110,10 @@
* and {@code proxyUserPassword} to {@code password}.
*/
public final class RewriterProxy {
+
+ /** The timeout after which a connection will be marked as failed. */
+ private static final long TIMEOUT_SECONDS = 3;
+
private static final class Rewriter implements RequestHandler<RequestContext> {
/** This example hard codes the attribute... */
@@ -393,15 +400,15 @@
final String proxyPassword = args[3];
final String remoteAddress = args[4];
final int remotePort = Integer.parseInt(args[5]);
+ final LDAPOptions factoryOptions = new LDAPOptions()
+ .setBindRequest(Requests.newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()))
+ .setTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
// Create connection factories.
- final ConnectionFactory factory =
- Connections.newCachedConnectionPool(Connections.newAuthenticatedConnectionFactory(
- new LDAPConnectionFactory(remoteAddress, remotePort), Requests
- .newSimpleBindRequest(proxyDN, proxyPassword.toCharArray())));
- final ConnectionFactory bindFactory =
- Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
- remotePort));
+ final ConnectionFactory factory = newCachedConnectionPool(
+ newLDAPConnectionFactory(remoteAddress, remotePort, factoryOptions));
+ final ConnectionFactory bindFactory = newCachedConnectionPool(
+ newLDAPConnectionFactory(remoteAddress, remotePort));
/*
* Create a server connection adapter which will create a new proxy
@@ -424,7 +431,7 @@
final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
LDAPListener listener = null;
try {
- listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
+ listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
System.out.println("Press any key to stop the server...");
System.in.read();
} catch (final IOException e) {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
index 50ae094..d2b6b66 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SASLAuth.java
@@ -37,15 +37,17 @@
import javax.net.ssl.SSLContext;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which performs SASL PLAIN authentication to a
* directory server over LDAP with StartTLS. This example takes the following
@@ -87,8 +89,7 @@
// --- JCite ---
try {
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(host, port, getTrustAllOptions());
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(host, port, getTrustAllOptions());
connection = factory.getConnection();
PlainSASLBindRequest request =
Requests.newPlainSASLBindRequest(authcid, passwd.toCharArray())
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
index 9a70944..7bbf21c 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Search.java
@@ -31,8 +31,8 @@
import java.util.Arrays;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
@@ -40,6 +40,8 @@
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which searches a Directory Server. This example
* takes the following command line parameters:
@@ -92,7 +94,7 @@
final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
// Connect and bind to the server.
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
index d4007ef..eec45ef 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchAsync.java
@@ -31,6 +31,7 @@
import java.util.concurrent.CountDownLatch;
import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LdapPromise;
@@ -175,7 +176,7 @@
// --- Using Promises ---
// Initiate the asynchronous connect, bind, and search.
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = Connections.newLDAPConnectionFactory(hostName, port);
factory.getConnectionAsync()
.thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
index 67db44b..85b12cc 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SearchBind.java
@@ -31,11 +31,13 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An interactive command-line client that performs a search and subsequent
* simple bind. The client prompts for email address and for a password, and
@@ -62,7 +64,7 @@
System.err.println("For example: localhost 1389 dc=example,dc=com");
System.exit(1);
}
- String host = args[0];
+ String hostName = args[0];
int port = Integer.parseInt(args[1]);
String baseDN = args[2];
@@ -78,7 +80,7 @@
char[] password = c.readPassword("Password: ");
// Search using mail address, and then bind with the DN and password.
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
connection = factory.getConnection();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
index 93dcddd..43908cf 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -46,6 +46,8 @@
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldif.LDIFEntryReader;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An LDAP directory server which exposes data contained in an LDIF file. This
* is implementation is very simple and is only intended as an example:
@@ -125,10 +127,10 @@
}
};
- listener = new LDAPListener(localAddress, localPort, sslWrapper, options);
+ listener = newLDAPListener(localAddress, localPort, sslWrapper, options);
} else {
// No SSL.
- listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
+ listener = newLDAPListener(localAddress, localPort, connectionHandler, options);
}
System.out.println("Press any key to stop the server...");
System.in.read();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
index 6c5d881..e5b1074 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ShortLife.java
@@ -31,14 +31,16 @@
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.Entries;
import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.TreeMapEntry;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* A command-line client that creates, updates, renames, and deletes a
* short-lived entry in order to demonstrate LDAP write operations using the
@@ -68,7 +70,7 @@
System.err.println("For example: localhost 1389");
System.exit(1);
}
- String host = args[0];
+ String hostName = args[0];
int port = Integer.parseInt(args[1]);
// User credentials of a "Directory Administrators" group member.
@@ -95,7 +97,7 @@
LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
connection = factory.getConnection();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
index 60d9456..91feb0f 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/SimpleAuth.java
@@ -34,13 +34,15 @@
import javax.net.ssl.TrustManager;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.TrustManagers;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* An example client application which performs simple authentication to a
* directory server. This example takes the following command line parameters:
@@ -88,7 +90,7 @@
* Authenticate over LDAP.
*/
private static void connect() {
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
@@ -167,8 +169,7 @@
try {
final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(host, port,
- getTrustOptions(host, keystore, storepass));
+ newLDAPConnectionFactory(hostName, port, getTrustOptions(hostName, keystore, storepass));
connection = factory.getConnection();
connection.bind(bindDN, bindPassword.toCharArray());
@@ -220,8 +221,7 @@
Connection connection = null;
try {
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(host, port, getTrustAllOptions());
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port, getTrustAllOptions());
connection = factory.getConnection();
connection.bind(bindDN, bindPassword.toCharArray());
System.out.println("Authenticated as " + bindDN + ".");
@@ -256,7 +256,7 @@
// trustAllConnect();
}
- private static String host;
+ private static String hostName;
private static int port;
private static String bindDN;
private static String bindPassword;
@@ -276,7 +276,7 @@
giveUp();
}
- host = args[0];
+ hostName = args[0];
port = Integer.parseInt(args[1]);
bindDN = args[2];
bindPassword = args[3];
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
index dd4cfa1..31cd6f4 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UpdateGroup.java
@@ -29,8 +29,8 @@
import java.util.Collection;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RootDSE;
@@ -40,6 +40,8 @@
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.responses.CompareResult;
+import static org.forgerock.opendj.ldap.Connections.*;
+
/**
* This command-line client demonstrates adding and removing a member from a
* (potentially large) static group.
@@ -81,13 +83,13 @@
if (args.length != 5) {
printUsage();
}
- final String host = args[0];
+ final String hostName = args[0];
final int port = Integer.parseInt(args[1]);
final String groupDN = args[2];
final String memberDN = args[3];
final ModificationType modType = getModificationType(args[4]);
- final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
try {
connection = factory.getConnection();
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
index 2ccdf53..ba096bc 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/UseGenericControl.java
@@ -27,14 +27,16 @@
package org.forgerock.opendj.examples;
+import java.io.IOException;
+
import org.forgerock.opendj.io.ASN1;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
@@ -46,7 +48,7 @@
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
-import java.io.IOException;
+import static org.forgerock.opendj.ldap.Connections.*;
/**
* An example client application which uses
@@ -88,8 +90,7 @@
final LDIFEntryWriter writer = new LDIFEntryWriter(System.out);
// Connect and bind to the server.
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory(hostName, port);
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port);
Connection connection = null;
// Prepare the value for the GenericControl.
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
index 09cf837..8cc108d 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPModify.java
@@ -29,7 +29,8 @@
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
import static com.forgerock.opendj.cli.Utils.filterExitCode;
-import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage;
+import static com.forgerock.opendj.ldap.tools.Utils.*;
+
import static org.forgerock.util.Utils.closeSilently;
import java.io.FileInputStream;
@@ -56,6 +57,7 @@
import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
@@ -248,10 +250,10 @@
int run(final String[] args) {
// Create the command-line argument parser for use with this program.
final LocalizableMessage toolDescription = INFO_LDAPMODIFY_TOOL_DESCRIPTION.get();
- final ArgumentParser argParser =
- new ArgumentParser(LDAPModify.class.getName(), toolDescription, false);
+ final ArgumentParser argParser = new ArgumentParser(LDAPModify.class.getName(), toolDescription, false);
ConnectionFactoryProvider connectionFactoryProvider;
ConnectionFactory connectionFactory;
+ BindRequest bindRequest;
BooleanArgument continueOnError;
// TODO: Remove this due to new LDIF reader api?
@@ -366,7 +368,8 @@
return 0;
}
- connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
+ connectionFactory = connectionFactoryProvider.getConnectionFactory();
+ bindRequest = connectionFactoryProvider.getBindRequest();
} catch (final ArgumentException ae) {
final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
errPrintln(message);
@@ -456,13 +459,14 @@
if (!noop.isPresent()) {
try {
connection = connectionFactory.getConnection();
+ if (bindRequest != null) {
+ printPasswordPolicyResults(this, connection.bind(bindRequest));
+ }
} catch (final LdapException ere) {
- return Utils.printErrorMessage(this, ere);
+ return printErrorMessage(this, ere);
}
}
- Utils.printPasswordPolicyResults(this, connection);
-
writer = new LDIFEntryWriter(getOutputStream());
final VisitorImpl visitor = new VisitorImpl();
ChangeRecordReader reader = null;
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
index dd1c16b..36ba5f2 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/LDAPSearch.java
@@ -63,6 +63,7 @@
import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
import org.forgerock.opendj.ldap.controls.VirtualListViewRequestControl;
import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl;
+import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Result;
@@ -88,6 +89,7 @@
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
+import static com.forgerock.opendj.ldap.tools.Utils.*;
/**
* A tool that can be used to issue Search requests to the Directory Server.
@@ -239,6 +241,7 @@
"[filter] [attributes ...]");
ConnectionFactoryProvider connectionFactoryProvider;
ConnectionFactory connectionFactory;
+ BindRequest bindRequest;
BooleanArgument countEntries;
BooleanArgument dontWrap;
@@ -440,7 +443,8 @@
return 0;
}
- connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory();
+ connectionFactory = connectionFactoryProvider.getConnectionFactory();
+ bindRequest = connectionFactoryProvider.getBindRequest();
} catch (final ArgumentException ae) {
final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
errPrintln(message);
@@ -825,11 +829,13 @@
Connection connection;
try {
connection = connectionFactory.getConnection();
+ if (bindRequest != null) {
+ printPasswordPolicyResults(this, connection.bind(bindRequest));
+ }
} catch (final LdapException ere) {
- return Utils.printErrorMessage(this, ere);
+ return printErrorMessage(this, ere);
}
- Utils.printPasswordPolicyResults(this, connection);
try {
int filterIndex = 0;
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
index 50e0795..8b728d8 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -53,7 +53,6 @@
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentParser;
-import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
import com.forgerock.opendj.cli.BooleanArgument;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.IntegerArgument;
@@ -547,10 +546,9 @@
}
} else {
connection = this.connection;
- if (!noRebind && connection instanceof AuthenticatedConnection) {
- final AuthenticatedConnection ac = (AuthenticatedConnection) connection;
+ if (!noRebind && app.getBindRequest() != null) {
try {
- ac.rebindAsync().getOrThrow();
+ connection.bindAsync(app.getBindRequest()).getOrThrow();
} catch (final InterruptedException e) {
// Ignore and check stop requested
continue;
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
index 2d7a493..7ac2692 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
@@ -26,13 +26,8 @@
*/
package com.forgerock.opendj.ldap.tools;
-import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
-import static com.forgerock.opendj.cli.Utils.readBytesFromFile;
-import static com.forgerock.opendj.cli.Utils.secondsToTimeString;
-
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
-import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.LdapException;
@@ -51,9 +46,11 @@
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl;
-import com.forgerock.opendj.cli.AuthenticatedConnectionFactory.AuthenticatedConnection;
import com.forgerock.opendj.util.StaticUtils;
+import static com.forgerock.opendj.cli.Utils.*;
+import static com.forgerock.opendj.ldap.tools.ToolsMessages.*;
+
/**
* This class provides utility functions for all the client side tools.
*/
@@ -196,84 +193,77 @@
return ere.getResult().getResultCode().intValue();
}
- static void printPasswordPolicyResults(final ConsoleApplication app, final Connection connection) {
- if (connection instanceof AuthenticatedConnection) {
- final AuthenticatedConnection conn = (AuthenticatedConnection) connection;
- final BindResult result = conn.getAuthenticatedBindResult();
-
- try {
- final AuthorizationIdentityResponseControl control =
- result.getControl(AuthorizationIdentityResponseControl.DECODER,
- new DecodeOptions());
- if (control != null) {
- final LocalizableMessage message =
- INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID());
- app.println(message);
- }
- } catch (final DecodeException e) {
- app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+ static void printPasswordPolicyResults(final ConsoleApplication app, BindResult result) {
+ try {
+ final AuthorizationIdentityResponseControl control =
+ result.getControl(AuthorizationIdentityResponseControl.DECODER, new DecodeOptions());
+ if (control != null) {
+ final LocalizableMessage message = INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID());
+ app.println(message);
}
+ } catch (final DecodeException e) {
+ app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+ }
- try {
- final PasswordExpiredResponseControl control =
- result.getControl(PasswordExpiredResponseControl.DECODER,
- new DecodeOptions());
- if (control != null) {
+ try {
+ final PasswordExpiredResponseControl control =
+ result.getControl(PasswordExpiredResponseControl.DECODER,
+ new DecodeOptions());
+ if (control != null) {
+ final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
+ app.println(message);
+ }
+ } catch (final DecodeException e) {
+ app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+ }
+
+ try {
+ final PasswordExpiringResponseControl control =
+ result.getControl(PasswordExpiringResponseControl.DECODER,
+ new DecodeOptions());
+ if (control != null) {
+ final LocalizableMessage timeString =
+ secondsToTimeString(control.getSecondsUntilExpiration());
+ final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+ app.println(message);
+ }
+ } catch (final DecodeException e) {
+ app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
+ }
+
+ try {
+ final PasswordPolicyResponseControl control =
+ result.getControl(PasswordPolicyResponseControl.DECODER,
+ new DecodeOptions());
+ if (control != null) {
+ final PasswordPolicyErrorType errorType = control.getErrorType();
+ if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
app.println(message);
- }
- } catch (final DecodeException e) {
- app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
- }
+ } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) {
+ final LocalizableMessage message = INFO_BIND_ACCOUNT_LOCKED.get();
+ app.println(message);
+ } else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET) {
- try {
- final PasswordExpiringResponseControl control =
- result.getControl(PasswordExpiringResponseControl.DECODER,
- new DecodeOptions());
- if (control != null) {
- final LocalizableMessage timeString =
- secondsToTimeString(control.getSecondsUntilExpiration());
- final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+ final LocalizableMessage message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
app.println(message);
}
- } catch (final DecodeException e) {
- app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
- }
- try {
- final PasswordPolicyResponseControl control =
- result.getControl(PasswordPolicyResponseControl.DECODER,
- new DecodeOptions());
- if (control != null) {
- final PasswordPolicyErrorType errorType = control.getErrorType();
- if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) {
- final LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
- app.println(message);
- } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) {
- final LocalizableMessage message = INFO_BIND_ACCOUNT_LOCKED.get();
- app.println(message);
- } else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET) {
-
- final LocalizableMessage message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
- app.println(message);
- }
-
- final PasswordPolicyWarningType warningType = control.getWarningType();
- if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
- final LocalizableMessage timeString =
- secondsToTimeString(control.getWarningValue());
- final LocalizableMessage message =
- INFO_BIND_PASSWORD_EXPIRING.get(timeString);
- app.println(message);
- } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) {
- final LocalizableMessage message =
- INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue());
- app.println(message);
- }
+ final PasswordPolicyWarningType warningType = control.getWarningType();
+ if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) {
+ final LocalizableMessage timeString =
+ secondsToTimeString(control.getWarningValue());
+ final LocalizableMessage message =
+ INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+ app.println(message);
+ } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) {
+ final LocalizableMessage message =
+ INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue());
+ app.println(message);
}
- } catch (final DecodeException e) {
- app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
}
+ } catch (final DecodeException e) {
+ app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage()));
}
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index a0a3d26..7e5e26a 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -16,11 +16,6 @@
package org.forgerock.opendj.rest2ldap;
-import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
-import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
-import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
-import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
-
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
@@ -50,11 +45,10 @@
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
-import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.RDN;
@@ -64,12 +58,18 @@
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.TrustManagers;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
+import static java.util.concurrent.TimeUnit.*;
+
+import static org.forgerock.opendj.ldap.Connections.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
+import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.*;
+import static org.forgerock.opendj.rest2ldap.Utils.*;
+
/**
* Provides core factory methods and builders for constructing LDAP resource
* collections.
@@ -970,31 +970,28 @@
Math.max(configuration.get("connectionPoolSize").defaultTo(10).asInteger(), 1);
final int heartBeatIntervalSeconds =
Math.max(configuration.get("heartBeatIntervalSeconds").defaultTo(30).asInteger(), 1);
- final int heartBeatTimeoutMilliSeconds =
- Math.max(configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500)
- .asInteger(), 100);
+ final int heartBeatTimeoutMS =
+ Math.max(configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500).asInteger(), 100);
+ final LDAPOptions options =
+ new LDAPOptions().setHeartBeatInterval(heartBeatIntervalSeconds, SECONDS)
+ .setTimeout(heartBeatTimeoutMS, MILLISECONDS);
// Parse authentication parameters.
- final BindRequest bindRequest;
if (configuration.isDefined("authentication")) {
final JsonValue authn = configuration.get("authentication");
if (authn.isDefined("simple")) {
final JsonValue simple = authn.get("simple");
- bindRequest =
- Requests.newSimpleBindRequest(simple.get("bindDN").required().asString(),
- simple.get("bindPassword").required().asString().toCharArray());
+ options.setBindRequest(newSimpleBindRequest(simple.get("bindDN").required().asString(),
+ simple.get("bindPassword").required().asString().toCharArray()));
} else {
throw new IllegalArgumentException("Only simple authentication is supported");
}
- } else {
- bindRequest = null;
}
// Parse SSL/StartTLS parameters.
final ConnectionSecurity connectionSecurity =
configuration.get("connectionSecurity").defaultTo(ConnectionSecurity.NONE).asEnum(
ConnectionSecurity.class);
- final LDAPOptions options = new LDAPOptions();
if (connectionSecurity != ConnectionSecurity.NONE) {
try {
// Configure SSL.
@@ -1038,24 +1035,18 @@
throw new IllegalArgumentException("No primaryLDAPServers");
}
final ConnectionFactory primary =
- parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
- heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
+ parseLDAPServers(primaryLDAPServers, connectionPoolSize, heartBeatIntervalSeconds, options);
// Parse secondary data center(s).
final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
- final ConnectionFactory secondary;
+ ConnectionFactory secondary = null;
if (secondaryLDAPServers.isList()) {
if (secondaryLDAPServers.size() > 0) {
secondary =
- parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
- heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options);
- } else {
- secondary = null;
+ parseLDAPServers(secondaryLDAPServers, connectionPoolSize, heartBeatIntervalSeconds, options);
}
} else if (!secondaryLDAPServers.isNull()) {
throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
- } else {
- secondary = null;
}
// Create fail-over.
@@ -1094,32 +1085,22 @@
}
}
- private static ConnectionFactory parseLDAPServers(final JsonValue config,
- final BindRequest bindRequest, final int connectionPoolSize,
- final int heartBeatIntervalSeconds, final int heartBeatTimeoutMilliSeconds,
- final LDAPOptions options) {
+ private static ConnectionFactory parseLDAPServers(final JsonValue config, final int connectionPoolSize,
+ final int heartBeatIntervalSeconds, final LDAPOptions options) {
final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
+
for (final JsonValue server : config) {
final String host = server.get("hostname").required().asString();
final int port = server.get("port").required().asInteger();
- ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
- factory =
- Connections.newHeartBeatConnectionFactory(factory,
- heartBeatIntervalSeconds * 1000, heartBeatTimeoutMilliSeconds,
- TimeUnit.MILLISECONDS);
- if (bindRequest != null) {
- factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
- }
+ ConnectionFactory factory = newLDAPConnectionFactory(host, port, options);
if (connectionPoolSize > 1) {
- factory =
- Connections.newCachedConnectionPool(factory, 0, connectionPoolSize, 60L,
- TimeUnit.SECONDS);
+ factory = newCachedConnectionPool(factory, 0, connectionPoolSize, 60L, SECONDS);
}
servers.add(factory);
}
+
if (servers.size() > 1) {
- return Connections.newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers,
- heartBeatIntervalSeconds, TimeUnit.SECONDS));
+ return newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers, heartBeatIntervalSeconds, SECONDS));
} else {
return servers.get(0);
}
diff --git a/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java b/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
index d0cc6e2..f02982f 100644
--- a/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
+++ b/opendj-server2x-adapter/src/test/java/org/forgerock/opendj/adapter/server2x/AdaptersTestCase.java
@@ -32,7 +32,6 @@
import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.ConstraintViolationException;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
@@ -41,6 +40,7 @@
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ModificationType;
@@ -74,9 +74,14 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import static java.util.concurrent.TimeUnit.*;
+
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.adapter.server2x.Adapters.*;
import static org.forgerock.opendj.adapter.server2x.EmbeddedServerTestCaseUtils.*;
+import static org.forgerock.opendj.ldap.Connections.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
/**
* This class defines a set of tests for the Adapters.class.
@@ -85,6 +90,13 @@
@Test
public class AdaptersTestCase extends ForgeRockTestCase {
+ /** The timeout after which a connection will be marked as failed. */
+ private static final long TIMEOUT_SEC = 3;
+
+ private Integer getListenPort() {
+ return Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"));
+ }
+
/**
* Provides an anonymous connection factories.
*
@@ -93,9 +105,8 @@
@DataProvider
public Object[][] anonymousConnectionFactories() {
return new Object[][] {
- { new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) },
- { Adapters.newAnonymousConnectionFactory() } };
+ { newLDAPConnectionFactory("localhost", getListenPort()) },
+ { newAnonymousConnectionFactory() } };
}
/**
@@ -106,11 +117,13 @@
@DataProvider
public Object[][] rootConnectionFactories() {
return new Object[][] {
- { Connections.newAuthenticatedConnectionFactory(
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))),
- Requests.newSimpleBindRequest("cn=directory manager", "password".toCharArray())) },
- { Adapters.newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
+ { newLDAPConnectionFactory(
+ "localhost",
+ getListenPort(),
+ new LDAPOptions()
+ .setBindRequest(newSimpleBindRequest("cn=directory manager", "password".toCharArray()))
+ .setTimeout(TIMEOUT_SEC, SECONDS)) },
+ { newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
}
/**
@@ -214,8 +227,7 @@
@Test
public void testSimpleLDAPConnectionFactorySimpleBind() throws LdapException {
final LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = null;
try {
connection = factory.getConnection();
@@ -239,8 +251,7 @@
@Test
public void testLDAPSASLBind() throws NumberFormatException, GeneralSecurityException, LdapException {
LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = factory.getConnection();
PlainSASLBindRequest request =
@@ -993,9 +1004,7 @@
final DeleteRequest deleteRequest = Requests.newDeleteRequest("sn=babs,dc=example,dc=org");
// LDAP Connection
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = null;
connection = factory.getConnection();
connection.bind("cn=Directory Manager", "password".toCharArray());
diff --git a/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java b/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
index 87acf0d..7a9c597 100644
--- a/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
+++ b/opendj-server3x-adapter/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
@@ -32,7 +32,6 @@
import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.ConstraintViolationException;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
@@ -41,6 +40,7 @@
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
import org.forgerock.opendj.ldap.ModificationType;
@@ -74,9 +74,14 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import static java.util.concurrent.TimeUnit.*;
+
import static org.fest.assertions.Assertions.*;
import static org.fest.assertions.Fail.*;
+import static org.forgerock.opendj.adapter.server3x.Adapters.*;
import static org.forgerock.opendj.adapter.server3x.EmbeddedServerTestCaseUtils.*;
+import static org.forgerock.opendj.ldap.Connections.*;
+import static org.forgerock.opendj.ldap.requests.Requests.*;
/**
* This class defines a set of tests for the Adapters.class.
@@ -85,6 +90,13 @@
@Test
public class AdaptersTestCase extends ForgeRockTestCase {
+ /** The timeout after which a connection will be marked as failed. */
+ private static final long TIMEOUT_SEC = 3;
+
+ private Integer getListenPort() {
+ return Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"));
+ }
+
/**
* Provides an anonymous connection factories.
*
@@ -93,9 +105,8 @@
@DataProvider
public Object[][] anonymousConnectionFactories() {
return new Object[][] {
- { new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) },
- { Adapters.newAnonymousConnectionFactory() } };
+ { newLDAPConnectionFactory("localhost", getListenPort()) },
+ { newAnonymousConnectionFactory() } };
}
/**
@@ -106,11 +117,13 @@
@DataProvider
public Object[][] rootConnectionFactories() {
return new Object[][] {
- { Connections.newAuthenticatedConnectionFactory(
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))),
- Requests.newSimpleBindRequest("cn=directory manager", "password".toCharArray())) },
- { Adapters.newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
+ { newLDAPConnectionFactory(
+ "localhost",
+ getListenPort(),
+ new LDAPOptions()
+ .setBindRequest(newSimpleBindRequest("cn=directory manager", "password".toCharArray()))
+ .setTimeout(TIMEOUT_SEC, SECONDS)) },
+ { newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
}
/**
@@ -214,8 +227,7 @@
@Test
public void testSimpleLDAPConnectionFactorySimpleBind() throws LdapException {
final LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = null;
try {
connection = factory.getConnection();
@@ -239,8 +251,7 @@
@Test
public void testLDAPSASLBind() throws NumberFormatException, GeneralSecurityException, LdapException {
LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = factory.getConnection();
PlainSASLBindRequest request =
@@ -993,9 +1004,7 @@
final DeleteRequest deleteRequest = Requests.newDeleteRequest("sn=babs,dc=example,dc=org");
// LDAP Connection
- final LDAPConnectionFactory factory =
- new LDAPConnectionFactory("localhost",
- Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port")));
+ final LDAPConnectionFactory factory = newLDAPConnectionFactory("localhost", getListenPort());
Connection connection = null;
connection = factory.getConnection();
connection.bind("cn=Directory Manager", "password".toCharArray());
--
Gitblit v1.10.0