OPENDJ-1607 (CR-5295) Merge HeartBeatConnectionFactory, AuthenticatedConnectionFactory and GrizzlyLDAPConnectionFactory
To avoid code duplication and makes API more fluent, we have merged HBCF, ACF and GCF by creating an AbstractLdapConnectionFactoryImpl.
The new connection skeletal associated is AbstractLdapConnectionImpl.
This is a sub-optimal solution, there is still some concurrency issues to address in AbstractLdapConnecitonImpl.
4 files deleted
3 files added
44 files modified
| | |
| | | */ |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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. |
| | | */ |
| | |
| | | /** 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 |
| | |
| | | } |
| | | } |
| | | } 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(); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | |
| | | |
| | | 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. |
| | |
| | | |
| | | private boolean isProgressSuite; |
| | | |
| | | private BindRequest bindRequest = null; |
| | | |
| | | /** |
| | | * Defines the different line styles for output. |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | |
| | | 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). |
| | | * |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | |
| | | 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 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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> |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | * |
| | |
| | | * 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 |
| | |
| | | * @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()); |
| | |
| | | /** |
| | | * 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 |
| | |
| | | * 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); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | * |
| | | * |
| | | * 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> |
| | |
| | | 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; |
| | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | * timeout. |
| | | */ |
| | | public long getConnectTimeout(final TimeUnit unit) { |
| | | return unit.convert(connectTimeoutInMillis, TimeUnit.MILLISECONDS); |
| | | return unit.convert(connectTimeoutInMillis, MILLISECONDS); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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> |
| | |
| | | * 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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> |
| New file |
| | |
| | | /* |
| | | * 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(); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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(); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | 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) |
| | | 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 |
| | | |
| | |
| | | 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.*; |
| | | |
| | |
| | | return; |
| | | } |
| | | sslContext = new SSLContextBuilder().getSSLContext(); |
| | | listener = |
| | | new LDAPListener(findFreeSocketAddress(), getInstance(), |
| | | new LDAPListenerOptions().setBacklog(4096)); |
| | | listener = newLDAPListener(findFreeSocketAddress(), getInstance(), new LDAPListenerOptions().setBacklog(4096)); |
| | | isRunning = true; |
| | | } |
| | | |
| | |
| | | * 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; |
| | |
| | | /** Saved scheduled tasks. */ |
| | | private final List<Callable<?>> tasks = new CopyOnWriteArrayList<Callable<?>>(); |
| | | |
| | | MockScheduler() { |
| | | public MockScheduler() { |
| | | // Nothing to do. |
| | | } |
| | | |
| | |
| | | return tasks.get(0); |
| | | } |
| | | |
| | | boolean isScheduled() { |
| | | public boolean isScheduled() { |
| | | return !tasks.isEmpty(); |
| | | } |
| | | |
| | | void runAllTasks() { |
| | | public void runAllTasks() { |
| | | for (final Callable<?> task : tasks) { |
| | | runTask0(task); |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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); |
| | | } |
| | | } |
| | |
| | | * 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; |
| | |
| | | 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 |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | * 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) { |
| | |
| | | } |
| | | } |
| | | |
| | | 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"); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | 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; |
| | | |
| | |
| | | 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; |
| | |
| | | /** |
| | | * 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; |
| | |
| | | } |
| | | |
| | | @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); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | // 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 |
| | |
| | | |
| | | 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 |
| | |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | * 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(); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | 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"); |
| | |
| | | * |
| | | * @return the reader to read incoming LDAP messages |
| | | */ |
| | | @Override |
| | | public LDAPReader<ASN1BufferReader> getReader() { |
| | | return this.reader; |
| | | } |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | |
| | | 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(); |
| | |
| | | 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) |
| | |
| | | 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); |
| | |
| | | // 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 = |
| | |
| | | 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); |
| | | } |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | |
| | | @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; |
| | | } |
| | | } |
| | |
| | | } |
| | | 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 |
| | |
| | | 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(); |
| | | } |
| | |
| | | final Result errorResult = |
| | | Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e) |
| | | .setDiagnosticMessage(e.getMessage()); |
| | | ldapConnection.close(null, false, errorResult); |
| | | ldapConnection.connectionErrorOccurred(false, errorResult); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @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); |
| | | } |
| | | } |
| | |
| | | # |
| | | # 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 |
| | |
| | | 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; |
| | |
| | | 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.*; |
| | |
| | | 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. |
| | | * |
| | |
| | | "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", |
| | |
| | | "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. |
| | |
| | | // 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(); |
| | | |
| | |
| | | @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? */ |
| | |
| | | @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(), |
| | |
| | | } |
| | | }); |
| | | |
| | | 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. |
| | |
| | | // 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 { |
| | |
| | | } |
| | | }); |
| | | |
| | | 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 { |
| | |
| | | @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(); |
| | |
| | | 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; |
| | |
| | | |
| | | 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.*; |
| | |
| | | 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; |
| | |
| | | @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++) { |
| | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | 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(); |
| | | } |
| | |
| | | // 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(); |
| | | } |
| | |
| | | |
| | | private LDAPListener createServer() { |
| | | try { |
| | | return new LDAPListener(findFreeSocketAddress(), |
| | | return newLDAPListener(findFreeSocketAddress(), |
| | | new ServerConnectionFactory<LDAPClientContext, Integer>() { |
| | | @Override |
| | | public ServerConnection<Integer> handleAccept( |
| | |
| | | |
| | | 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; |
| | |
| | | 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. |
| | | */ |
| | |
| | | * 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 { |
| | |
| | | |
| | | 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.*; |
| | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | // 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(); |
| | | } |
| | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | 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(); |
| | |
| | | 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"); |
| | | |
| | |
| | | |
| | | }; |
| | | |
| | | 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(); |
| | |
| | | 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 = |
| | |
| | | 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()); |
| | | |
| | |
| | | final MockServerConnectionFactory onlineServerConnectionFactory = |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | |
| | | try { |
| | | final MockServerConnection proxyServerConnection = new MockServerConnection(); |
| | |
| | | // 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(); |
| | |
| | | } 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(); |
| | |
| | | } |
| | | |
| | | }; |
| | | 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(); |
| | |
| | | final MockServerConnectionFactory onlineServerConnectionFactory = |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | newLDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | |
| | | try { |
| | | final MockServerConnection proxyServerConnection = new MockServerConnection() { |
| | |
| | | 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(); |
| | |
| | | } 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)); |
| | |
| | | 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()); |
| | | |
| | |
| | | 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()); |
| | |
| | | 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) { |
| | |
| | | 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"); |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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(); |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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 { |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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 { |
| | |
| | | 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; |
| | |
| | | */ |
| | | private static void connect() { |
| | | // --- JCite --- |
| | | final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port); |
| | | final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port); |
| | | Connection connection = null; |
| | | |
| | | try { |
| | |
| | | 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); |
| | | } |
| | |
| | | giveUp(); |
| | | } |
| | | |
| | | host = args[0]; |
| | | hostName = args[0]; |
| | | port = Integer.parseInt(args[1]); |
| | | infoType = args[2]; |
| | | final String infoTypeLc = infoType.toLowerCase(); |
| | |
| | | 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 |
| | |
| | | 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 { |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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(); |
| | |
| | | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | |
| | | 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()); |
| | | |
| | |
| | | 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 |
| | |
| | | * </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. |
| | | * |
| | |
| | | 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 --- |
| | | |
| | |
| | | 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) { |
| | |
| | | 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. |
| | |
| | | |
| | | // --- JCite --- |
| | | // Connect and bind to the server. |
| | | final LDAPConnectionFactory factory = new LDAPConnectionFactory(hostName, port); |
| | | final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port); |
| | | Connection connection = null; |
| | | |
| | | try { |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | * 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... */ |
| | |
| | | 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 |
| | |
| | | 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) { |
| | |
| | | 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 |
| | |
| | | |
| | | // --- 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()) |
| | |
| | | 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; |
| | |
| | | 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: |
| | |
| | | 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 { |
| | |
| | | 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; |
| | |
| | | |
| | | // --- 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>() { |
| | |
| | | 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 |
| | |
| | | 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]; |
| | | |
| | |
| | | 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(); |
| | |
| | | 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: |
| | |
| | | } |
| | | }; |
| | | |
| | | 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(); |
| | |
| | | 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 |
| | |
| | | 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. |
| | |
| | | |
| | | 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(); |
| | |
| | | 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: |
| | |
| | | * Authenticate over LDAP. |
| | | */ |
| | | private static void connect() { |
| | | final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port); |
| | | final LDAPConnectionFactory factory = newLDAPConnectionFactory(hostName, port); |
| | | Connection connection = null; |
| | | |
| | | try { |
| | |
| | | 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()); |
| | | |
| | |
| | | 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 + "."); |
| | |
| | | // trustAllConnect(); |
| | | } |
| | | |
| | | private static String host; |
| | | private static String hostName; |
| | | private static int port; |
| | | private static String bindDN; |
| | | private static String bindPassword; |
| | |
| | | giveUp(); |
| | | } |
| | | |
| | | host = args[0]; |
| | | hostName = args[0]; |
| | | port = Integer.parseInt(args[1]); |
| | | bindDN = args[2]; |
| | | bindPassword = args[3]; |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | 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(); |
| | |
| | | |
| | | 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; |
| | |
| | | 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 |
| | |
| | | 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. |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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? |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | "[filter] [attributes ...]"); |
| | | ConnectionFactoryProvider connectionFactoryProvider; |
| | | ConnectionFactory connectionFactory; |
| | | BindRequest bindRequest; |
| | | |
| | | BooleanArgument countEntries; |
| | | BooleanArgument dontWrap; |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | |
| | | 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; |
| | |
| | | } |
| | | } 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; |
| | |
| | | */ |
| | | 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; |
| | |
| | | |
| | | 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. |
| | | */ |
| | |
| | | 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())); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | 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. |
| | |
| | | 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. |
| | |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | @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. |
| | | * |
| | |
| | | @DataProvider |
| | | public Object[][] anonymousConnectionFactories() { |
| | | return new Object[][] { |
| | | { new LDAPConnectionFactory("localhost", |
| | | Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) }, |
| | | { Adapters.newAnonymousConnectionFactory() } }; |
| | | { newLDAPConnectionFactory("localhost", getListenPort()) }, |
| | | { newAnonymousConnectionFactory() } }; |
| | | } |
| | | |
| | | /** |
| | |
| | | @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")) } }; |
| | | } |
| | | |
| | | /** |
| | |
| | | @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(); |
| | |
| | | @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 = |
| | |
| | | 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()); |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | @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. |
| | | * |
| | |
| | | @DataProvider |
| | | public Object[][] anonymousConnectionFactories() { |
| | | return new Object[][] { |
| | | { new LDAPConnectionFactory("localhost", |
| | | Integer.valueOf(CONFIG_PROPERTIES.getProperty("listen-port"))) }, |
| | | { Adapters.newAnonymousConnectionFactory() } }; |
| | | { newLDAPConnectionFactory("localhost", getListenPort()) }, |
| | | { newAnonymousConnectionFactory() } }; |
| | | } |
| | | |
| | | /** |
| | |
| | | @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")) } }; |
| | | } |
| | | |
| | | /** |
| | |
| | | @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(); |
| | |
| | | @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 = |
| | |
| | | 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()); |