| | |
| | | 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.CliMessages.*; |
| | | import static com.forgerock.opendj.cli.Utils.getHostNameForLdapUrl; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.AUTHN_BIND_REQUEST; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.CONNECT_TIMEOUT; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | |
| | | import org.forgerock.opendj.ldap.requests.PlainSASLBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.time.Duration; |
| | | |
| | | /** |
| | | * A connection factory designed for use with command line tools. |
| | |
| | | |
| | | /** The basic connection factory. */ |
| | | private ConnectionFactory connFactory; |
| | | /** The authenticated connection factory. */ |
| | | protected ConnectionFactory authenticatedConnFactory; |
| | | |
| | | /** The bind request to connect with. */ |
| | | private BindRequest bindRequest; |
| | |
| | | } |
| | | |
| | | /** |
| | | * Checks if any conflicting arguments are present, build the connection with selected arguments and returns the |
| | | * connection factory. If the application is interactive, it will prompt the user for missing parameters. |
| | | * Constructs a connection factory for pre-authenticated connections. Checks if any conflicting arguments are |
| | | * present, build the connection with selected arguments and returns the connection factory. If the application is |
| | | * interactive, it will prompt the user for missing parameters. |
| | | * |
| | | * @return The connection factory. |
| | | * @throws ArgumentException |
| | | * If an error occurs during the parsing of the arguments. |
| | | * (conflicting arguments or if an error occurs during building SSL context). |
| | | * If an error occurs during the parsing of the arguments (conflicting arguments or if an error occurs |
| | | * during building SSL context). |
| | | */ |
| | | public ConnectionFactory getConnectionFactory() throws ArgumentException { |
| | | public ConnectionFactory getAuthenticatedConnectionFactory() throws ArgumentException { |
| | | return getConnectionFactory(true); |
| | | } |
| | | |
| | | /** |
| | | * Constructs a connection factory for unauthenticated connections. Checks if any conflicting arguments are present, |
| | | * build the connection with selected arguments and returns the connection factory. If the application is |
| | | * interactive, it will prompt the user for missing parameters. |
| | | * |
| | | * @return The connection factory. |
| | | * @throws ArgumentException |
| | | * If an error occurs during the parsing of the arguments (conflicting arguments or if an error occurs |
| | | * during building SSL context). |
| | | */ |
| | | public ConnectionFactory getUnauthenticatedConnectionFactory() throws ArgumentException { |
| | | return getConnectionFactory(false); |
| | | } |
| | | |
| | | private ConnectionFactory getConnectionFactory(boolean usePreAuthentication) throws ArgumentException { |
| | | if (connFactory == null) { |
| | | port = portArg.isPresent() ? portArg.getIntValue() : 0; |
| | | |
| | |
| | | } |
| | | |
| | | Options options = Options.defaultOptions(); |
| | | |
| | | if (sslContext != null) { |
| | | options.set(SSL_CONTEXT, sslContext) |
| | | .set(USE_STARTTLS, useStartTLSArg.isPresent()); |
| | | .set(SSL_USE_STARTTLS, useStartTLSArg.isPresent()); |
| | | } |
| | | options.set(CONNECT_TIMEOUT_IN_MILLISECONDS, |
| | | TimeUnit.MILLISECONDS.toMillis(getConnectTimeout())); |
| | | options.set(CONNECT_TIMEOUT, new Duration((long) getConnectTimeout(), TimeUnit.MILLISECONDS)); |
| | | if (usePreAuthentication) { |
| | | options.set(AUTHN_BIND_REQUEST, getBindRequest()); |
| | | } |
| | | connFactory = new LDAPConnectionFactory(hostNameArg.getValue(), port, options); |
| | | } |
| | | return connFactory; |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the authenticated connection factory. |
| | | * |
| | | * @return The authenticated connection factory. |
| | | * @throws ArgumentException |
| | | * If an error occurs during parsing the arguments. |
| | | */ |
| | | public ConnectionFactory getAuthenticatedConnectionFactory() throws ArgumentException { |
| | | if (authenticatedConnFactory == null) { |
| | | authenticatedConnFactory = getConnectionFactory(); |
| | | final BindRequest bindRequest = getBindRequest(); |
| | | if (bindRequest != null) { |
| | | authenticatedConnFactory = new AuthenticatedConnectionFactory(authenticatedConnFactory, bindRequest); |
| | | } |
| | | } |
| | | return authenticatedConnFactory; |
| | | } |
| | | |
| | | /** |
| | | * Returns {@code true} if we can read on the provided path and |
| | | * {@code false} otherwise. |
| | | * |
| | |
| | | <justification>OPENDJ-1802 ByteString.valueOf() => valueOfInt(), valueOfLong(), valueOfUtf8(), valueOfBytes(), valueOfObject()</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/Connections</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>*newAuthenticatedConnectionFactory*</method> |
| | | <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into |
| | | LDAPConnectionFactory. Pre-authenticated connection support is now part of |
| | | LDAPConnectionFactory and can be enabled by specifying the AUTHN_BIND_REQUEST option.</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/Connections</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>*newHeartBeatConnectionFactory*</method> |
| | | <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into |
| | | LDAPConnectionFactory. Heart-beat support is now part of |
| | | LDAPConnectionFactory and can be configured using the HEARTBEAT_* options.</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/AuthenticatedConnectionFactory*</className> |
| | | <differenceType>8001</differenceType> |
| | | <justification>OPENDJ-1607: merge authenticated and heart-beat connection factories into |
| | | LDAPConnectionFactory. Pre-authenticated connection support is now part of |
| | | LDAPConnectionFactory and can be enabled by specifying the AUTHN_BIND_REQUEST option.</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/requests/StartTLSExtendedRequest</className> |
| | | <differenceType>7012</differenceType> |
| | | <method>*addEnabled*(java.util.Collection)</method> |
| | | <justification>OPENDJ-1607: added Collection based addEnabled* methods</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/AttributeParser</className> |
| | | <differenceType>7014</differenceType> |
| | | <method>java.util.Set asSetOf(org.forgerock.opendj.ldap.Function, java.lang.Object[])</method> |
| | |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import static com.forgerock.opendj.util.StaticUtils.getProvider; |
| | | |
| | | import org.forgerock.opendj.ldap.spi.TransportProvider; |
| | | import org.forgerock.util.Option; |
| | | import org.forgerock.util.Options; |
| | | |
| | | /** |
| | | * Common options for LDAP clients and listeners. |
| | | */ |
| | | abstract class CommonLDAPOptions { |
| | | /** |
| | | * Specifies the class loader which will be used to load the |
| | | * {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider}. |
| | | * <p> |
| | | * By default the default class loader will be used. |
| | | * <p> |
| | | * The transport provider is loaded using {@code java.util.ServiceLoader}, |
| | | * the JDK service-provider loading facility. The provider must be |
| | | * accessible from the same class loader that was initially queried to |
| | | * locate the configuration file; note that this is not necessarily the |
| | | * class loader from which the file was actually loaded. This method allows |
| | | * to provide a class loader to be used for loading the provider. |
| | | * |
| | | */ |
| | | public static final Option<ClassLoader> TRANSPORT_PROVIDER_CLASS_LOADER = Option.of(ClassLoader.class, null); |
| | | |
| | | /** |
| | | * Specifies the name of the provider to use for transport. |
| | | * <p> |
| | | * Transport providers implement {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider} |
| | | * interface. |
| | | * <p> |
| | | * The name should correspond to the name of an existing provider, as |
| | | * returned by {@code TransportProvider#getName()} method. |
| | | */ |
| | | public static final Option<String> TRANSPORT_PROVIDER = Option.of(String.class, null); |
| | | |
| | | /** |
| | | * Specifies the transport provider to use. This option is internal and only intended for testing. |
| | | */ |
| | | static final Option<TransportProvider> TRANSPORT_PROVIDER_INSTANCE = Option.of(TransportProvider.class, null); |
| | | |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#TCP_NODELAY |
| | | * TCP_NODELAY} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.tcpNoDelay} property. |
| | | */ |
| | | public static final Option<Boolean> TCP_NO_DELAY = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.tcpNoDelay", true)); |
| | | |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_REUSEADDR |
| | | * SO_REUSEADDR} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.reuseAddress} property. |
| | | * |
| | | */ |
| | | public static final Option<Boolean> SO_REUSE_ADDRESS = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.reuseAddress", true)); |
| | | |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_LINGER |
| | | * SO_LINGER} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code -1} (disabled) and may be configured using |
| | | * the {@code org.forgerock.opendj.io.linger} property. |
| | | * |
| | | * @param linger |
| | | * The value of the {@link java.net.SocketOptions#SO_LINGER |
| | | * SO_LINGER} socket option for new connections, or -1 if linger |
| | | * should be disabled. |
| | | * @return A reference to this set of options. |
| | | */ |
| | | public static final Option<Integer> SO_LINGER_IN_SECONDS = Option.withDefault( |
| | | getIntProperty("org.forgerock.opendj.io.linger", -1)); |
| | | |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_KEEPALIVE |
| | | * SO_KEEPALIVE} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.keepAlive} property. |
| | | * |
| | | */ |
| | | public static final Option<Boolean> SO_KEEPALIVE = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.keepAlive", true)); |
| | | |
| | | /** Sets the decoding options which will be used to control how requests and responses are decoded. */ |
| | | public static final Option<DecodeOptions> LDAP_DECODE_OPTIONS = Option.withDefault(new DecodeOptions()); |
| | | |
| | | static TransportProvider getTransportProvider(final Options options) { |
| | | final TransportProvider transportProvider = options.get(TRANSPORT_PROVIDER_INSTANCE); |
| | | if (transportProvider != null) { |
| | | return transportProvider; |
| | | } |
| | | return getProvider(TransportProvider.class, |
| | | options.get(TRANSPORT_PROVIDER), |
| | | options.get(TRANSPORT_PROVIDER_CLASS_LOADER)); |
| | | } |
| | | |
| | | static boolean getBooleanProperty(final String name, final boolean defaultValue) { |
| | | final String value = System.getProperty(name); |
| | |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | /** Sets the decoding options which will be used to control how requests and responses are decoded. */ |
| | | public static final Option<DecodeOptions> DECODE_OPTIONS = Option.withDefault(new DecodeOptions()); |
| | | /** |
| | | * Specifies the class loader which will be used to load the {@link TransportProvider}. |
| | | * <p> |
| | | * By default the default class loader will be used. |
| | | * <p> |
| | | * The transport provider is loaded using {@code java.util.ServiceLoader}, |
| | | * the JDK service-provider loading facility. The provider must be |
| | | * accessible from the same class loader that was initially queried to |
| | | * locate the configuration file; note that this is not necessarily the |
| | | * class loader from which the file was actually loaded. This method allows |
| | | * to provide a class loader to be used for loading the provider. |
| | | * |
| | | */ |
| | | public static final Option<ClassLoader> PROVIDER_CLASS_LOADER = Option.of(ClassLoader.class, null); |
| | | /** |
| | | * Specifies the name of the provider to use for transport. |
| | | * <p> |
| | | * Transport providers implement {@link TransportProvider} interface. |
| | | * <p> |
| | | * The name should correspond to the name of an existing provider, as |
| | | * returned by {@code TransportProvider#getName()} method. |
| | | */ |
| | | public static final Option<String> TRANSPORT_PROVIDER = Option.of(String.class, null); |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#TCP_NODELAY |
| | | * TCP_NODELAY} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.tcpNoDelay} property. |
| | | */ |
| | | public static final Option<Boolean> TCP_NO_DELAY = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.tcpNoDelay", true)); |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_REUSEADDR |
| | | * SO_REUSEADDR} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.reuseAddress} property. |
| | | * |
| | | */ |
| | | public static final Option<Boolean> REUSE_ADDRESS = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.reuseAddress", true)); |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_LINGER |
| | | * SO_LINGER} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code -1} (disabled) and may be configured using |
| | | * the {@code org.forgerock.opendj.io.linger} property. |
| | | * |
| | | * @param linger |
| | | * The value of the {@link java.net.SocketOptions#SO_LINGER |
| | | * SO_LINGER} socket option for new connections, or -1 if linger |
| | | * should be disabled. |
| | | * @return A reference to this set of options. |
| | | */ |
| | | public static final Option<Integer> LINGER = Option.withDefault( |
| | | getIntProperty("org.forgerock.opendj.io.linger", -1)); |
| | | /** |
| | | * Specifies the value of the {@link java.net.SocketOptions#SO_KEEPALIVE |
| | | * SO_KEEPALIVE} socket option for new connections. |
| | | * <p> |
| | | * The default setting is {@code true} and may be configured using the |
| | | * {@code org.forgerock.opendj.io.keepAlive} property. |
| | | * |
| | | */ |
| | | public static final Option<Boolean> KEEPALIVE = Option.withDefault( |
| | | getBooleanProperty("org.forgerock.opendj.io.keepAlive", true)); |
| | | } |
| | |
| | | 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.Options; |
| | | import org.forgerock.util.Reject; |
| | | import org.forgerock.util.promise.Promise; |
| | | |
| | |
| | | */ |
| | | 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 |
| | | * using the provided connection factory, but will reuse previously |
| | | * allocated connections when they are available. |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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> |
| | |
| | | public void close(org.forgerock.opendj.ldap.requests.UnbindRequest request, |
| | | String reason) { |
| | | // Do nothing. |
| | | }; |
| | | } |
| | | }; |
| | | } |
| | | |
| | |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import static com.forgerock.opendj.util.StaticUtils.getProvider; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_CONNECTION_CLOSED_BY_CLIENT; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_FAILED; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_TIMEOUT; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.LDAP_CONNECTION_CONNECT_TIMEOUT; |
| | | import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER; |
| | | import static java.util.concurrent.TimeUnit.SECONDS; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newStartTLSExtendedRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.unmodifiableSearchRequest; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newBindResult; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newGenericExtendedResult; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newResult; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.newLdapPromiseImpl; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newFailedLdapPromise; |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | import static org.forgerock.util.promise.Promises.newResultPromise; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.ScheduledFuture; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | import java.util.concurrent.locks.AbstractQueuedSynchronizer; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.DeleteRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.UnbindRequest; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.CompareResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.opendj.ldap.spi.ConnectionState; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl; |
| | | import org.forgerock.opendj.ldap.spi.LdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.TransportProvider; |
| | | import org.forgerock.util.AsyncFunction; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.Option; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.Reject; |
| | | import org.forgerock.util.promise.ExceptionHandler; |
| | | import org.forgerock.util.promise.Promise; |
| | | import org.forgerock.util.promise.PromiseImpl; |
| | | import org.forgerock.util.promise.ResultHandler; |
| | | import org.forgerock.util.time.Duration; |
| | | import org.forgerock.util.time.TimeService; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import com.forgerock.opendj.util.ReferenceCountedObject; |
| | | |
| | | /** |
| | | * A factory class which can be used to obtain connections to an LDAP Directory |
| | | * Server. |
| | | * A factory class which can be used to obtain connections to an LDAP Directory Server. A connection attempt comprises |
| | | * of the following steps: |
| | | * <ul> |
| | | * <li>first of all a TCP connection to the remote LDAP server is obtained. The attempt will fail if a connection is |
| | | * not obtained within the configured {@link #CONNECT_TIMEOUT connect timeout} |
| | | * <li>if LDAPS (not StartTLS) is requested then an SSL handshake is performed. LDAPS is enabled by specifying the |
| | | * {@link #SSL_CONTEXT} option along with {@link #SSL_USE_STARTTLS} set to {@code false} |
| | | * <li>if StartTLS is requested then a StartTLS request is sent and then an SSL handshake performed once the response |
| | | * has been received. StartTLS is enabled by specifying the {@link #SSL_CONTEXT} option along with |
| | | * {@link #SSL_USE_STARTTLS} set to {@code true} |
| | | * <li>an initial authentication request is sent if the {@link #AUTHN_BIND_REQUEST} option is specified |
| | | * <li>if heart-beat support is enabled via the {@link #HEARTBEAT_ENABLED} option, and none of steps 2-4 were performed, |
| | | * then an initial heart-beat is sent in order to determine whether the directory service is available. |
| | | * <li>the connect attempt will fail if it does not complete within the configured connection timeout. If the SSL |
| | | * handshake, StartTLS request, initial bind request, or initial heart-beat fail for any reason then the connection |
| | | * attempt will be deemed to have failed and an appropriate error returned. |
| | | * </ul> |
| | | * Once a connection has been established heart-beats will be sent periodically 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. |
| | | */ |
| | | public final class LDAPConnectionFactory extends CommonLDAPOptions implements ConnectionFactory { |
| | | /** |
| | | * Configures the connection factory to return pre-authenticated connections using the specified {@link |
| | | * BindRequest}. The connections returned by the connection factory will 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. |
| | | */ |
| | | public static final Option<BindRequest> AUTHN_BIND_REQUEST = Option.of(BindRequest.class, null); |
| | | |
| | | /** |
| | | * Specifies the connect timeout spcified. If a connection is not established within the timeout period (incl. SSL |
| | | * negotiation, initial bind request, and/or heart-beat), then a {@link TimeoutResultException} error result will be |
| | | * returned. |
| | | * <p> |
| | | * The default operation timeout is 10 seconds and may be configured using the {@code |
| | | * org.forgerock.opendj.io.connectTimeout} property. A timeout setting of 0 causes the OS connect timeout to be |
| | | * used. |
| | | */ |
| | | public static final Option<Duration> CONNECT_TIMEOUT = |
| | | Option.withDefault(new Duration((long) getIntProperty("org.forgerock.opendj.io.connectTimeout", 10000), |
| | | TimeUnit.MILLISECONDS)); |
| | | |
| | | /** |
| | | * Configures the connection factory to periodically send "heart-beat" or "keep-alive" requests to the Directory |
| | | * Server. This feature allows client applications to proactively detect network problems or unresponsive |
| | | * servers. In addition, frequent heartbeat requests may also prevent load-balancers or Directory Servers from |
| | | * closing otherwise idle connections. |
| | | * <p> |
| | | * Before returning new connections to the application the factory will first send an initial heart-beat request in |
| | | * order to determine that the remote server is responsive. If the heart-beat request fails or is too slow to |
| | | * respond then the connection is closed immediately and an error returned to the client. |
| | | * <p> |
| | | * Once a connection has been established successfully (including the initial heart-beat request), the connection |
| | | * factory will periodically send heart-beat requests on the connection based on the configured heart-beat interval. |
| | | * If the Directory Server is too slow to respond to the heart-beat 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-beat requests 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. The LDAP |
| | | * connection 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. |
| | | */ |
| | | public static final Option<Boolean> HEARTBEAT_ENABLED = Option.withDefault(false); |
| | | |
| | | /** |
| | | * Specifies the time between successive heart-beat requests (default interval is 10 seconds). Heart-beats will only |
| | | * be sent if {@link #HEARTBEAT_ENABLED} is set to {@code true}. |
| | | * |
| | | * @see #HEARTBEAT_ENABLED |
| | | */ |
| | | public static final Option<Duration> HEARTBEAT_INTERVAL = Option.withDefault(new Duration(10L, SECONDS)); |
| | | |
| | | /** |
| | | * Specifies the scheduler which will be used for periodically sending heart-beat requests. A system-wide scheduler |
| | | * will be used by default. Heart-beats will only be sent if {@link #HEARTBEAT_ENABLED} is set to {@code true}. |
| | | * |
| | | * @see #HEARTBEAT_ENABLED |
| | | */ |
| | | public static final Option<ScheduledExecutorService> HEARTBEAT_SCHEDULER = |
| | | Option.of(ScheduledExecutorService.class, null); |
| | | |
| | | /** |
| | | * Specifies the timeout for heart-beat requests, after which the remote Directory Server will be deemed to be |
| | | * unavailable (default timeout is 3 seconds). Heart-beats will only be sent if {@link #HEARTBEAT_ENABLED} is set to |
| | | * {@code true}. If a {@link #REQUEST_TIMEOUT request timeout} is also set then the lower of the two will be used |
| | | * for sending heart-beats. |
| | | * |
| | | * @see #HEARTBEAT_ENABLED |
| | | */ |
| | | public static final Option<Duration> HEARTBEAT_TIMEOUT = Option.withDefault(new Duration(3L, SECONDS)); |
| | | |
| | | /** |
| | | * Specifies the operation timeout. If a response is not received from the Directory Server within the timeout |
| | | * period, then the operation will be abandoned and a {@link TimeoutResultException} error result returned. A |
| | | * timeout setting of 0 disables operation timeout limits. |
| | | * <p> |
| | | * The default operation timeout is 0 (no timeout) and may be configured using the {@code |
| | | * org.forgerock.opendj.io.requestTimeout} property or the deprecated {@code org.forgerock.opendj.io.timeout} |
| | | * property. |
| | | */ |
| | | public static final Option<Duration> REQUEST_TIMEOUT = |
| | | Option.withDefault(new Duration((long) getIntProperty("org.forgerock.opendj.io.requestTimeout", |
| | | getIntProperty("org.forgerock.opendj.io.timeout", 0)), |
| | | TimeUnit.MILLISECONDS)); |
| | | |
| | | /** |
| | | * Specifies the SSL context which will be used when initiating connections with the Directory Server. |
| | | * <p> |
| | | * By default no SSL context will be used, indicating that connections will not be secured. |
| | | * If an SSL context is set then connections will be secured using either SSL or StartTLS |
| | | * depending on {@link #USE_STARTTLS}. |
| | | * By default no SSL context will be used, indicating that connections will not be secured. If an SSL context is set |
| | | * then connections will be secured using either SSL or StartTLS depending on {@link #SSL_USE_STARTTLS}. |
| | | */ |
| | | public static final Option<SSLContext> SSL_CONTEXT = Option.of(SSLContext.class, null); |
| | | |
| | | /** |
| | | * Specifies the cipher suites enabled for secure connections with the Directory Server. |
| | | * <p> |
| | | * The suites must be supported by the SSLContext specified by option {@link #SSL_CONTEXT}. Only the suites listed |
| | | * in the parameter are enabled for use. |
| | | */ |
| | | @SuppressWarnings({ "unchecked", "rawtypes" }) |
| | | public static final Option<List<String>> SSL_ENABLED_CIPHER_SUITES = |
| | | (Option) Option.of(List.class, Collections.<String>emptyList()); |
| | | |
| | | /** |
| | | * Specifies the protocol versions enabled for secure connections with the Directory Server. |
| | | * <p> |
| | | * The protocols must be supported by the SSLContext specified by option {@link #SSL_CONTEXT}. Only the protocols |
| | | * listed in the parameter are enabled for use. |
| | | */ |
| | | @SuppressWarnings({ "unchecked", "rawtypes" }) |
| | | public static final Option<List<String>> SSL_ENABLED_PROTOCOLS = |
| | | (Option) Option.of(List.class, Collections.<String>emptyList()); |
| | | |
| | | /** |
| | | * Specifies whether SSL or StartTLS should be used for securing connections when an SSL context is specified. |
| | | * <p> |
| | | * By default SSL will be used in preference to StartTLS. |
| | | */ |
| | | public static final Option<Boolean> USE_STARTTLS = Option.withDefault(false); |
| | | /** |
| | | * Specifies the operation timeout in milliseconds. If a response is not |
| | | * received from the Directory Server within the timeout period, then the |
| | | * operation will be abandoned and a {@link TimeoutResultException} error |
| | | * result returned. A timeout setting of 0 disables operation timeout limits. |
| | | * <p> |
| | | * The default operation timeout is 0 (no timeout) and may be configured |
| | | * using the {@code org.forgerock.opendj.io.timeout} property. |
| | | */ |
| | | public static final Option<Long> TIMEOUT_IN_MILLISECONDS = Option.of(Long.class, |
| | | (long) getIntProperty("org.forgerock.opendj.io.timeout", 0)); |
| | | /** |
| | | * Specifies the connect timeout spcified in milliseconds. If a connection is not established |
| | | * within the timeout period, then a {@link TimeoutResultException} error result will be returned. |
| | | * <p> |
| | | * The default operation timeout is 10 seconds and may be configured using |
| | | * the {@code org.forgerock.opendj.io.connectTimeout} property. |
| | | * A timeout setting of 0 causes the OS connect timeout to be used. |
| | | */ |
| | | public static final Option<Long> CONNECT_TIMEOUT_IN_MILLISECONDS = Option.of(Long.class, |
| | | (long) getIntProperty("org.forgerock.opendj.io.connectTimeout", 10000)); |
| | | /** |
| | | * Specifies the cipher suites enabled for secure connections with the Directory Server. |
| | | * <p> |
| | | * The suites must be supported by the SSLContext specified by option {@link SSL_CONTEXT}. |
| | | * Only the suites listed in the parameter are enabled for use. |
| | | */ |
| | | public static final Option<List<String>> ENABLED_CIPHER_SUITES = Option.withDefault( |
| | | Collections.<String>emptyList()); |
| | | /** |
| | | * Specifies the protocol versions enabled for secure connections with the |
| | | * Directory Server. |
| | | * <p> |
| | | * The protocols must be supported by the SSLContext specified by option {@link SSL_CONTEXT}. |
| | | * Only the protocols listed in the parameter are enabled for use. |
| | | */ |
| | | public static final Option<List<String>> ENABLED_PROTOCOLS = |
| | | Option.withDefault(Collections.<String>emptyList()); |
| | | public static final Option<Boolean> SSL_USE_STARTTLS = Option.withDefault(false); |
| | | |
| | | /** Default heart-beat which will target the root DSE but not return any results. */ |
| | | private static final SearchRequest DEFAULT_HEARTBEAT = |
| | | unmodifiableSearchRequest(newSearchRequest("", SearchScope.BASE_OBJECT, "(objectClass=*)", "1.1")); |
| | | |
| | | /** |
| | | * We implement the factory using the pimpl idiom in order to avoid making |
| | | * too many implementation classes public. |
| | | * Specifies the parameters of the search request that will be used for heart-beats. The default heart-beat search |
| | | * request is a base object search against the root DSE requesting no attributes. Heart-beats will only be sent if |
| | | * {@link #HEARTBEAT_ENABLED} is set to {@code true}. |
| | | * |
| | | * @see #HEARTBEAT_ENABLED |
| | | */ |
| | | public static final Option<SearchRequest> HEARTBEAT_SEARCH_REQUEST = |
| | | Option.of(SearchRequest.class, DEFAULT_HEARTBEAT); |
| | | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** The overall timeout to use when establishing connections, including SSL, bind, and heart-beat. */ |
| | | private final long connectTimeoutMS; |
| | | |
| | | /** |
| | | * The minimum amount of time the connection should remain idle (no responses) before starting to send heartbeats. |
| | | */ |
| | | private final long heartBeatDelayMS; |
| | | |
| | | /** Indicates whether heartbeats should be performed. */ |
| | | private final Boolean heartBeatEnabled; |
| | | |
| | | /** The heartbeat search request. */ |
| | | private final SearchRequest heartBeatRequest; |
| | | |
| | | /** |
| | | * The heartbeat timeout in milli-seconds. The connection will be marked as failed if no heartbeat response is |
| | | * received within the timeout. |
| | | */ |
| | | private final long heartBeatTimeoutMS; |
| | | |
| | | /** The interval between successive heartbeats. */ |
| | | private final long heartBeatintervalMS; |
| | | |
| | | /** The factory responsible for handling the low-level network communication with the Directory Server. */ |
| | | private final LDAPConnectionFactoryImpl impl; |
| | | |
| | | /** |
| | | * Transport provider that provides the implementation of this factory. |
| | | */ |
| | | /** The optional bind request which will be used as the initial heartbeat if specified. */ |
| | | private final BindRequest initialBindRequest; |
| | | |
| | | /** Flag which indicates whether this factory has been closed. */ |
| | | private final AtomicBoolean isClosed = new AtomicBoolean(); |
| | | |
| | | /** A copy of the original options. This is only useful for debugging. */ |
| | | private final Options options; |
| | | |
| | | /** Transport provider that provides the implementation of this factory. */ |
| | | private final TransportProvider provider; |
| | | |
| | | /** |
| | | * 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. |
| | | * Prevents the scheduler being released when there are remaining references (this factory or any connections). It |
| | | * is initially set to 1 because this factory has a reference. |
| | | */ |
| | | private final AtomicInteger referenceCount = new AtomicInteger(1); |
| | | |
| | | /** The heartbeat scheduler. */ |
| | | private final ReferenceCountedObject<ScheduledExecutorService>.Reference scheduler; |
| | | |
| | | /** Non-null if SSL or StartTLS should be used when creating new connections. */ |
| | | private final SSLContext sslContext; |
| | | |
| | | /** The list of permitted SSL ciphers for SSL negotiation. */ |
| | | private final List<String> sslEnabledCipherSuites; |
| | | |
| | | /** The list of permitted SSL protocols for SSL negotiation. */ |
| | | private final List<String> sslEnabledProtocols; |
| | | |
| | | /** Indicates whether a StartTLS request should be sent immediately after connecting. */ |
| | | private final boolean sslUseStartTLS; |
| | | |
| | | /** List of valid connections to which heartbeats will be sent. */ |
| | | private final List<ConnectionImpl> validConnections = new LinkedList<>(); |
| | | |
| | | /** This is package private in order to allow unit tests to inject fake time stamps. */ |
| | | TimeService timeService = TimeService.SYSTEM; |
| | | |
| | | /** Scheduled task which sends heart beats for all valid idle connections. */ |
| | | private final Runnable sendHeartBeatRunnable = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | boolean heartBeatSent = false; |
| | | for (final ConnectionImpl connection : getValidConnections()) { |
| | | heartBeatSent |= connection.sendHeartBeat(); |
| | | } |
| | | if (heartBeatSent) { |
| | | scheduler.get().schedule(checkHeartBeatRunnable, heartBeatTimeoutMS, TimeUnit.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 ConnectionImpl connection : getValidConnections()) { |
| | | connection.checkForHeartBeat(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /** The heartbeat scheduled future - which may be null if heartbeats are not being sent (no valid connections). */ |
| | | private ScheduledFuture<?> heartBeatFuture; |
| | | |
| | | /** |
| | | * 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. |
| | | * @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. |
| | | * @param host |
| | | * 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, Options.defaultOptions()); |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | * 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 If {@code host} or {@code options} was {@code null}. |
| | | * @throws ProviderNotFoundException if no provider is available or if the |
| | | * provider requested using options is not found. |
| | | * @param host |
| | | * The host name. |
| | | * @param port |
| | | * The port number. |
| | | * @param options |
| | | * The LDAP options to use when creating connections. |
| | | * @throws NullPointerException |
| | | * If {@code host} or {@code options} 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, final Options options) { |
| | | Reject.ifNull(host, options); |
| | | this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER), |
| | | options.get(PROVIDER_CLASS_LOADER)); |
| | | |
| | | this.connectTimeoutMS = options.get(CONNECT_TIMEOUT).to(TimeUnit.MILLISECONDS); |
| | | Reject.ifTrue(connectTimeoutMS < 0, "connect timeout must be >= 0"); |
| | | Reject.ifTrue(options.get(REQUEST_TIMEOUT).getValue() < 0, "request timeout must be >= 0"); |
| | | |
| | | this.heartBeatEnabled = options.get(HEARTBEAT_ENABLED); |
| | | this.heartBeatintervalMS = options.get(HEARTBEAT_INTERVAL).to(TimeUnit.MILLISECONDS); |
| | | this.heartBeatTimeoutMS = options.get(HEARTBEAT_TIMEOUT).to(TimeUnit.MILLISECONDS); |
| | | this.heartBeatDelayMS = heartBeatintervalMS / 2; |
| | | this.heartBeatRequest = options.get(HEARTBEAT_SEARCH_REQUEST); |
| | | if (heartBeatEnabled) { |
| | | Reject.ifTrue(heartBeatintervalMS <= 0, "heart-beat interval must be positive"); |
| | | Reject.ifTrue(heartBeatTimeoutMS <= 0, "heart-beat timeout must be positive"); |
| | | } |
| | | |
| | | this.provider = getTransportProvider(options); |
| | | this.scheduler = DEFAULT_SCHEDULER.acquireIfNull(options.get(HEARTBEAT_SCHEDULER)); |
| | | this.impl = provider.getLDAPConnectionFactory(host, port, options); |
| | | this.initialBindRequest = options.get(AUTHN_BIND_REQUEST); |
| | | this.sslContext = options.get(SSL_CONTEXT); |
| | | this.sslUseStartTLS = options.get(SSL_USE_STARTTLS); |
| | | this.sslEnabledProtocols = options.get(SSL_ENABLED_PROTOCOLS); |
| | | this.sslEnabledCipherSuites = options.get(SSL_ENABLED_CIPHER_SUITES); |
| | | |
| | | this.options = Options.copyOf(options); |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | impl.close(); |
| | | } |
| | | |
| | | @Override |
| | | public Promise<Connection, LdapException> getConnectionAsync() { |
| | | return impl.getConnectionAsync(); |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | synchronized (validConnections) { |
| | | if (!validConnections.isEmpty()) { |
| | | logger.debug(LocalizableMessage.raw( |
| | | "HeartbeatConnectionFactory '%s' is closing while %d active connections remain", |
| | | this, |
| | | validConnections.size())); |
| | | } |
| | | } |
| | | releaseScheduler(); |
| | | impl.close(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Connection getConnection() throws LdapException { |
| | | return impl.getConnection(); |
| | | return getConnectionAsync().getOrThrowUninterruptibly(); |
| | | } |
| | | |
| | | @Override |
| | | public Promise<Connection, LdapException> getConnectionAsync() { |
| | | acquireScheduler(); // Protect scheduler. |
| | | |
| | | // Register the connect timeout timer. |
| | | final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create(); |
| | | final AtomicReference<LDAPConnectionImpl> connectionHolder = new AtomicReference<>(); |
| | | if (connectTimeoutMS > 0) { |
| | | scheduler.get().schedule(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | if (promise.tryHandleException(newConnectTimeoutError())) { |
| | | closeSilently(connectionHolder.get()); |
| | | releaseScheduler(); |
| | | } |
| | | } |
| | | }, connectTimeoutMS, TimeUnit.MILLISECONDS); |
| | | } |
| | | |
| | | // Now connect, negotiate SSL, etc. |
| | | impl.getConnectionAsync() |
| | | // Save the connection. |
| | | .then(new Function<LDAPConnectionImpl, LDAPConnectionImpl, LdapException>() { |
| | | @Override |
| | | public LDAPConnectionImpl apply(final LDAPConnectionImpl connection) throws LdapException { |
| | | connectionHolder.set(connection); |
| | | return connection; |
| | | } |
| | | }) |
| | | .thenAsync(performStartTLSIfNeeded()) |
| | | .thenAsync(performSSLHandShakeIfNeeded(connectionHolder)) |
| | | .thenAsync(performInitialBindIfNeeded(connectionHolder)) |
| | | .thenAsync(performInitialHeartBeatIfNeeded(connectionHolder)) |
| | | .thenOnResult(new ResultHandler<Result>() { |
| | | @Override |
| | | public void handleResult(Result result) { |
| | | final LDAPConnectionImpl connection = connectionHolder.get(); |
| | | final ConnectionImpl connectionImpl = new ConnectionImpl(connection); |
| | | if (!promise.tryHandleResult(registerConnection(connectionImpl))) { |
| | | connectionImpl.close(); |
| | | } |
| | | } |
| | | }) |
| | | .thenOnException(new ExceptionHandler<LdapException>() { |
| | | @Override |
| | | public void handleException(final LdapException e) { |
| | | final LdapException connectException; |
| | | if (e instanceof ConnectionException || e instanceof AuthenticationException) { |
| | | connectException = e; |
| | | } else if (e instanceof TimeoutResultException) { |
| | | connectException = newHeartBeatTimeoutError(); |
| | | } else { |
| | | connectException = newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, |
| | | HBCF_HEARTBEAT_FAILED.get(), |
| | | e); |
| | | } |
| | | if (promise.tryHandleException(connectException)) { |
| | | closeSilently(connectionHolder.get()); |
| | | releaseScheduler(); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return promise; |
| | | } |
| | | |
| | | /** |
| | | * Returns the host name of the Directory Server. The returned host name is |
| | | * the same host name that was provided during construction and may be an IP |
| | | * address. More specifically, this method will not perform a reverse DNS |
| | | * Returns the host name of the Directory Server. The returned host name is the same host name that was provided |
| | | * during construction and may be an IP address. More specifically, this method will not perform a reverse DNS |
| | | * lookup. |
| | | * |
| | | * @return The host name of the Directory Server. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the name of the transport provider, which provides the implementation |
| | | * of this factory. |
| | | * Returns the name of the transport provider, which provides the implementation of this factory. |
| | | * |
| | | * @return The name of actual transport provider. |
| | | */ |
| | |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return impl.toString(); |
| | | return "LDAPConnectionFactory(provider=`" + getProviderName() + ", host='" + getHostName() + "', port=" |
| | | + getPort() + ", options=" + options + ")"; |
| | | } |
| | | |
| | | private void acquireScheduler() { |
| | | /* |
| | | * If the factory is not closed then we need to prevent the scheduler from being released while the |
| | | * connection attempt is in progress. |
| | | */ |
| | | referenceCount.incrementAndGet(); |
| | | if (isClosed.get()) { |
| | | releaseScheduler(); |
| | | throw new IllegalStateException("Attempted to get a connection on closed factory"); |
| | | } |
| | | } |
| | | |
| | | private ConnectionImpl[] getValidConnections() { |
| | | synchronized (validConnections) { |
| | | return validConnections.toArray(new ConnectionImpl[validConnections.size()]); |
| | | } |
| | | } |
| | | |
| | | private LdapException newConnectTimeoutError() { |
| | | final LocalizableMessage msg = LDAP_CONNECTION_CONNECT_TIMEOUT.get(impl.getSocketAddress(), connectTimeoutMS); |
| | | return newLdapException(ResultCode.CLIENT_SIDE_CONNECT_ERROR, msg.toString()); |
| | | } |
| | | |
| | | private LdapException newHeartBeatTimeoutError() { |
| | | return newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, HBCF_HEARTBEAT_TIMEOUT.get(heartBeatTimeoutMS)); |
| | | } |
| | | |
| | | private AsyncFunction<Void, BindResult, LdapException> performInitialBindIfNeeded( |
| | | final AtomicReference<LDAPConnectionImpl> connectionHolder) { |
| | | return new AsyncFunction<Void, BindResult, LdapException>() { |
| | | @Override |
| | | public Promise<BindResult, LdapException> apply(final Void ignored) throws LdapException { |
| | | if (initialBindRequest != null) { |
| | | return connectionHolder.get().bindAsync(initialBindRequest, null); |
| | | } else { |
| | | return newResultPromise(newBindResult(ResultCode.SUCCESS)); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private AsyncFunction<BindResult, Result, LdapException> performInitialHeartBeatIfNeeded( |
| | | final AtomicReference<LDAPConnectionImpl> connectionHolder) { |
| | | return new AsyncFunction<BindResult, Result, LdapException>() { |
| | | @Override |
| | | public Promise<Result, LdapException> apply(final BindResult ignored) throws LdapException { |
| | | // Only send an initial heartbeat if we haven't already interacted with the server. |
| | | if (heartBeatEnabled && sslContext == null && initialBindRequest == null) { |
| | | return connectionHolder.get().searchAsync(heartBeatRequest, null, null); |
| | | } else { |
| | | return newResultPromise(newResult(ResultCode.SUCCESS)); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private AsyncFunction<ExtendedResult, Void, LdapException> performSSLHandShakeIfNeeded( |
| | | final AtomicReference<LDAPConnectionImpl> connectionHolder) { |
| | | return new AsyncFunction<ExtendedResult, Void, LdapException>() { |
| | | @Override |
| | | public Promise<Void, LdapException> apply(final ExtendedResult extendedResult) throws LdapException { |
| | | if (sslContext != null && !sslUseStartTLS) { |
| | | return connectionHolder.get().enableTLS(sslContext, sslEnabledProtocols, sslEnabledCipherSuites); |
| | | } else { |
| | | return newResultPromise(null); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private AsyncFunction<LDAPConnectionImpl, ExtendedResult, LdapException> performStartTLSIfNeeded() { |
| | | return new AsyncFunction<LDAPConnectionImpl, ExtendedResult, LdapException>() { |
| | | @Override |
| | | public Promise<ExtendedResult, LdapException> apply(final LDAPConnectionImpl connection) |
| | | throws LdapException { |
| | | if (sslContext != null && sslUseStartTLS) { |
| | | final StartTLSExtendedRequest startTLS = newStartTLSExtendedRequest(sslContext) |
| | | .addEnabledCipherSuite(sslEnabledCipherSuites) |
| | | .addEnabledProtocol(sslEnabledProtocols); |
| | | return connection.extendedRequestAsync(startTLS, null); |
| | | } else { |
| | | return newResultPromise((ExtendedResult) newGenericExtendedResult(ResultCode.SUCCESS)); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private Connection registerConnection(final ConnectionImpl heartBeatConnection) { |
| | | synchronized (validConnections) { |
| | | if (heartBeatEnabled && validConnections.isEmpty()) { |
| | | // This is the first active connection, so start the heart beat. |
| | | heartBeatFuture = scheduler.get() |
| | | .scheduleWithFixedDelay(sendHeartBeatRunnable, |
| | | 0, |
| | | heartBeatintervalMS, |
| | | TimeUnit.MILLISECONDS); |
| | | } |
| | | validConnections.add(heartBeatConnection); |
| | | } |
| | | return heartBeatConnection; |
| | | } |
| | | |
| | | private void releaseScheduler() { |
| | | if (referenceCount.decrementAndGet() == 0) { |
| | | scheduler.release(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This synchronizer prevents Bind or StartTLS operations from being processed concurrently with heart-beats. This |
| | | * is required because the LDAP protocol specifically states that servers receiving a Bind operation should either |
| | | * wait for existing operations to complete or abandon them. The same presumably applies to StartTLS operations. |
| | | * Note that concurrent bind/StartTLS operations are not permitted. |
| | | * <p> |
| | | * This connection factory only coordinates Bind and StartTLS requests with heart-beats. It does not attempt to |
| | | * prevent or control attempts to send multiple concurrent Bind or StartTLS operations, etc. |
| | | * <p> |
| | | * This synchronizer can be thought of as cross between a read-write lock and a semaphore. Unlike a read-write lock |
| | | * there is no requirement that a thread releasing a lock must hold it. In addition, this synchronizer does not |
| | | * support reentrancy. A thread attempting to acquire exclusively more than once will deadlock, and a thread |
| | | * attempting to acquire shared more than once will succeed and be required to release an equivalent number of |
| | | * times. |
| | | * <p> |
| | | * The synchronizer has three states: |
| | | * <ul> |
| | | * <li> UNLOCKED(0) - the synchronizer may be acquired shared or exclusively |
| | | * <li> LOCKED_EXCLUSIVELY(-1) - the synchronizer is held exclusively and cannot be acquired shared or |
| | | * exclusively. An exclusive lock is held while a heart beat is in progress |
| | | * <li> LOCKED_SHARED(>0) - the synchronizer is held shared and cannot be acquired exclusively. N shared locks |
| | | * are held while N Bind or StartTLS operations are in progress. |
| | | * </ul> |
| | | */ |
| | | private static final class Sync extends AbstractQueuedSynchronizer { |
| | | /** 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 |
| | | |
| | | /** Keep compiler quiet. */ |
| | | private static final long serialVersionUID = -3590428415442668336L; |
| | | |
| | | boolean isHeld() { |
| | | return getState() != 0; |
| | | } |
| | | |
| | | 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 */); |
| | | } |
| | | |
| | | @Override |
| | | protected boolean isHeldExclusively() { |
| | | return getState() == LOCKED_EXCLUSIVELY; |
| | | } |
| | | |
| | | @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; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | /** A connection that sends heart beats and supports all operations. */ |
| | | private final class ConnectionImpl extends AbstractAsynchronousConnection implements ConnectionEventListener { |
| | | /** The wrapped connection. */ |
| | | private final LDAPConnectionImpl connectionImpl; |
| | | |
| | | /** List of pending Bind or StartTLS requests which must be invoked once the current heart beat completes. */ |
| | | private final Queue<Runnable> pendingBindOrStartTLSRequests = new ConcurrentLinkedQueue<>(); |
| | | |
| | | /** |
| | | * List of pending responses for all active operations. These will be signaled if no heart beat is detected |
| | | * within the permitted timeout period. |
| | | */ |
| | | private final Queue<LdapResultHandler<?>> pendingResults = new ConcurrentLinkedQueue<>(); |
| | | |
| | | /** Internal connection state. */ |
| | | private final ConnectionState state = new ConnectionState(); |
| | | |
| | | /** Coordinates heart-beats with Bind and StartTLS requests. */ |
| | | private final Sync sync = new Sync(); |
| | | |
| | | /** Timestamp of last response received (any response, not just heart beats). */ |
| | | private volatile long lastResponseTimestamp = timeService.now(); |
| | | |
| | | private ConnectionImpl(final LDAPConnectionImpl connectionImpl) { |
| | | this.connectionImpl = connectionImpl; |
| | | connectionImpl.addConnectionEventListener(this); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Void> abandonAsync(final AbandonRequest request) { |
| | | return connectionImpl.abandonAsync(request); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Result> addAsync( |
| | | final AddRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | return timestampPromise(connectionImpl.addAsync(request, intermediateResponseHandler)); |
| | | } |
| | | |
| | | @Override |
| | | public void addConnectionEventListener(final ConnectionEventListener listener) { |
| | | state.addConnectionEventListener(listener); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<BindResult> bindAsync( |
| | | final BindRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | if (sync.tryLockShared()) { |
| | | // Fast path |
| | | return timestampBindOrStartTLSPromise(connectionImpl.bindAsync(request, intermediateResponseHandler)); |
| | | } |
| | | return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, BindResult, LdapException>() { |
| | | @Override |
| | | public Promise<BindResult, LdapException> apply(Void value) throws LdapException { |
| | | return timestampBindOrStartTLSPromise(connectionImpl.bindAsync(request, |
| | | intermediateResponseHandler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | handleConnectionClosed(); |
| | | connectionImpl.close(); |
| | | } |
| | | |
| | | @Override |
| | | public void close(final UnbindRequest request, final String reason) { |
| | | handleConnectionClosed(); |
| | | connectionImpl.close(request, reason); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<CompareResult> compareAsync( |
| | | final CompareRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | return timestampPromise(connectionImpl.compareAsync(request, intermediateResponseHandler)); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Result> deleteAsync( |
| | | final DeleteRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | return timestampPromise(connectionImpl.deleteAsync(request, intermediateResponseHandler)); |
| | | } |
| | | |
| | | @Override |
| | | public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync( |
| | | final ExtendedRequest<R> request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | if (!isStartTLSRequest(request)) { |
| | | return timestampPromise(connectionImpl.extendedRequestAsync(request, intermediateResponseHandler)); |
| | | } |
| | | if (sync.tryLockShared()) { |
| | | // Fast path |
| | | return timestampBindOrStartTLSPromise( |
| | | connectionImpl.extendedRequestAsync(request, intermediateResponseHandler)); |
| | | } |
| | | return enqueueBindOrStartTLSPromise(new AsyncFunction<Void, R, LdapException>() { |
| | | @Override |
| | | public Promise<R, LdapException> apply(Void value) throws LdapException { |
| | | return timestampBindOrStartTLSPromise( |
| | | connectionImpl.extendedRequestAsync(request, intermediateResponseHandler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public void handleConnectionClosed() { |
| | | if (state.notifyConnectionClosed()) { |
| | | failPendingResults(newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, |
| | | HBCF_CONNECTION_CLOSED_BY_CLIENT.get())); |
| | | synchronized (validConnections) { |
| | | connectionImpl.removeConnectionEventListener(this); |
| | | validConnections.remove(this); |
| | | if (heartBeatEnabled && validConnections.isEmpty()) { |
| | | // This is the last active connection, so stop the heartbeat. |
| | | heartBeatFuture.cancel(false); |
| | | } |
| | | } |
| | | releaseScheduler(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleConnectionError(final boolean isDisconnectNotification, final LdapException error) { |
| | | if (state.notifyConnectionError(isDisconnectNotification, error)) { |
| | | failPendingResults(error); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleUnsolicitedNotification(final ExtendedResult notification) { |
| | | timestamp(notification); |
| | | state.notifyUnsolicitedNotification(notification); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isClosed() { |
| | | return state.isClosed(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isValid() { |
| | | return state.isValid() && connectionImpl.isValid(); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Result> modifyAsync( |
| | | final ModifyRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | return timestampPromise(connectionImpl.modifyAsync(request, intermediateResponseHandler)); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Result> modifyDNAsync( |
| | | final ModifyDNRequest request, final IntermediateResponseHandler intermediateResponseHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | return timestampPromise(connectionImpl.modifyDNAsync(request, intermediateResponseHandler)); |
| | | } |
| | | |
| | | @Override |
| | | public void removeConnectionEventListener(final ConnectionEventListener listener) { |
| | | state.removeConnectionEventListener(listener); |
| | | } |
| | | |
| | | @Override |
| | | public LdapPromise<Result> searchAsync( |
| | | final SearchRequest request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final SearchResultHandler searchHandler) { |
| | | if (hasConnectionErrorOccurred()) { |
| | | return newConnectionErrorPromise(); |
| | | } |
| | | |
| | | final AtomicBoolean searchDone = new AtomicBoolean(); |
| | | final SearchResultHandler entryHandler = new SearchResultHandler() { |
| | | @Override |
| | | public synchronized boolean handleEntry(SearchResultEntry entry) { |
| | | if (!searchDone.get()) { |
| | | timestamp(entry); |
| | | if (searchHandler != null) { |
| | | searchHandler.handleEntry(entry); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public synchronized boolean handleReference(SearchResultReference reference) { |
| | | if (!searchDone.get()) { |
| | | timestamp(reference); |
| | | if (searchHandler != null) { |
| | | searchHandler.handleReference(reference); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | }; |
| | | return timestampPromise(connectionImpl.searchAsync(request, intermediateResponseHandler, entryHandler) |
| | | .thenOnResultOrException(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | searchDone.getAndSet(true); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return connectionImpl.toString(); |
| | | } |
| | | |
| | | private void checkForHeartBeat() { |
| | | if (sync.isHeld()) { |
| | | /* |
| | | * A heart beat or bind/startTLS is still in progress, but it should have completed by now. Let's |
| | | * avoid aggressively terminating the connection, because the heart beat may simply have been delayed |
| | | * by a sudden surge of activity. Therefore, only flag the connection as failed if no activity has been |
| | | * seen on the connection since the heart beat was sent. |
| | | */ |
| | | final long currentTimeMillis = timeService.now(); |
| | | if (lastResponseTimestamp < (currentTimeMillis - heartBeatTimeoutMS)) { |
| | | logger.warn(LocalizableMessage.raw("No heartbeat detected for connection '%s'", connectionImpl)); |
| | | handleConnectionError(false, newHeartBeatTimeoutError()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean hasConnectionErrorOccurred() { |
| | | return state.getConnectionError() != null; |
| | | } |
| | | |
| | | private <R extends Result> LdapPromise<R> enqueueBindOrStartTLSPromise( |
| | | AsyncFunction<Void, R, LdapException> doRequest) { |
| | | /* |
| | | * A heart beat must be in progress so create a runnable task which will be executed when the heart beat |
| | | * completes. |
| | | */ |
| | | final LdapPromiseImpl<Void> promise = newLdapPromiseImpl(); |
| | | final LdapPromise<R> result = promise.thenAsync(doRequest); |
| | | |
| | | // Enqueue and flush if the heart beat has completed in the mean time. |
| | | pendingBindOrStartTLSRequests.offer(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | // FIXME: Handle cancel chaining. |
| | | if (!result.isCancelled()) { |
| | | sync.lockShared(); // Will not block. |
| | | promise.handleResult(null); |
| | | } |
| | | } |
| | | }); |
| | | flushPendingBindOrStartTLSRequests(); |
| | | return result; |
| | | } |
| | | |
| | | private void failPendingResults(final LdapException error) { |
| | | // Peek instead of pool because notification is responsible for removing the element from the queue. |
| | | LdapResultHandler<?> pendingResult; |
| | | while ((pendingResult = pendingResults.peek()) != null) { |
| | | pendingResult.handleException(error); |
| | | } |
| | | } |
| | | |
| | | private void flushPendingBindOrStartTLSRequests() { |
| | | if (!pendingBindOrStartTLSRequests.isEmpty()) { |
| | | /* |
| | | * The pending requests will acquire the shared lock, but we take it here anyway to ensure that |
| | | * pending requests do not get blocked. |
| | | */ |
| | | if (sync.tryLockShared()) { |
| | | try { |
| | | Runnable pendingRequest; |
| | | while ((pendingRequest = pendingBindOrStartTLSRequests.poll()) != null) { |
| | | // Dispatch the waiting request. This will not block. |
| | | pendingRequest.run(); |
| | | } |
| | | } finally { |
| | | sync.unlockShared(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean isStartTLSRequest(final ExtendedRequest<?> request) { |
| | | return request.getOID().equals(StartTLSExtendedRequest.OID); |
| | | } |
| | | |
| | | private <R> LdapPromise<R> newConnectionErrorPromise() { |
| | | return newFailedLdapPromise(state.getConnectionError()); |
| | | } |
| | | |
| | | private void releaseBindOrStartTLSLock() { |
| | | sync.unlockShared(); |
| | | } |
| | | |
| | | private void releaseHeartBeatLock() { |
| | | sync.unlockExclusively(); |
| | | flushPendingBindOrStartTLSRequests(); |
| | | } |
| | | |
| | | /** |
| | | * Sends a heart beat on this connection if required to do so. |
| | | * |
| | | * @return {@code true} if a heart beat was sent, otherwise {@code false}. |
| | | */ |
| | | private boolean sendHeartBeat() { |
| | | // Don't attempt to send a heart beat if the connection has already failed. |
| | | if (!state.isValid()) { |
| | | return false; |
| | | } |
| | | |
| | | // Only send the heart beat if the connection has been idle for some time. |
| | | final long currentTimeMillis = timeService.now(); |
| | | if (currentTimeMillis < (lastResponseTimestamp + heartBeatDelayMS)) { |
| | | 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 { |
| | | connectionImpl.searchAsync(heartBeatRequest, 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; |
| | | } |
| | | }).thenOnResult(new org.forgerock.util.promise.ResultHandler<Result>() { |
| | | @Override |
| | | public void handleResult(Result result) { |
| | | timestamp(result); |
| | | releaseHeartBeatLock(); |
| | | } |
| | | }).thenOnException(new ExceptionHandler<LdapException>() { |
| | | @Override |
| | | public void handleException(LdapException exception) { |
| | | /* |
| | | * 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 (!(exception 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'", |
| | | LDAPConnectionFactory.this, |
| | | exception)); |
| | | timestamp(exception); |
| | | } |
| | | releaseHeartBeatLock(); |
| | | } |
| | | }); |
| | | } catch (final IllegalStateException e) { |
| | | /* |
| | | * This may happen when we attempt to send the heart beat just after the connection is closed but |
| | | * before we are notified. Release the lock because we're never going to get a response. |
| | | */ |
| | | releaseHeartBeatLock(); |
| | | } |
| | | } |
| | | /* |
| | | * Indicate that a the heartbeat should be checked even if a bind/startTLS is in progress, since these |
| | | * operations will effectively act as the heartbeat. |
| | | */ |
| | | return true; |
| | | } |
| | | |
| | | private <R> R timestamp(final R response) { |
| | | if (!(response instanceof ConnectionException)) { |
| | | lastResponseTimestamp = timeService.now(); |
| | | } |
| | | return response; |
| | | } |
| | | |
| | | private <R extends Result> LdapPromise<R> timestampBindOrStartTLSPromise(LdapPromise<R> wrappedPromise) { |
| | | return timestampPromise(wrappedPromise).thenOnResultOrException(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | releaseBindOrStartTLSLock(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private <R extends Result> LdapPromise<R> timestampPromise(LdapPromise<R> wrappedPromise) { |
| | | final LdapPromiseImpl<R> outerPromise = new LdapPromiseImplWrapper<>(wrappedPromise); |
| | | pendingResults.add(outerPromise); |
| | | wrappedPromise.thenOnResult(new ResultHandler<R>() { |
| | | @Override |
| | | public void handleResult(R result) { |
| | | outerPromise.handleResult(result); |
| | | timestamp(result); |
| | | } |
| | | }).thenOnException(new ExceptionHandler<LdapException>() { |
| | | @Override |
| | | public void handleException(LdapException exception) { |
| | | outerPromise.handleException(exception); |
| | | timestamp(exception); |
| | | } |
| | | }); |
| | | outerPromise.thenOnResultOrException(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | pendingResults.remove(outerPromise); |
| | | } |
| | | }); |
| | | if (hasConnectionErrorOccurred()) { |
| | | outerPromise.handleException(state.getConnectionError()); |
| | | } |
| | | return outerPromise; |
| | | } |
| | | |
| | | private class LdapPromiseImplWrapper<R> extends LdapPromiseImpl<R> { |
| | | protected LdapPromiseImplWrapper(final LdapPromise<R> wrappedPromise) { |
| | | super(new PromiseImpl<R, LdapException>() { |
| | | @Override |
| | | protected LdapException tryCancel(boolean mayInterruptIfRunning) { |
| | | /* |
| | | * FIXME: if the inner cancel succeeds then this promise will be completed and we can never |
| | | * indicate that this cancel request has succeeded. |
| | | */ |
| | | wrappedPromise.cancel(mayInterruptIfRunning); |
| | | return null; |
| | | } |
| | | }, wrappedPromise.getRequestID()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | * Specifies the maximum queue length for incoming connections requests. If a |
| | | * connection request arrives when the queue is full, the connection is refused. |
| | | */ |
| | | public static final Option<Integer> BACKLOG = Option.withDefault(50); |
| | | public static final Option<Integer> CONNECT_MAX_BACKLOG = Option.withDefault(50); |
| | | |
| | | /** |
| | | * Specifies the maximum request size in bytes for incoming LDAP requests. |
| | | * If an incoming request exceeds the limit then the connection will be aborted by the listener. |
| | | * Default value is 5MiB. |
| | | */ |
| | | public static final Option<Integer> MAX_REQUEST_SIZE_BYTES = Option.withDefault(5 * 1024 * 1024); |
| | | public static final Option<Integer> REQUEST_MAX_SIZE_IN_BYTES = Option.withDefault(5 * 1024 * 1024); |
| | | |
| | | /** |
| | | * We implement the factory using the pimpl idiom in order have |
| | |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(factory, options); |
| | | final InetSocketAddress address = new InetSocketAddress(port); |
| | | this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER), |
| | | options.get(PROVIDER_CLASS_LOADER)); |
| | | this.impl = provider.getLDAPListener(address, factory, options); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(new InetSocketAddress(port), factory, options); |
| | | } |
| | | |
| | | /** |
| | |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(address, factory, options); |
| | | this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER), |
| | | options.get(PROVIDER_CLASS_LOADER)); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(address, factory, options); |
| | | } |
| | | |
| | |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(host, factory, options); |
| | | final InetSocketAddress address = new InetSocketAddress(host, port); |
| | | this.provider = getProvider(TransportProvider.class, options.get(TRANSPORT_PROVIDER), |
| | | options.get(PROVIDER_CLASS_LOADER)); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(address, factory, options); |
| | | } |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | |
| | | StartTLSExtendedRequest addEnabledCipherSuite(String... suites); |
| | | |
| | | /** |
| | | * Adds the cipher suites enabled for secure connections with the Directory |
| | | * Server. The suites must be supported by the SSLContext specified in |
| | | * {@link #setSSLContext(SSLContext)}. Following a successful call to this |
| | | * method, only the suites listed in the protocols parameter are enabled for |
| | | * use. |
| | | * |
| | | * @param suites |
| | | * Names of all the suites to enable. |
| | | * @return A reference to this LDAP connection options. |
| | | * @throws UnsupportedOperationException |
| | | * If this start TLS extended request does not permit the |
| | | * enabled cipher suites to be set. |
| | | */ |
| | | StartTLSExtendedRequest addEnabledCipherSuite(Collection<String> suites); |
| | | |
| | | /** |
| | | * Adds the protocol versions enabled for secure connections with the |
| | | * Directory Server. The protocols must be supported by the SSLContext |
| | | * specified in {@link #setSSLContext(SSLContext)}. Following a successful |
| | |
| | | */ |
| | | StartTLSExtendedRequest addEnabledProtocol(String... protocols); |
| | | |
| | | /** |
| | | * Adds the protocol versions enabled for secure connections with the |
| | | * Directory Server. The protocols must be supported by the SSLContext |
| | | * specified in {@link #setSSLContext(SSLContext)}. Following a successful |
| | | * call to this method, only the protocols listed in the protocols parameter |
| | | * are enabled for use. |
| | | * |
| | | * @param protocols |
| | | * Names of all the protocols to enable. |
| | | * @return A reference to this LDAP connection options. |
| | | * @throws UnsupportedOperationException |
| | | * If this start TLS extended request does not permit the |
| | | * enabled protocols to be set. |
| | | */ |
| | | StartTLSExtendedRequest addEnabledProtocol(Collection<String> protocols); |
| | | |
| | | @Override |
| | | <C extends Control> C getControl(ControlDecoder<C> decoder, DecodeOptions options) |
| | | throws DecodeException; |
| | |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | |
| | |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledCipherSuite(final String... suites) { |
| | | return addEnabledCipherSuite(Arrays.asList(suites)); |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledCipherSuite(final Collection<String> suites) { |
| | | for (final String suite : suites) { |
| | | this.enabledCipherSuites.add(Reject.checkNotNull(suite)); |
| | | } |
| | |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledProtocol(final String... protocols) { |
| | | return addEnabledProtocol(Arrays.asList(protocols)); |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledProtocol(final Collection<String> protocols) { |
| | | for (final String protocol : protocols) { |
| | | this.enabledProtocols.add(Reject.checkNotNull(protocol)); |
| | | } |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledCipherSuite(final Collection<String> suites) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledCipherSuite(final String... suites) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledProtocol(final Collection<String> protocols) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public StartTLSExtendedRequest addEnabledProtocol(final String... protocols) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | |
| | | * |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2014 ForgeRock AS. |
| | | * Portions copyright 2011-2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | |
| | | public final class BindResultLdapPromiseImpl extends ResultLdapPromiseImpl<BindRequest, BindResult> { |
| | | private final BindClient bindClient; |
| | | |
| | | BindResultLdapPromiseImpl(final int requestID, final BindRequest request, final BindClient bindClient, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | super(new PromiseImpl<BindResult, LdapException>() { |
| | | protected LdapException tryCancel(boolean mayInterruptIfRunning) { |
| | | /* |
| | | * No other operations can be performed while a bind is active. |
| | | * Therefore it is not possible to cancel bind or requests, |
| | | * since doing so will leave the connection in a state which |
| | | * prevents other operations from being performed. |
| | | */ |
| | | return null; |
| | | } |
| | | }, requestID, request, intermediateResponseHandler, connection); |
| | | BindResultLdapPromiseImpl( |
| | | final PromiseImpl<BindResult, LdapException> impl, |
| | | final int requestID, |
| | | final BindRequest request, |
| | | final BindClient bindClient, |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | super(impl, requestID, request, intermediateResponseHandler); |
| | | this.bindClient = bindClient; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isBindOrStartTLS() { |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Returns the bind client. |
| | | * |
| | |
| | | } |
| | | |
| | | @Override |
| | | BindResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage, |
| | | final Throwable cause) { |
| | | return Responses.newBindResult(resultCode).setDiagnosticMessage(diagnosticMessage) |
| | | .setCause(cause); |
| | | public boolean isBindOrStartTLS() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | BindResult newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) { |
| | | return Responses.newBindResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause); |
| | | } |
| | | } |
| | |
| | | * |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2014 ForgeRock AS. |
| | | * Portions copyright 2011-2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.util.promise.PromiseImpl; |
| | | |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | |
| | | /** |
| | | * Extended result promise implementation. |
| | | * |
| | | * @param <S> |
| | | * The type of result returned by this promise. |
| | | * The type of result returned by this promise. |
| | | */ |
| | | public final class ExtendedResultLdapPromiseImpl<S extends ExtendedResult> extends |
| | | ResultLdapPromiseImpl<ExtendedRequest<S>, S> { |
| | | ExtendedResultLdapPromiseImpl(final int requestID, final ExtendedRequest<S> request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | super(new PromiseImpl<S, LdapException>() { |
| | | @Override |
| | | protected LdapException tryCancel(boolean mayInterruptIfRunning) { |
| | | if (!StartTLSExtendedRequest.OID.equals(request.getOID())) { |
| | | connection.abandonAsync(Requests.newAbandonRequest(requestID)); |
| | | return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED); |
| | | } |
| | | ExtendedResultLdapPromiseImpl( |
| | | final PromiseImpl<S, LdapException> impl, |
| | | final int requestID, |
| | | final ExtendedRequest<S> request, |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | super(impl, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | /* |
| | | * No other operations can be performed while a startTLS is active. |
| | | * Therefore it is not possible to cancel startTLS requests, |
| | | * since doing so will leave the connection in a state which |
| | | * prevents other operations from being performed. |
| | | */ |
| | | return null; |
| | | } |
| | | }, requestID, request, intermediateResponseHandler, connection); |
| | | /** |
| | | * Decode an extended result. |
| | | * |
| | | * @param result |
| | | * Extended result to decode. |
| | | * @param options |
| | | * Decoding options. |
| | | * @return The decoded extended result. |
| | | * @throws DecodeException |
| | | * If a problem occurs during decoding. |
| | | */ |
| | | public S decodeResult(final ExtendedResult result, final DecodeOptions options) throws DecodeException { |
| | | return getRequest().getResultDecoder().decodeExtendedResult(result, options); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return StartTLSExtendedRequest.OID.equals(getRequest().getOID()); |
| | | } |
| | | |
| | | /** |
| | | * Decode an extended result. |
| | | * |
| | | * @param result |
| | | * Extended result to decode. |
| | | * @param options |
| | | * Decoding options. |
| | | * @return The decoded extended result. |
| | | * @throws DecodeException |
| | | * If a problem occurs during decoding. |
| | | */ |
| | | public S decodeResult(final ExtendedResult result, final DecodeOptions options) throws DecodeException { |
| | | return getRequest().getResultDecoder().decodeExtendedResult(result, options); |
| | | } |
| | | |
| | | @Override |
| | | S newErrorResult(final ResultCode resultCode, final String diagnosticMessage, |
| | | final Throwable cause) { |
| | | S newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) { |
| | | return getRequest().getResultDecoder().newExtendedErrorResult(resultCode, "", diagnosticMessage); |
| | | } |
| | | } |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2013-2014 ForgeRock AS. |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.Closeable; |
| | | import java.net.InetSocketAddress; |
| | | |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.util.promise.Promise; |
| | | |
| | | /** |
| | | * Interface for all classes that actually implement |
| | | * {@code LDAPConnectionFactory}. |
| | | * Interface for all classes that implement {@code LDAPConnectionFactory}. |
| | | * <p> |
| | | * An implementation class is provided by a {@code TransportProvider}. |
| | | * <p> |
| | |
| | | * {@code TransportProvider} is declared in the provider-configuration file |
| | | * {@code META-INF/services/org.forgerock.opendj.ldap.spi.TransportProvider}. |
| | | */ |
| | | public interface LDAPConnectionFactoryImpl extends ConnectionFactory { |
| | | public interface LDAPConnectionFactoryImpl extends Closeable { |
| | | |
| | | /** |
| | | * Returns the address used by the connections created by this factory. |
| | |
| | | String getHostName(); |
| | | |
| | | /** |
| | | * Returns the remote port number used by the connections created by this |
| | | * factory. |
| | | * Returns the remote port number used by the connections created by this factory. |
| | | * |
| | | * @return The remote port number used by the connections. |
| | | */ |
| | | int getPort(); |
| | | |
| | | /** |
| | | * Releases any resources associated with this connection factory implementation. |
| | | */ |
| | | @Override |
| | | void close(); |
| | | |
| | | /** |
| | | * Asynchronously obtains a connection to the Directory Server associated |
| | | * with this connection factory. The returned {@code Promise} can be used to |
| | | * retrieve the completed connection. |
| | | * |
| | | * @return A promise which can be used to retrieve the connection. |
| | | */ |
| | | Promise<LDAPConnectionImpl, LdapException> getConnectionAsync(); |
| | | } |
| 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 2015 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.Closeable; |
| | | import java.util.List; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | |
| | | import org.forgerock.opendj.ldap.ConnectionEventListener; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapPromise; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.DeleteRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | 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.util.promise.Promise; |
| | | |
| | | /** |
| | | * LDAP connection interface which implementations of {@link LDAPConnectionFactoryImpl} should implement. |
| | | */ |
| | | public interface LDAPConnectionImpl extends Closeable { |
| | | |
| | | /** |
| | | * Abandons the unfinished operation identified in the provided abandon request. |
| | | * |
| | | * @param request |
| | | * The request identifying the operation to be abandoned. |
| | | * @return A promise whose result is Void. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support abandon operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#abandonAsync(AbandonRequest) |
| | | */ |
| | | LdapPromise<Void> abandonAsync(AbandonRequest request); |
| | | |
| | | /** |
| | | * Asynchronously adds an entry to the Directory Server using the provided add request. |
| | | * |
| | | * @param request |
| | | * The add request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support add operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#addAsync(AddRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Registers the provided connection event listener so that it will be notified when this connection is closed by |
| | | * the application, receives an unsolicited notification, or experiences a fatal error. |
| | | * |
| | | * @param listener |
| | | * The listener which wants to be notified when events occur on this connection. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If the {@code listener} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#addConnectionEventListener(ConnectionEventListener) |
| | | */ |
| | | void addConnectionEventListener(ConnectionEventListener listener); |
| | | |
| | | /** |
| | | * Asynchronously authenticates to the Directory Server using the provided bind request. |
| | | * |
| | | * @param request |
| | | * The bind request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support bind operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#bindAsync(BindRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<BindResult> bindAsync(BindRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Releases any resources associated with this connection. |
| | | * <p/> |
| | | * Calling {@code close} on a connection that is already closed has no effect. |
| | | * @see org.forgerock.opendj.ldap.Connection#close() |
| | | */ |
| | | @Override |
| | | void close(); |
| | | |
| | | /** |
| | | * Releases any resources associated with this connection. |
| | | * <p/> |
| | | * Calling {@code close} on a connection that is already closed has no effect. |
| | | * |
| | | * @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. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#close(UnbindRequest, String) |
| | | */ |
| | | void close(UnbindRequest request, String reason); |
| | | |
| | | /** |
| | | * Asynchronously compares an entry in the Directory Server using the provided compare request. |
| | | * |
| | | * @param request |
| | | * The compare request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support compare operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#compareAsync(CompareRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<CompareResult> compareAsync( |
| | | CompareRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Asynchronously deletes an entry from the Directory Server using the provided delete request. |
| | | * |
| | | * @param request |
| | | * The delete request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support delete operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#deleteAsync(DeleteRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<Result> deleteAsync(DeleteRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Installs the TLS/SSL security layer on the underlying connection. The TLS/SSL security layer will be installed |
| | | * beneath any existing connection security layers and can only be installed at most once. |
| | | * |
| | | * @param sslContext |
| | | * The {@code SSLContext} which should be used to secure the |
| | | * @param protocols |
| | | * Names of all the protocols to enable or {@code null} to use the default protocols. |
| | | * @param suites |
| | | * Names of all the suites to enable or {@code null} to use the default cipher suites. |
| | | * @return A promise which will complete once the SSL handshake has completed. |
| | | * @throws IllegalStateException |
| | | * If the TLS/SSL security layer has already been installed. |
| | | */ |
| | | Promise<Void, LdapException> enableTLS(SSLContext sslContext, List<String> protocols, List<String> suites); |
| | | |
| | | /** |
| | | * Asynchronously performs the provided extended request in the Directory Server. |
| | | * |
| | | * @param <R> |
| | | * The type of result returned by the extended request. |
| | | * @param request |
| | | * The extended request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support extended operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#extendedRequestAsync(ExtendedRequest, IntermediateResponseHandler) |
| | | */ |
| | | <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync( |
| | | ExtendedRequest<R> request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Indicates whether or not this connection has been explicitly closed by calling {@code close}. This method will |
| | | * not return {@code true} if a fatal error has occurred on the connection unless {@code close} has been called. |
| | | * |
| | | * @return {@code true} if this connection has been explicitly closed by calling {@code close}, or {@code false} |
| | | * otherwise. |
| | | * @see org.forgerock.opendj.ldap.Connection#isClosed() |
| | | */ |
| | | boolean isClosed(); |
| | | |
| | | /** |
| | | * Returns {@code true} if this connection has not been closed and no fatal errors have been detected. This method |
| | | * is guaranteed to return {@code false} only when it is called after the method {@code close} has been called. |
| | | * |
| | | * @return {@code true} if this connection is valid, {@code false} otherwise. |
| | | * @see org.forgerock.opendj.ldap.Connection#isValid() |
| | | */ |
| | | boolean isValid(); |
| | | |
| | | /** |
| | | * Asynchronously modifies an entry in the Directory Server using the provided modify request. |
| | | * |
| | | * @param request |
| | | * The modify request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support modify operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#modifyAsync(ModifyRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<Result> modifyAsync(ModifyRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Asynchronously renames an entry in the Directory Server using the provided modify DN request. |
| | | * |
| | | * @param request |
| | | * The modify DN request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support modify DN operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#modifyDNAsync(ModifyDNRequest, IntermediateResponseHandler) |
| | | */ |
| | | LdapPromise<Result> modifyDNAsync( |
| | | ModifyDNRequest request, IntermediateResponseHandler intermediateResponseHandler); |
| | | |
| | | /** |
| | | * Removes the provided connection event listener from this connection so that it will no longer be notified when |
| | | * this connection is closed by the application, receives an unsolicited notification, or experiences a fatal |
| | | * error. |
| | | * |
| | | * @param listener |
| | | * The listener which no longer wants to be notified when events occur on this connection. |
| | | * @throws NullPointerException |
| | | * If the {@code listener} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#removeConnectionEventListener(ConnectionEventListener) |
| | | */ |
| | | void removeConnectionEventListener(ConnectionEventListener listener); |
| | | |
| | | /** |
| | | * Asynchronously searches the Directory Server using the provided search request. |
| | | * |
| | | * @param request |
| | | * The search request. |
| | | * @param intermediateResponseHandler |
| | | * An intermediate response handler which can be used to process any intermediate responses as they are |
| | | * received, may be {@code null}. |
| | | * @param entryHandler |
| | | * A search result handler which can be used to asynchronously process the search result entries and |
| | | * references as they are received, may be {@code null}. |
| | | * @return A promise representing the result of the operation. |
| | | * @throws UnsupportedOperationException |
| | | * If this connection does not support search operations. |
| | | * @throws IllegalStateException |
| | | * If this connection has already been closed, i.e. if {@code isClosed() == true}. |
| | | * @throws NullPointerException |
| | | * If {@code request} was {@code null}. |
| | | * @see org.forgerock.opendj.ldap.Connection#searchAsync(SearchRequest, IntermediateResponseHandler, |
| | | * SearchResultHandler) |
| | | */ |
| | | LdapPromise<Result> searchAsync( |
| | | SearchRequest request, |
| | | IntermediateResponseHandler intermediateResponseHandler, |
| | | SearchResultHandler entryHandler); |
| | | } |
| | |
| | | this.requestID = requestID; |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | @Override |
| | | public int getRequestID() { |
| | | return wrappedPromise instanceof LdapPromise ? ((LdapPromise<R>) wrappedPromise).getRequestID() |
| | |
| | | } |
| | | |
| | | @Override |
| | | // @Checkstyle:ignore |
| | | public LdapPromise<R> thenFinally(Runnable onResultOrException) { |
| | | wrappedPromise.thenFinally(onResultOrException); |
| | | return this; |
| | |
| | | // @Checkstyle:ignore |
| | | public <EOUT extends Exception> Promise<R, EOUT> thenCatchAsync( |
| | | AsyncFunction<? super LdapException, R, EOUT> onException) { |
| | | return null; |
| | | return wrappedPromise.thenCatchAsync(onException); |
| | | } |
| | | |
| | | public P getWrappedPromise() { |
| | |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newCompareResult; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newResult; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.Request; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest; |
| | | import org.forgerock.opendj.ldap.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.util.promise.Promise; |
| | | import org.forgerock.util.promise.PromiseImpl; |
| | | import org.forgerock.util.promise.Promises; |
| | | |
| | | import static org.forgerock.opendj.ldap.responses.Responses.*; |
| | | |
| | | /** |
| | | * Utility methods for creating and composing {@link LdapPromise}s. |
| | | */ |
| | | public final class LdapPromises { |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task which has |
| | | * already succeeded with the provided result. Attempts to get the result |
| | | * will immediately return the result. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task |
| | | * does not return anything (i.e. it only has side-effects). |
| | | * @param result |
| | | * The result of the asynchronous task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has |
| | | * already succeeded with the provided result. |
| | | */ |
| | | public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result) { |
| | | return wrap(Promises.<R, LdapException> newResultPromise(result), -1); |
| | | private LdapPromises() { |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task, |
| | | * identified by the provided requestID, which has already succeeded with |
| | | * the provided result. Attempts to get the result will immediately return |
| | | * the result. |
| | | * Converts a {@link Promise} to a {@link LdapPromise}. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task |
| | | * does not return anything (i.e. it only has side-effects). |
| | | * @param result |
| | | * The result of the asynchronous task. |
| | | * @param requestID |
| | | * The request ID of the succeeded task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has |
| | | * already succeeded with the provided result. |
| | | * The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has |
| | | * side-effects). |
| | | * @param wrappedPromise |
| | | * The {@link Promise} to wrap. |
| | | * @return A {@link LdapPromise} representing the same asynchronous task as the {@link Promise} provided. |
| | | */ |
| | | public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result, int requestID) { |
| | | return wrap(Promises.<R, LdapException> newResultPromise(result), requestID); |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task which has |
| | | * already failed with the provided error. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task |
| | | * does not return anything (i.e. it only has side-effects). |
| | | * @param <E> |
| | | * The type of the exception thrown by the task if it fails. |
| | | * @param error |
| | | * The exception indicating why the asynchronous task has failed. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has |
| | | * already failed with the provided error. |
| | | */ |
| | | public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error) { |
| | | return wrap(Promises.<R, LdapException> newExceptionPromise(error), -1); |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task, |
| | | * identified by the provided requestID, which has already failed with the |
| | | * provided error. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task |
| | | * does not return anything (i.e. it only has side-effects). |
| | | * @param <E> |
| | | * The type of the exception thrown by the task if it fails. |
| | | * @param error |
| | | * The exception indicating why the asynchronous task has failed. |
| | | * @param requestID |
| | | * The request ID of the failed task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has |
| | | * already failed with the provided error. |
| | | */ |
| | | public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error, int requestID) { |
| | | return wrap(Promises.<R, LdapException> newExceptionPromise(error), requestID); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new {@link ResultLdapPromiseImpl} to handle a standard request (add, delete, modify and modidyDN). |
| | | * |
| | | * @param <R> |
| | | * The type of the task's request. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * |
| | | * @return The new {@link ResultLdapPromiseImpl}. |
| | | */ |
| | | public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise(final int requestID, |
| | | final R request, final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | return new ResultLdapPromiseImpl<R, Result>(requestID, request, intermediateResponseHandler, connection) { |
| | | @Override |
| | | protected Result newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) { |
| | | return newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause); |
| | | } |
| | | }; |
| | | public static <R> LdapPromise<R> asPromise(Promise<R, LdapException> wrappedPromise) { |
| | | return wrap(wrappedPromise, -1); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new bind {@link BindResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The bind request sent to server. |
| | | * The bind request sent to server. |
| | | * @param bindClient |
| | | * Client that binds to the server. |
| | | * Client that binds to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * |
| | | * The connection to directory server. |
| | | * @return The new {@link BindResultLdapPromiseImpl}. |
| | | */ |
| | | public static BindResultLdapPromiseImpl newBindLdapPromise(final int requestID, |
| | | final BindRequest request, final BindClient bindClient, |
| | | final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) { |
| | | return new BindResultLdapPromiseImpl(requestID, request, bindClient, intermediateResponseHandler, connection); |
| | | public static BindResultLdapPromiseImpl newBindLdapPromise( |
| | | final int requestID, |
| | | final BindRequest request, |
| | | final BindClient bindClient, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | return new BindResultLdapPromiseImpl(LdapPromises.<BindResult>newInnerBindOrStartTLSPromise(), |
| | | requestID, |
| | | request, |
| | | bindClient, |
| | | intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new bind {@link BindResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The bind request sent to server. |
| | | * @param bindClient |
| | | * Client that binds to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link BindResultLdapPromiseImpl}. |
| | | */ |
| | | public static BindResultLdapPromiseImpl newBindLdapPromise( |
| | | final int requestID, |
| | | final BindRequest request, |
| | | final BindClient bindClient, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final LDAPConnectionImpl connection) { |
| | | return new BindResultLdapPromiseImpl(LdapPromises.<BindResult>newInnerBindOrStartTLSPromise(), |
| | | requestID, |
| | | request, |
| | | bindClient, |
| | | intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new compare {@link ResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The compare request sent to the server. |
| | | * The compare request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * |
| | | * The connection to directory server. |
| | | * @return The new {@link ResultLdapPromiseImpl}. |
| | | */ |
| | | public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise(final int requestID, |
| | | final CompareRequest request, final IntermediateResponseHandler intermediateResponseHandler, |
| | | public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise( |
| | | final int requestID, |
| | | final CompareRequest request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | return new ResultLdapPromiseImpl<CompareRequest, CompareResult>(requestID, request, intermediateResponseHandler, |
| | | connection) { |
| | | final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID); |
| | | return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new compare {@link ResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The compare request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link ResultLdapPromiseImpl}. |
| | | */ |
| | | public static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise( |
| | | final int requestID, |
| | | final CompareRequest request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final LDAPConnectionImpl connection) { |
| | | final PromiseImpl<CompareResult, LdapException> innerPromise = newInnerPromise(connection, requestID); |
| | | return newCompareLdapPromise(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | private static ResultLdapPromiseImpl<CompareRequest, CompareResult> newCompareLdapPromise( |
| | | final PromiseImpl<CompareResult, LdapException> innerPromise, |
| | | final int requestID, |
| | | final CompareRequest request, |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | return new ResultLdapPromiseImpl<CompareRequest, CompareResult>(innerPromise, |
| | | requestID, |
| | | request, |
| | | intermediateResponseHandler) { |
| | | @Override |
| | | protected CompareResult newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) { |
| | | return newCompareResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause); |
| | |
| | | * Creates a new extended {@link ExtendedResultLdapPromiseImpl}. |
| | | * |
| | | * @param <S> |
| | | * The type of result returned by this promise. |
| | | * The type of result returned by this promise. |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The extended request sent to the server. |
| | | * The extended request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * |
| | | * The connection to directory server. |
| | | * @return The new {@link ExtendedResultLdapPromiseImpl}. |
| | | */ |
| | | public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise( |
| | | final int requestID, final ExtendedRequest<S> request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) { |
| | | return new ExtendedResultLdapPromiseImpl<>(requestID, request, intermediateResponseHandler, connection); |
| | | final int requestID, |
| | | final ExtendedRequest<S> request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | final PromiseImpl<S, LdapException> innerPromise; |
| | | if (!StartTLSExtendedRequest.OID.equals(request.getOID())) { |
| | | innerPromise = newInnerBindOrStartTLSPromise(); |
| | | } else { |
| | | innerPromise = newInnerPromise(connection, requestID); |
| | | } |
| | | return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new extended {@link ExtendedResultLdapPromiseImpl}. |
| | | * |
| | | * @param <S> |
| | | * The type of result returned by this promise. |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The extended request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link ExtendedResultLdapPromiseImpl}. |
| | | */ |
| | | public static <S extends ExtendedResult> ExtendedResultLdapPromiseImpl<S> newExtendedLdapPromise( |
| | | final int requestID, |
| | | final ExtendedRequest<S> request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final LDAPConnectionImpl connection) { |
| | | final PromiseImpl<S, LdapException> innerPromise; |
| | | if (!StartTLSExtendedRequest.OID.equals(request.getOID())) { |
| | | innerPromise = newInnerBindOrStartTLSPromise(); |
| | | } else { |
| | | innerPromise = newInnerPromise(connection, requestID); |
| | | } |
| | | return new ExtendedResultLdapPromiseImpl<>(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new {@link ResultLdapPromiseImpl} to handle a standard request (add, delete, modify and modidyDN). |
| | | * |
| | | * @param <R> |
| | | * The type of the task's request. |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link ResultLdapPromiseImpl}. |
| | | */ |
| | | public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise( |
| | | final int requestID, |
| | | final R request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID); |
| | | return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new {@link ResultLdapPromiseImpl} to handle a standard request (add, delete, modify and modidyDN). |
| | | * |
| | | * @param <R> |
| | | * The type of the task's request. |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The request sent to the server. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link ResultLdapPromiseImpl}. |
| | | */ |
| | | public static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise( |
| | | final int requestID, |
| | | final R request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final LDAPConnectionImpl connection) { |
| | | final PromiseImpl<Result, LdapException> innerPromise = newInnerPromise(connection, requestID); |
| | | return newResultLdapPromise(innerPromise, requestID, request, intermediateResponseHandler); |
| | | } |
| | | |
| | | private static <R extends Request> ResultLdapPromiseImpl<R, Result> newResultLdapPromise( |
| | | final PromiseImpl<Result, LdapException> innerPromise, |
| | | final int requestID, |
| | | final R request, |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | return new ResultLdapPromiseImpl<R, Result>(innerPromise, requestID, request, intermediateResponseHandler) { |
| | | @Override |
| | | protected Result newErrorResult(ResultCode resultCode, String diagnosticMessage, Throwable cause) { |
| | | return newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new search {@link SearchResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The search request sent to the server. |
| | | * The search request sent to the server. |
| | | * @param resultHandler |
| | | * Handler that consumes search result. |
| | | * Handler that consumes search result. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * |
| | | * The connection to directory server. |
| | | * @return The new {@link SearchResultLdapPromiseImpl}. |
| | | */ |
| | | public static SearchResultLdapPromiseImpl newSearchLdapPromise(final int requestID, final SearchRequest request, |
| | | final SearchResultHandler resultHandler, final IntermediateResponseHandler intermediateResponseHandler, |
| | | public static SearchResultLdapPromiseImpl newSearchLdapPromise( |
| | | final int requestID, |
| | | final SearchRequest request, |
| | | final SearchResultHandler resultHandler, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | return new SearchResultLdapPromiseImpl(requestID, request, resultHandler, intermediateResponseHandler, |
| | | connection); |
| | | return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID), |
| | | requestID, |
| | | request, |
| | | resultHandler, |
| | | intermediateResponseHandler); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Creates a new search {@link SearchResultLdapPromiseImpl}. |
| | | * |
| | | * @param requestID |
| | | * Identifier of the request. |
| | | * @param request |
| | | * The search request sent to the server. |
| | | * @param resultHandler |
| | | * Handler that consumes search result. |
| | | * @param intermediateResponseHandler |
| | | * Handler that consumes intermediate responses from extended operations. |
| | | * @param connection |
| | | * The connection to directory server. |
| | | * @return The new {@link SearchResultLdapPromiseImpl}. |
| | | */ |
| | | public static SearchResultLdapPromiseImpl newSearchLdapPromise( |
| | | final int requestID, |
| | | final SearchRequest request, |
| | | final SearchResultHandler resultHandler, |
| | | final IntermediateResponseHandler intermediateResponseHandler, |
| | | final LDAPConnectionImpl connection) { |
| | | return new SearchResultLdapPromiseImpl(newInnerPromise(connection, requestID), |
| | | requestID, |
| | | request, |
| | | resultHandler, |
| | | intermediateResponseHandler); |
| | | } |
| | | |
| | | /** |
| | | * Converts a {@link Promise} to a {@link LdapPromise}. |
| | | * Returns a {@link LdapPromise} representing an asynchronous task which has already failed with the provided |
| | | * error. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task |
| | | * does not return anything (i.e. it only has side-effects). |
| | | * @param wrappedPromise |
| | | * The {@link Promise} to wrap. |
| | | * @return A {@link LdapPromise} representing the same asynchronous task as |
| | | * the {@link Promise} provided. |
| | | * The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has |
| | | * side-effects). |
| | | * @param <E> |
| | | * The type of the exception thrown by the task if it fails. |
| | | * @param error |
| | | * The exception indicating why the asynchronous task has failed. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error. |
| | | */ |
| | | public static <R> LdapPromise<R> asPromise(Promise<R, LdapException> wrappedPromise) { |
| | | return wrap(wrappedPromise, -1); |
| | | public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error) { |
| | | return wrap(Promises.<R, LdapException>newExceptionPromise(error), -1); |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has |
| | | * already failed with the provided error. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has |
| | | * side-effects). |
| | | * @param <E> |
| | | * The type of the exception thrown by the task if it fails. |
| | | * @param error |
| | | * The exception indicating why the asynchronous task has failed. |
| | | * @param requestID |
| | | * The request ID of the failed task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has already failed with the provided error. |
| | | */ |
| | | public static <R, E extends LdapException> LdapPromise<R> newFailedLdapPromise(final E error, int requestID) { |
| | | return wrap(Promises.<R, LdapException>newExceptionPromise(error), requestID); |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided |
| | | * result. Attempts to get the result will immediately return the result. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has |
| | | * side-effects). |
| | | * @param result |
| | | * The result of the asynchronous task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided |
| | | * result. |
| | | */ |
| | | public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result) { |
| | | return wrap(Promises.<R, LdapException>newResultPromise(result), -1); |
| | | } |
| | | |
| | | /** |
| | | * Returns a {@link LdapPromise} representing an asynchronous task, identified by the provided requestID, which has |
| | | * already succeeded with the provided result. Attempts to get the result will immediately return the result. |
| | | * |
| | | * @param <R> |
| | | * The type of the task's result, or {@link Void} if the task does not return anything (i.e. it only has |
| | | * side-effects). |
| | | * @param result |
| | | * The result of the asynchronous task. |
| | | * @param requestID |
| | | * The request ID of the succeeded task. |
| | | * @return A {@link LdapPromise} representing an asynchronous task which has already succeeded with the provided |
| | | * result. |
| | | */ |
| | | public static <R> LdapPromise<R> newSuccessfulLdapPromise(final R result, int requestID) { |
| | | return wrap(Promises.<R, LdapException>newResultPromise(result), requestID); |
| | | } |
| | | |
| | | private static <S extends Result> PromiseImpl<S, LdapException> newInnerBindOrStartTLSPromise() { |
| | | return new PromiseImpl<S, LdapException>() { |
| | | @Override |
| | | protected LdapException tryCancel(boolean mayInterruptIfRunning) { |
| | | /* |
| | | * No other operations can be performed while a bind or StartTLS is active. Therefore it is not |
| | | * possible to cancel bind or StartTLS requests, since doing so will leave the connection in a |
| | | * state which prevents other operations from being performed. |
| | | */ |
| | | return null; |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise( |
| | | final Connection connection, final int requestID) { |
| | | return new PromiseImpl<S, LdapException>() { |
| | | @Override |
| | | protected final LdapException tryCancel(final boolean mayInterruptIfRunning) { |
| | | /* |
| | | * This will abandon the request, but will also recursively cancel this future. There is no risk of |
| | | * an infinite loop because the state of this future has already been changed. |
| | | */ |
| | | connection.abandonAsync(Requests.newAbandonRequest(requestID)); |
| | | return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private static <S extends Result> PromiseImpl<S, LdapException> newInnerPromise( |
| | | final LDAPConnectionImpl connection, final int requestID) { |
| | | return new PromiseImpl<S, LdapException>() { |
| | | @Override |
| | | protected final LdapException tryCancel(final boolean mayInterruptIfRunning) { |
| | | /* |
| | | * This will abandon the request, but will also recursively cancel this future. There is no risk of |
| | | * an infinite loop because the state of this future has already been changed. |
| | | */ |
| | | connection.abandonAsync(Requests.newAbandonRequest(requestID)); |
| | | return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | static <R> LdapPromise<R> wrap(Promise<R, LdapException> wrappedPromise, int requestID) { |
| | | return new LdapPromiseWrapper<>(wrappedPromise, requestID); |
| | | } |
| | | |
| | | private LdapPromises() { |
| | | } |
| | | |
| | | } |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2014 ForgeRock AS. |
| | | * Copyright 2014-2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapPromise; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.requests.Request; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.responses.IntermediateResponse; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.util.promise.Promise; |
| | |
| | | private IntermediateResponseHandler intermediateResponseHandler; |
| | | private volatile long timestamp; |
| | | |
| | | ResultLdapPromiseImpl(final int requestID, final R request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) { |
| | | this(new PromiseImpl<S, LdapException>() { |
| | | @Override |
| | | protected final LdapException tryCancel(final boolean mayInterruptIfRunning) { |
| | | /* |
| | | * This will abandon the request, but will also recursively cancel this |
| | | * future. There is no risk of an infinite loop because the state of |
| | | * this future has already been changed. |
| | | */ |
| | | connection.abandonAsync(Requests.newAbandonRequest(requestID)); |
| | | return newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED); |
| | | } |
| | | }, requestID, request, intermediateResponseHandler, connection); |
| | | } |
| | | |
| | | ResultLdapPromiseImpl(final PromiseImpl<S, LdapException> impl, final int requestID, final R request, |
| | | final IntermediateResponseHandler intermediateResponseHandler, final Connection connection) { |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | super(impl, requestID); |
| | | this.request = request; |
| | | this.intermediateResponseHandler = intermediateResponseHandler; |
| | |
| | | * |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2014 ForgeRock AS. |
| | | * Portions copyright 2011-2015 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.controls.ADNotificationRequestControl; |
| | |
| | | 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.PromiseImpl; |
| | | |
| | | /** |
| | | * Search result promise implementation. |
| | |
| | | private SearchResultHandler searchResultHandler; |
| | | private final boolean isPersistentSearch; |
| | | |
| | | SearchResultLdapPromiseImpl(final int requestID, final SearchRequest request, |
| | | final SearchResultHandler resultHandler, final IntermediateResponseHandler intermediateResponseHandler, |
| | | final Connection connection) { |
| | | super(requestID, request, intermediateResponseHandler, connection); |
| | | SearchResultLdapPromiseImpl( |
| | | final PromiseImpl<Result, LdapException> impl, |
| | | final int requestID, |
| | | final SearchRequest request, |
| | | final SearchResultHandler resultHandler, |
| | | final IntermediateResponseHandler intermediateResponseHandler) { |
| | | super(impl, requestID, request, intermediateResponseHandler); |
| | | this.searchResultHandler = resultHandler; |
| | | this.isPersistentSearch = |
| | | request.containsControl(PersistentSearchRequestControl.OID) |
| | | this.isPersistentSearch = request.containsControl(PersistentSearchRequestControl.OID) |
| | | || request.containsControl(ADNotificationRequestControl.OID); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | Result newErrorResult(final ResultCode resultCode, final String diagnosticMessage, |
| | | final Throwable cause) { |
| | | Result newErrorResult(final ResultCode resultCode, final String diagnosticMessage, final Throwable cause) { |
| | | return Responses.newResult(resultCode).setDiagnosticMessage(diagnosticMessage).setCause(cause); |
| | | } |
| | | |
| | |
| | | DOC_SUPPORTED_SUBTYPES_TITLE=Supported Language Subtypes |
| | | DOC_SUPPORTED_SUBTYPES_INDEXTERM=Language subtypes |
| | | DOC_LANGUAGE_SH=Serbo-Croatian |
| | | |
| | | LDAP_CONNECTION_CONNECT_TIMEOUT=The connection attempt to server %s has failed \ |
| | | because the connection timeout period of %d ms was exceeded |
| | |
| | | package org.forgerock.opendj.io; |
| | | |
| | | import static org.fest.assertions.Assertions.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.DECODE_OPTIONS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS; |
| | | |
| | | import java.io.IOException; |
| | | |
| | |
| | | ExtendedRequest<R> request) throws DecodeException, IOException { |
| | | CancelExtendedRequest cancelRequest = |
| | | CancelExtendedRequest.DECODER.decodeExtendedRequest(request, |
| | | Options.defaultOptions().get(DECODE_OPTIONS)); |
| | | Options.defaultOptions().get(LDAP_DECODE_OPTIONS)); |
| | | assertThat(cancelRequest.getOID()).isEqualTo(oidCancel); |
| | | assertThat(cancelRequest.getRequestID()).isEqualTo(requestID); |
| | | } |
| | |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import static org.fest.assertions.Assertions.assertThat; |
| | | import static org.fest.assertions.Fail.fail; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.ResultCode.SUCCESS; |
| | | import static org.forgerock.opendj.ldap.SearchScope.BASE_OBJECT; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.mockTimeService; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newModifyRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newBindResult; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newResult; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.newLdapPromiseImpl; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newBindLdapPromise; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newFailedLdapPromise; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newSearchLdapPromise; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.any; |
| | | import static org.mockito.Matchers.anyInt; |
| | | import static org.mockito.Matchers.anyString; |
| | | import static org.mockito.Matchers.same; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.logging.Level; |
| | | |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl; |
| | | import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.TransportProvider; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.promise.ExceptionHandler; |
| | | import org.forgerock.util.promise.NeverThrowsException; |
| | | import org.forgerock.util.promise.Promise; |
| | |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import static org.fest.assertions.Assertions.*; |
| | | import static org.fest.assertions.Fail.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.forgerock.opendj.ldap.ResultCode.*; |
| | | import static org.forgerock.opendj.ldap.SearchScope.*; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.*; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.*; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.*; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | /** |
| | | * Tests the connection pool implementation.. |
| | | */ |
| | |
| | | |
| | | // @formatter:on |
| | | |
| | | private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT, |
| | | "(objectclass=*)", "1.1"); |
| | | private static final SearchRequest HEARTBEAT = newSearchRequest("dc=test", BASE_OBJECT, "(objectclass=*)", "1.1"); |
| | | |
| | | private Connection connection; |
| | | private ConnectionFactory factory; |
| | | private LDAPConnectionImpl ldapConnection; |
| | | private LDAPConnectionFactoryImpl ldapFactory; |
| | | private Connection hbc; |
| | | private HeartBeatConnectionFactory hbcf; |
| | | private LDAPConnectionFactory hbcf; |
| | | private List<ConnectionEventListener> listeners; |
| | | private MockScheduler scheduler; |
| | | |
| | |
| | | assertThat(scheduler.isScheduled()).isFalse(); // No more connections to check. |
| | | hbcf.close(); |
| | | } finally { |
| | | connection = null; |
| | | factory = null; |
| | | ldapConnection = null; |
| | | ldapFactory = null; |
| | | hbcf = null; |
| | | listeners = null; |
| | | scheduler = null; |
| | |
| | | * Send a heartbeat, trapping the search call-back so that we can send |
| | | * the response once we have attempted a bind. |
| | | */ |
| | | when(connection.searchAsync(any(SearchRequest.class), any(SearchResultHandler.class))).thenReturn( |
| | | newSuccessfulLdapPromise(newResult(SUCCESS))); |
| | | final SearchResultLdapPromiseImpl heartBeatPromise = |
| | | newSearchLdapPromise(-1, HEARTBEAT, null, null, ldapConnection); |
| | | when(ldapConnection.searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class))) |
| | | .thenReturn(heartBeatPromise); |
| | | when(hbcf.timeService.now()).thenReturn(11000L); |
| | | scheduler.runAllTasks(); // Send the heartbeat. |
| | | |
| | | // Capture the heartbeat search result handler. |
| | | verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | verify(ldapConnection, times(2)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | assertThat(hbc.isValid()).isTrue(); // Not checked yet. |
| | | |
| | | /* |
| | |
| | | * heart beat completes. |
| | | */ |
| | | hbc.bindAsync(newSimpleBindRequest()); |
| | | verify(connection, times(0)).bindAsync(any(BindRequest.class)); |
| | | verify(ldapConnection, times(0)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | |
| | | // Send fake heartbeat response, releasing the bind request. |
| | | verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | heartBeatPromise.getWrappedPromise().handleResult(newResult(SUCCESS)); |
| | | verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | } |
| | | |
| | | @Test |
| | |
| | | // Get a connection and check that it was pinged. |
| | | hbc = hbcf.getConnection(); |
| | | |
| | | verifyHeartBeatSent(connection, 1); |
| | | verifyHeartBeatSent(ldapConnection, 1); |
| | | assertThat(scheduler.isScheduled()).isTrue(); // heartbeater |
| | | assertThat(listeners).hasSize(1); |
| | | assertThat(hbc.isValid()).isTrue(); |
| | | |
| | | // Invoke heartbeat before the connection is considered idle. |
| | | scheduler.runAllTasks(); |
| | | verifyHeartBeatSent(connection, 1); // No heartbeat sent - not idle yet. |
| | | verifyHeartBeatSent(ldapConnection, 1); // No heartbeat sent - not idle yet. |
| | | assertThat(hbc.isValid()).isTrue(); |
| | | |
| | | // Invoke heartbeat after the connection is considered idle. |
| | | when(hbcf.timeService.now()).thenReturn(6000L); |
| | | scheduler.runAllTasks(); |
| | | verifyHeartBeatSent(connection, 2); // Heartbeat sent. |
| | | verifyHeartBeatSent(ldapConnection, 2); // Heartbeat sent. |
| | | assertThat(hbc.isValid()).isTrue(); |
| | | |
| | | // Now force the heartbeat to fail. |
| | | mockHeartBeatResponse(connection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN); |
| | | mockHeartBeatResponse(ldapConnection, listeners, ResultCode.CLIENT_SIDE_SERVER_DOWN); |
| | | when(hbcf.timeService.now()).thenReturn(11000L); |
| | | scheduler.runAllTasks(); |
| | | verifyHeartBeatSent(connection, 3); |
| | | verifyHeartBeatSent(ldapConnection, 3); |
| | | assertThat(hbc.isValid()).isFalse(); |
| | | |
| | | // Flush redundant timeout tasks. |
| | |
| | | hbc.modifyAsync(newModifyRequest(DN.rootDN())).thenOnException(mockHandler); |
| | | final ArgumentCaptor<LdapException> arg = ArgumentCaptor.forClass(LdapException.class); |
| | | verify(mockHandler).handleException(arg.capture()); |
| | | assertThat(arg.getValue().getResult().getResultCode()).isEqualTo( |
| | | ResultCode.CLIENT_SIDE_SERVER_DOWN); |
| | | assertThat(arg.getValue().getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_SERVER_DOWN); |
| | | |
| | | assertThat(hbc.isValid()).isFalse(); |
| | | assertThat(hbc.isClosed()).isFalse(); |
| | |
| | | hbc = hbcf.getConnection(); |
| | | |
| | | // Now force the heartbeat to fail due to timeout. |
| | | mockHeartBeatResponse(connection, listeners, null /* no response */); |
| | | mockHeartBeatResponse(ldapConnection, listeners, null /* no response */); |
| | | when(hbcf.timeService.now()).thenReturn(11000L); |
| | | scheduler.runAllTasks(); // Send the heartbeat. |
| | | verifyHeartBeatSent(connection, 2); |
| | | verifyHeartBeatSent(ldapConnection, 2); |
| | | assertThat(hbc.isValid()).isTrue(); // Not checked yet. |
| | | when(hbcf.timeService.now()).thenReturn(12000L); |
| | | scheduler.runAllTasks(); // Check for heartbeat. |
| | |
| | | when(hbcf.timeService.now()).thenReturn(11000L); |
| | | hbc.bindAsync(newSimpleBindRequest()); |
| | | |
| | | verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | |
| | | // Verify no heartbeat is sent because there is a bind in progress. |
| | | when(hbcf.timeService.now()).thenReturn(11001L); |
| | | scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat() |
| | | verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | |
| | | // Send fake bind response, releasing the heartbeat. |
| | | when(hbcf.timeService.now()).thenReturn(11099L); |
| | | ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS)); |
| | | promise.getWrappedPromise().handleResult(newBindResult(SUCCESS)); |
| | | |
| | | // Check that bind response acts as heartbeat. |
| | | assertThat(hbc.isValid()).isTrue(); |
| | |
| | | // Send another bind request which will timeout. |
| | | when(hbcf.timeService.now()).thenReturn(20000L); |
| | | hbc.bindAsync(newSimpleBindRequest()); |
| | | verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | |
| | | // Verify no heartbeat is sent because there is a bind in progress. |
| | | when(hbcf.timeService.now()).thenReturn(20001L); |
| | | scheduler.runAllTasks(); // Invokes HBCF.ConnectionImpl.sendHeartBeat() |
| | | verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | |
| | | // Check that lack of bind response acts as heartbeat timeout. |
| | | assertThat(hbc.isValid()).isTrue(); |
| | |
| | | hbc = hbcf.getConnection(); |
| | | hbc.bindAsync(newSimpleBindRequest()); |
| | | |
| | | verify(connection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | verify(ldapConnection, times(1)).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | |
| | | /* |
| | | * Now attempt the heartbeat which should not happen because there is a |
| | |
| | | when(hbcf.timeService.now()).thenReturn(11000L); |
| | | // Attempt to send the heartbeat. |
| | | scheduler.runAllTasks(); |
| | | verify(connection, times(1)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | verify(ldapConnection, times(1)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | |
| | | // Send fake bind response, releasing the heartbeat. |
| | | ((PromiseImpl) promise.getWrappedPromise()).handleResult(newResult(SUCCESS)); |
| | | promise.getWrappedPromise().handleResult(newBindResult(SUCCESS)); |
| | | |
| | | // Attempt to send a heartbeat again. |
| | | when(hbcf.timeService.now()).thenReturn(16000L); |
| | | // Attempt to send the heartbeat. |
| | | scheduler.runAllTasks(); |
| | | verify(connection, times(2)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | verify(ldapConnection, times(2)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | } |
| | | |
| | | @Test |
| | |
| | | |
| | | private void mockConnectionWithInitialHeartbeatResult(final ResultCode initialHeartBeatResult) { |
| | | listeners = new LinkedList<>(); |
| | | connection = mockConnection(listeners); |
| | | when(connection.isValid()).thenReturn(true); |
| | | mockHeartBeatResponse(connection, listeners, initialHeartBeatResult); |
| | | |
| | | // Underlying connection factory. |
| | | factory = mockConnectionFactory(connection); |
| | | ldapConnection = mockLDAPConnectionImpl(listeners); |
| | | when(ldapConnection.isValid()).thenReturn(true); |
| | | mockHeartBeatResponse(ldapConnection, listeners, initialHeartBeatResult); |
| | | ldapFactory = mock(LDAPConnectionFactoryImpl.class); |
| | | when(ldapFactory.getConnectionAsync()).thenAnswer(new Answer<Promise<LDAPConnectionImpl, LdapException>>() { |
| | | @Override |
| | | public Promise<LDAPConnectionImpl, LdapException> answer(final InvocationOnMock invocation) |
| | | throws Throwable { |
| | | return newSuccessfulLdapPromise(ldapConnection); |
| | | } |
| | | }); |
| | | TransportProvider provider = mock(TransportProvider.class); |
| | | when(provider.getLDAPConnectionFactory(anyString(), anyInt(), any(Options.class))).thenReturn(ldapFactory); |
| | | scheduler = new MockScheduler(); |
| | | |
| | | // Create heart beat connection factory. |
| | | scheduler = new MockScheduler(); |
| | | hbcf = new HeartBeatConnectionFactory(factory, 10000, 100, TimeUnit.MILLISECONDS, HEARTBEAT, scheduler); |
| | | hbcf = new LDAPConnectionFactory("dummyHost", |
| | | 1389, |
| | | Options.defaultOptions() |
| | | .set(TRANSPORT_PROVIDER_INSTANCE, provider) |
| | | .set(HEARTBEAT_ENABLED, true) |
| | | .set(HEARTBEAT_TIMEOUT, duration("100 ms")) |
| | | .set(HEARTBEAT_SEARCH_REQUEST, HEARTBEAT) |
| | | .set(HEARTBEAT_SCHEDULER, scheduler)); |
| | | |
| | | // Set initial time stamp. |
| | | hbcf.timeService = mockTimeService(0); |
| | | } |
| | | |
| | | private BindResultLdapPromiseImpl mockBindAsyncResponse() { |
| | | final BindResultLdapPromiseImpl bindPromise = newBindLdapPromise(-1, null, null, null, connection); |
| | | doAnswer(new Answer<LdapPromise<BindResult>>() { |
| | | @Override |
| | | public LdapPromise<BindResult> answer(final InvocationOnMock invocation) throws Throwable { |
| | | return bindPromise; |
| | | } |
| | | }).when(connection).bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class)); |
| | | |
| | | final BindResultLdapPromiseImpl bindPromise = newBindLdapPromise(-1, null, null, null, ldapConnection); |
| | | when(ldapConnection.bindAsync(any(BindRequest.class), any(IntermediateResponseHandler.class))) |
| | | .thenReturn(bindPromise); |
| | | return bindPromise; |
| | | } |
| | | |
| | | private Connection mockHeartBeatResponse(final Connection mockConnection, |
| | | final List<ConnectionEventListener> listeners, final ResultCode resultCode) { |
| | | Answer<LdapPromise<Result>> answer = new Answer<LdapPromise<Result>>() { |
| | | // |
| | | private void mockHeartBeatResponse( |
| | | final LDAPConnectionImpl mockConnection, |
| | | final List<ConnectionEventListener> listeners, |
| | | final ResultCode resultCode) { |
| | | // @Checkstyle:off |
| | | when(mockConnection.searchAsync(any(SearchRequest.class), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class))).thenAnswer(new Answer<LdapPromise<Result>>() { |
| | | @Override |
| | | public LdapPromise<Result> answer(final InvocationOnMock invocation) throws Throwable { |
| | | if (resultCode == null) { |
| | |
| | | return newSuccessfulLdapPromise(newResult(resultCode)); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class), any(SearchResultHandler.class)); |
| | | doAnswer(answer).when(mockConnection).searchAsync(any(SearchRequest.class), |
| | | any(IntermediateResponseHandler.class), any(SearchResultHandler.class)); |
| | | |
| | | return mockConnection; |
| | | }); |
| | | // @Checkstyle:on |
| | | } |
| | | |
| | | private void verifyHeartBeatSent(final Connection connection, final int times) { |
| | | verify(connection, times(times)).searchAsync(same(HEARTBEAT), any(SearchResultHandler.class)); |
| | | private void verifyHeartBeatSent(final LDAPConnectionImpl connection, final int times) { |
| | | verify(connection, times(times)).searchAsync(same(HEARTBEAT), |
| | | any(IntermediateResponseHandler.class), |
| | | any(SearchResultHandler.class)); |
| | | } |
| | | |
| | | private static LDAPConnectionImpl mockLDAPConnectionImpl(final List<ConnectionEventListener> listeners) { |
| | | final LDAPConnectionImpl mockConnection = mock(LDAPConnectionImpl.class); |
| | | |
| | | // Handle listener registration / deregistration in mock connection. |
| | | doAnswer(new Answer<Void>() { |
| | | @Override |
| | | public Void answer(final InvocationOnMock invocation) throws Throwable { |
| | | final ConnectionEventListener listener = |
| | | (ConnectionEventListener) invocation.getArguments()[0]; |
| | | listeners.add(listener); |
| | | return null; |
| | | } |
| | | }).when(mockConnection).addConnectionEventListener(any(ConnectionEventListener.class)); |
| | | |
| | | doAnswer(new Answer<Void>() { |
| | | @Override |
| | | public Void answer(final InvocationOnMock invocation) throws Throwable { |
| | | final ConnectionEventListener listener = |
| | | (ConnectionEventListener) invocation.getArguments()[0]; |
| | | listeners.remove(listener); |
| | | return null; |
| | | } |
| | | }).when(mockConnection).removeConnectionEventListener(any(ConnectionEventListener.class)); |
| | | |
| | | return mockConnection; |
| | | } |
| | | } |
| | |
| | | } |
| | | sslContext = new SSLContextBuilder().getSSLContext(); |
| | | listener = new LDAPListener(findFreeSocketAddress(), getInstance(), |
| | | Options.defaultOptions().set(BACKLOG, 4096)); |
| | | Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096)); |
| | | isRunning = true; |
| | | } |
| | | |
| | |
| | | |
| | | import java.net.InetSocketAddress; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.promise.Promise; |
| | | |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.forgerock.util.promise.Promises.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | /** |
| | | * Basic LDAP connection factory implementation to use for tests only. |
| | | */ |
| | | public final class BasicLDAPConnectionFactory implements LDAPConnectionFactoryImpl { |
| | | |
| | | private final Options options; |
| | | final class BasicLDAPConnectionFactory implements LDAPConnectionFactoryImpl { |
| | | private final String host; |
| | | private final int port; |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory which does nothing. |
| | | * @param host |
| | | * The address of the Directory Server to connect to. |
| | | * @param port |
| | | * The port of the Directory Server to connect to. |
| | | * @param options |
| | | */ |
| | | public BasicLDAPConnectionFactory(final String host, final int port, final Options options) { |
| | | BasicLDAPConnectionFactory(final String host, final int port, final Options options) { |
| | | this.host = host; |
| | | this.port = port; |
| | | this.options = Options.copyOf(options); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Connection getConnection() throws LdapException { |
| | | try { |
| | | return getConnectionAsync().getOrThrow(); |
| | | } catch (final InterruptedException e) { |
| | | throw newLdapException(ResultCode.CLIENT_SIDE_USER_CANCELLED, e); |
| | | } |
| | | public Promise<LDAPConnectionImpl, LdapException> getConnectionAsync() { |
| | | return newResultPromise(mock(LDAPConnectionImpl.class)); |
| | | } |
| | | |
| | | @Override |
| | | public Promise<Connection, LdapException> getConnectionAsync() { |
| | | return newResultPromise(mock(Connection.class)); |
| | | } |
| | | |
| | | /** |
| | | * Returns the address of the Directory Server. |
| | | * |
| | | * @return The address of the Directory Server. |
| | | */ |
| | | @Override |
| | | public InetSocketAddress getSocketAddress() { |
| | | return new InetSocketAddress(host, port); |
| | | } |
| | |
| | | public String toString() { |
| | | return getClass().getSimpleName() + "(" + host + ':' + port + ')'; |
| | | } |
| | | |
| | | Options getLDAPOptions() { |
| | | return options; |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT; |
| | | import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT; |
| | | import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_REQUEST_TIMEOUT; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.REQUEST_TIMEOUT; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.ResultCode.CLIENT_SIDE_LOCAL_ERROR; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newResult; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.*; |
| | | |
| | | 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 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.requests.GenericBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.UnbindRequest; |
| | |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl; |
| | | import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.Reject; |
| | | import org.forgerock.util.promise.Promise; |
| | | import org.forgerock.util.promise.PromiseImpl; |
| | | import org.forgerock.util.time.Duration; |
| | | import org.glassfish.grizzly.CompletionHandler; |
| | | import org.glassfish.grizzly.EmptyCompletionHandler; |
| | | import org.glassfish.grizzly.filterchain.Filter; |
| | | import org.glassfish.grizzly.filterchain.FilterChain; |
| | | import org.glassfish.grizzly.ssl.SSLEngineConfigurator; |
| | | import org.glassfish.grizzly.ssl.SSLFilter; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | 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 implements LDAPConnectionImpl, TimeoutEventListener { |
| | | /** |
| | | * A dummy SSL client engine configurator as SSLFilter only needs client |
| | | * config. This prevents Grizzly from needlessly using JVM defaults which |
| | |
| | | private final AtomicInteger nextMsgID = new AtomicInteger(1); |
| | | private final GrizzlyLDAPConnectionFactory factory; |
| | | private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingRequests = new ConcurrentHashMap<>(); |
| | | private final long requestTimeoutMS; |
| | | private final Object stateLock = new Object(); |
| | | /** Guarded by stateLock. */ |
| | | private Result connectionInvalidReason; |
| | |
| | | final GrizzlyLDAPConnectionFactory factory) { |
| | | this.connection = connection; |
| | | this.factory = factory; |
| | | final Duration requestTimeout = factory.getLDAPOptions().get(REQUEST_TIMEOUT); |
| | | this.requestTimeoutMS = requestTimeout.isUnlimited() ? 0 : requestTimeout.to(TimeUnit.MILLISECONDS); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | close(Requests.newUnbindRequest(), null); |
| | | } |
| | | |
| | | @Override |
| | | public void close(final UnbindRequest request, final String reason) { |
| | | // FIXME: I18N need to internationalize this message. |
| | | Reject.ifNull(request); |
| | |
| | | |
| | | @Override |
| | | public long handleTimeout(final long currentTime) { |
| | | final long timeout = factory.getLDAPOptions().get(TIMEOUT_IN_MILLISECONDS); |
| | | if (timeout <= 0) { |
| | | if (requestTimeoutMS <= 0) { |
| | | return 0; |
| | | } |
| | | |
| | | long delay = timeout; |
| | | long delay = requestTimeoutMS; |
| | | for (final ResultLdapPromiseImpl<?, ?> promise : pendingRequests.values()) { |
| | | if (promise == null || !promise.checkForTimeout()) { |
| | | continue; |
| | | } |
| | | final long diff = (promise.getTimestamp() + timeout) - currentTime; |
| | | final long diff = (promise.getTimestamp() + requestTimeoutMS) - currentTime; |
| | | if (diff > 0) { |
| | | // Will expire in diff milliseconds. |
| | | delay = Math.min(delay, diff); |
| | |
| | | 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()); |
| | | LDAP_CONNECTION_BIND_OR_START_TLS_REQUEST_TIMEOUT.get(requestTimeoutMS).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()); |
| | | LDAP_CONNECTION_BIND_OR_START_TLS_CONNECTION_TIMEOUT.get(requestTimeoutMS).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()); |
| | | LDAP_CONNECTION_REQUEST_TIMEOUT.get(requestTimeoutMS).toString()); |
| | | promise.adaptErrorResult(result); |
| | | |
| | | /* |
| | |
| | | |
| | | @Override |
| | | public long getTimeout() { |
| | | return factory.getLDAPOptions().get(TIMEOUT_IN_MILLISECONDS); |
| | | return requestTimeoutMS; |
| | | } |
| | | |
| | | /** |
| | |
| | | bindOrStartTLSInProgress.set(state); |
| | | } |
| | | |
| | | @Override |
| | | public Promise<Void, LdapException> enableTLS( |
| | | final SSLContext sslContext, |
| | | final List<String> sslEnabledProtocols, |
| | | final List<String> sslEnabledCipherSuites) { |
| | | final PromiseImpl<Void, LdapException> promise = PromiseImpl.create(); |
| | | final EmptyCompletionHandler<SSLEngine> completionHandler = new EmptyCompletionHandler<SSLEngine>() { |
| | | @Override |
| | | public void completed(final SSLEngine result) { |
| | | promise.handleResult(null); |
| | | } |
| | | |
| | | @Override |
| | | public void failed(final Throwable throwable) { |
| | | final Result errorResult = newResult(CLIENT_SIDE_LOCAL_ERROR) |
| | | .setCause(throwable).setDiagnosticMessage("SSL handshake failed"); |
| | | connectionErrorOccurred(errorResult); |
| | | promise.handleException(newLdapException(errorResult)); |
| | | } |
| | | }; |
| | | |
| | | try { |
| | | startTLS(sslContext, sslEnabledProtocols, sslEnabledCipherSuites, completionHandler); |
| | | } catch (final IOException e) { |
| | | completionHandler.failed(e); |
| | | } |
| | | return promise; |
| | | } |
| | | |
| | | void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites, |
| | | final CompletionHandler<SSLEngine> completionHandler) throws IOException { |
| | | final CompletionHandler<SSLEngine> completionHandler) throws IOException { |
| | | synchronized (stateLock) { |
| | | if (isTLSEnabled()) { |
| | | throw new IllegalStateException("TLS already enabled"); |
| | |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import java.io.IOException; |
| | | import static com.forgerock.opendj.grizzly.GrizzlyMessages.LDAP_CONNECTION_CONNECT_TIMEOUT; |
| | | import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT; |
| | | import static org.forgerock.opendj.grizzly.GrizzlyUtils.buildFilterChain; |
| | | import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.CONNECT_TIMEOUT; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.TimeoutChecker.TIMEOUT_CHECKER; |
| | | |
| | | import java.net.InetSocketAddress; |
| | | import java.util.List; |
| | | 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.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | 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.LDAPConnectionFactoryImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionImpl; |
| | | import org.forgerock.util.Option; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.promise.ExceptionHandler; |
| | | import org.forgerock.util.promise.Promise; |
| | | import org.forgerock.util.promise.PromiseImpl; |
| | | import org.forgerock.util.promise.ResultHandler; |
| | | import org.forgerock.util.time.Duration; |
| | | import org.glassfish.grizzly.CompletionHandler; |
| | | import org.glassfish.grizzly.EmptyCompletionHandler; |
| | | import org.glassfish.grizzly.Connection; |
| | | import org.glassfish.grizzly.SocketConnectorHandler; |
| | | import org.glassfish.grizzly.filterchain.FilterChain; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler; |
| | |
| | | |
| | | import com.forgerock.opendj.util.ReferenceCountedObject; |
| | | |
| | | import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.*; |
| | | import static org.forgerock.opendj.grizzly.GrizzlyUtils.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.forgerock.opendj.ldap.TimeoutChecker.*; |
| | | |
| | | import static com.forgerock.opendj.grizzly.GrizzlyMessages.*; |
| | | |
| | | /** |
| | | * LDAP connection factory implementation using Grizzly for transport. |
| | | */ |
| | |
| | | * 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 implements CompletionHandler<Connection>, TimeoutEventListener { |
| | | private final PromiseImpl<LDAPConnectionImpl, LdapException> promise; |
| | | private final long timeoutEndTime; |
| | | |
| | | private CompletionHandlerAdapter(final PromiseImpl<Connection, LdapException> promise) { |
| | | private CompletionHandlerAdapter(final PromiseImpl<LDAPConnectionImpl, LdapException> promise) { |
| | | this.promise = promise; |
| | | final long timeoutMS = getTimeout(); |
| | | this.timeoutEndTime = timeoutMS > 0 ? System.currentTimeMillis() + timeoutMS : 0; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void completed(final org.glassfish.grizzly.Connection result) { |
| | | public void completed(final Connection result) { |
| | | // Adapt the connection. |
| | | final GrizzlyLDAPConnection connection = adaptConnection(result); |
| | | |
| | | // Plain connection. |
| | | if (options.get(SSL_CONTEXT) == null) { |
| | | thenOnResult(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); |
| | | timeoutChecker.get().removeListener(this); |
| | | if (!promise.tryHandleResult(connection)) { |
| | | // The connection has been either cancelled or it has timed out. |
| | | connection.close(); |
| | | return; |
| | | } |
| | | |
| | | List<String> protocols = options.get(ENABLED_PROTOCOLS); |
| | | List<String> suites = options.get(ENABLED_CIPHER_SUITES); |
| | | if (options.get(USE_STARTTLS)) { |
| | | // Chain StartTLS extended request. |
| | | final StartTLSExtendedRequest startTLS = |
| | | Requests.newStartTLSExtendedRequest(options.get(SSL_CONTEXT)); |
| | | startTLS.addEnabledCipherSuite(suites.toArray(new String[suites.size()])); |
| | | startTLS.addEnabledProtocol(protocols.toArray(new String[protocols.size()])); |
| | | |
| | | connection.extendedRequestAsync(startTLS).thenOnResult(new ResultHandler<ExtendedResult>() { |
| | | @Override |
| | | public void handleResult(final ExtendedResult result) { |
| | | thenOnResult(connection); |
| | | } |
| | | }).thenOnException(new ExceptionHandler<LdapException>() { |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | onException(connection, error); |
| | | } |
| | | }); |
| | | } else { |
| | | // Install SSL/TLS layer. |
| | | try { |
| | | connection.startTLS(options.get(SSL_CONTEXT), protocols, suites, |
| | | new EmptyCompletionHandler<SSLEngine>() { |
| | | @Override |
| | | public void completed(final SSLEngine result) { |
| | | thenOnResult(connection); |
| | | } |
| | | |
| | | @Override |
| | | public void failed(final Throwable throwable) { |
| | | onException(connection, throwable); |
| | | } |
| | | }); |
| | | } catch (final IOException e) { |
| | | onException(connection, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void updated(final org.glassfish.grizzly.Connection result) { |
| | | public void updated(final Connection result) { |
| | | // Ignore this. |
| | | } |
| | | |
| | | private GrizzlyLDAPConnection adaptConnection( |
| | | final org.glassfish.grizzly.Connection<?> connection) { |
| | | private GrizzlyLDAPConnection adaptConnection(final Connection<?> connection) { |
| | | configureConnection(connection, logger, options); |
| | | |
| | | final GrizzlyLDAPConnection ldapConnection = |
| | |
| | | if (!(t instanceof LdapException) && t instanceof ExecutionException) { |
| | | t = t.getCause() != null ? t.getCause() : t; |
| | | } |
| | | |
| | | if (t instanceof LdapException) { |
| | | return (LdapException) t; |
| | | } else { |
| | |
| | | } |
| | | } |
| | | |
| | | private void onException(final GrizzlyLDAPConnection connection, final Throwable t) { |
| | | // Abort connection attempt due to error. |
| | | timeoutChecker.get().removeListener(this); |
| | | promise.handleException(adaptConnectionException(t)); |
| | | connection.close(); |
| | | } |
| | | |
| | | private void thenOnResult(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 |
| | | public long handleTimeout(final long currentTime) { |
| | | if (timeoutEndTime == 0) { |
| | |
| | | |
| | | @Override |
| | | public long getTimeout() { |
| | | return options.get(CONNECT_TIMEOUT_IN_MILLISECONDS); |
| | | final Duration duration = options.get(CONNECT_TIMEOUT); |
| | | return duration.isUnlimited() ? 0L : duration.to(TimeUnit.MILLISECONDS); |
| | | } |
| | | } |
| | | |
| | |
| | | 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(); |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory based on Grizzly which can be used |
| | | * to create connections to the Directory Server at the provided host and |
| | | * port address using provided connection options. |
| | | * @param host |
| | | * The hostname of the Directory Server to connect to. |
| | | * Grizzly TCP Transport NIO implementation to use for connections. If {@code null}, default transport will be |
| | | * used. |
| | | */ |
| | | public static final Option<TCPNIOTransport> GRIZZLY_TRANSPORT = Option.of(TCPNIOTransport.class, null); |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory based on Grizzly which can be used to create connections to the Directory |
| | | * Server at the provided host and port address using provided connection options. |
| | | * |
| | | * @param host |
| | | * The hostname of the Directory Server to connect to. |
| | | * @param port |
| | | * The port number of the Directory Server to connect to. |
| | | * The port number of the Directory Server to connect to. |
| | | * @param options |
| | | * The LDAP connection options to use when creating connections. |
| | | * The LDAP connection options to use when creating connections. |
| | | */ |
| | | public GrizzlyLDAPConnectionFactory(final String host, final int port, final Options options) { |
| | | this(host, port, options, null); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory based on Grizzly which can be used |
| | | * to create connections to the Directory Server at the provided host and |
| | | * port address using provided connection options and provided TCP |
| | | * transport. |
| | | * @param host |
| | | * The hostname of the Directory Server to connect to. |
| | | * @param port |
| | | * The port number of the Directory Server to connect to. |
| | | * @param options |
| | | * The LDAP connection options to use when creating connections. |
| | | * @param transport |
| | | * Grizzly TCP Transport NIO implementation to use for |
| | | * connections. If {@code null}, default transport will be used. |
| | | */ |
| | | public GrizzlyLDAPConnectionFactory(final String host, final int port, final Options options, |
| | | TCPNIOTransport transport) { |
| | | this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport); |
| | | this.transport = DEFAULT_TRANSPORT.acquireIfNull(options.get(GRIZZLY_TRANSPORT)); |
| | | this.host = host; |
| | | this.port = port; |
| | | this.options = options; |
| | | this.clientFilter = new LDAPClientFilter(options.get(DECODE_OPTIONS), 0); |
| | | this.defaultFilterChain = |
| | | buildFilterChain(this.transport.get().getProcessor(), clientFilter); |
| | | this.clientFilter = new LDAPClientFilter(options.get(LDAP_DECODE_OPTIONS), 0); |
| | | this.defaultFilterChain = buildFilterChain(this.transport.get().getProcessor(), clientFilter); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @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() { |
| | | public Promise<LDAPConnectionImpl, LdapException> getConnectionAsync() { |
| | | acquireTransportAndTimeoutChecker(); // Protect resources. |
| | | final SocketConnectorHandler connectorHandler = |
| | | TCPNIOConnectorHandler.builder(transport.get()).processor(defaultFilterChain) |
| | | .build(); |
| | | final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create(); |
| | | final SocketConnectorHandler connectorHandler = TCPNIOConnectorHandler.builder(transport.get()) |
| | | .processor(defaultFilterChain) |
| | | .build(); |
| | | final PromiseImpl<LDAPConnectionImpl, LdapException> promise = PromiseImpl.create(); |
| | | connectorHandler.connect(getSocketAddress(), new CompletionHandlerAdapter(promise)); |
| | | return promise; |
| | | } |
| | |
| | | return port; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return getClass().getSimpleName() + "(" + host + ':' + port + ')'; |
| | | } |
| | | |
| | | TimeoutChecker getTimeoutChecker() { |
| | | return timeoutChecker.get(); |
| | | } |
| | |
| | | this.connectionFactory = factory; |
| | | this.options = Options.copyOf(options); |
| | | final LDAPServerFilter serverFilter = |
| | | new LDAPServerFilter(this, options.get(DECODE_OPTIONS), options.get(MAX_REQUEST_SIZE_BYTES)); |
| | | new LDAPServerFilter(this, options.get(LDAP_DECODE_OPTIONS), options.get(REQUEST_MAX_SIZE_IN_BYTES)); |
| | | final FilterChain ldapChain = |
| | | GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter); |
| | | final TCPNIOBindingHandler bindingHandler = |
| | | TCPNIOBindingHandler.builder(this.transport.get()).processor(ldapChain).build(); |
| | | this.serverConnection = bindingHandler.bind(address, options.get(BACKLOG)); |
| | | this.serverConnection = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG)); |
| | | |
| | | /* |
| | | * Get the socket address now, ensuring that the host is the same as the |
| | |
| | | final SocketChannel channel = (SocketChannel) ((TCPNIOConnection) connection).getChannel(); |
| | | final Socket socket = channel.socket(); |
| | | final boolean tcpNoDelay = options.get(TCP_NO_DELAY); |
| | | final boolean keepAlive = options.get(KEEPALIVE); |
| | | final boolean reuseAddress = options.get(REUSE_ADDRESS); |
| | | final int linger = options.get(LINGER); |
| | | final boolean keepAlive = options.get(SO_KEEPALIVE); |
| | | final boolean reuseAddress = options.get(SO_REUSE_ADDRESS); |
| | | final int linger = options.get(SO_LINGER_IN_SECONDS); |
| | | try { |
| | | socket.setTcpNoDelay(tcpNoDelay); |
| | | } catch (final SocketException e) { |
| | |
| | | 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().get(DECODE_OPTIONS)); |
| | | final R decodedResponse = promise.decodeResult(result, conn.getLDAPOptions().get(LDAP_DECODE_OPTIONS)); |
| | | |
| | | if (result.getResultCode() == ResultCode.SUCCESS |
| | | && promise.getRequest() instanceof StartTLSExtendedRequest) { |
| | |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static java.util.Arrays.asList; |
| | | import static java.util.Collections.singletonList; |
| | | import static org.fest.assertions.Assertions.assertThat; |
| | | import static org.forgerock.opendj.ldap.Connections.newFailoverLoadBalancer; |
| | | import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool; |
| | | import static org.forgerock.opendj.ldap.Connections.newRoundRobinLoadBalancer; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.getServerSocketAddress; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newCRAMMD5SASLBindRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newDigestMD5SASLBindRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise; |
| | | import static org.forgerock.util.Options.defaultOptions; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.any; |
| | | import static org.mockito.Matchers.anyInt; |
| | | import static org.mockito.Mockito.doAnswer; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | import static org.testng.Assert.assertNotNull; |
| | | import static org.testng.Assert.assertTrue; |
| | | |
| | | import java.net.InetSocketAddress; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.concurrent.Callable; |
| | | import java.util.concurrent.CountDownLatch; |
| | |
| | | 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.LDAPServer; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapPromise; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.MockConnectionEventListener; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm; |
| | | import org.forgerock.opendj.ldap.SSLContextBuilder; |
| | | import org.forgerock.opendj.ldap.SdkTestCase; |
| | | import org.forgerock.opendj.ldap.SearchScope; |
| | |
| | | import org.forgerock.opendj.ldap.TestCaseUtils; |
| | | import org.forgerock.opendj.ldap.TrustManagers; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.CRAMMD5SASLBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.DigestMD5SASLBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.SimpleBindRequest; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.Responses; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | 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.LDAPConnectionFactory.ENABLED_CIPHER_SUITES; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.*; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | import static org.testng.Assert.*; |
| | | |
| | | /** |
| | | * Tests the {@code ConnectionFactory} classes. |
| | | */ |
| | |
| | | "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, 2000, TimeUnit.MILLISECONDS, request); |
| | | factories[0][0] = new LDAPConnectionFactory(serverAddress.getHostName(), |
| | | serverAddress.getPort(), |
| | | defaultOptions() |
| | | .set(HEARTBEAT_ENABLED, true) |
| | | .set(HEARTBEAT_INTERVAL, duration("1000 ms")) |
| | | .set(HEARTBEAT_TIMEOUT, duration("2000 ms")) |
| | | .set(HEARTBEAT_SEARCH_REQUEST, request)); |
| | | |
| | | // InternalConnectionFactory |
| | | factories[1][0] = Connections.newInternalConnectionFactory(LDAPServer.getInstance(), null); |
| | | |
| | | // AuthenticatedConnectionFactory |
| | | factories[2][0] = |
| | | Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory( |
| | | serverAddress.getHostName(), serverAddress.getPort()), |
| | | Requests.newSimpleBindRequest("", new char[0])); |
| | | final SimpleBindRequest anon = newSimpleBindRequest("", new char[0]); |
| | | factories[2][0] = new LDAPConnectionFactory(serverAddress.getHostName(), |
| | | serverAddress.getPort(), |
| | | defaultOptions().set(AUTHN_BIND_REQUEST, anon)); |
| | | |
| | | // AuthenticatedConnectionFactory with multi-stage SASL |
| | | factories[3][0] = |
| | | Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory( |
| | | serverAddress.getHostName(), serverAddress.getPort()), |
| | | Requests.newCRAMMD5SASLBindRequest("id:user", "password".toCharArray())); |
| | | final CRAMMD5SASLBindRequest crammd5 = newCRAMMD5SASLBindRequest("id:user", "password".toCharArray()); |
| | | factories[3][0] = new LDAPConnectionFactory(serverAddress.getHostName(), |
| | | serverAddress.getPort(), |
| | | defaultOptions().set(AUTHN_BIND_REQUEST, crammd5)); |
| | | |
| | | // LDAPConnectionFactory with default options |
| | | factories[4][0] = new LDAPConnectionFactory(serverAddress.getHostName(), serverAddress.getPort()); |
| | | |
| | | // LDAPConnectionFactory with startTLS |
| | | SSLContext sslContext = |
| | | new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext(); |
| | | Options options = Options.defaultOptions().set(ENABLED_CIPHER_SUITES, |
| | | new ArrayList<String>(Arrays.asList( |
| | | "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", |
| | | "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", |
| | | "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", |
| | | "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); |
| | | SSLContext sslContext = new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext(); |
| | | final Options startTlsOptions = defaultOptions() |
| | | .set(SSL_CONTEXT, sslContext) |
| | | .set(SSL_USE_STARTTLS, true) |
| | | .set(SSL_ENABLED_CIPHER_SUITES, |
| | | asList("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", |
| | | "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", |
| | | "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", |
| | | "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(), |
| | | startTlsOptions); |
| | | |
| | | // 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)); |
| | | final DigestMD5SASLBindRequest digestmd5 = newDigestMD5SASLBindRequest("id:user", "password".toCharArray()) |
| | | .setCipher(DigestMD5SASLBindRequest.CIPHER_LOW); |
| | | factories[6][0] = new LDAPConnectionFactory(serverAddress.getHostName(), |
| | | serverAddress.getPort(), |
| | | Options.copyOf(startTlsOptions).set(AUTHN_BIND_REQUEST, digestmd5)); |
| | | |
| | | // Connection pool and load balancing tests. |
| | | InetSocketAddress offlineSocketAddress1 = findFreeSocketAddress(); |
| | |
| | | serverAddress.getPort()), "online"); |
| | | |
| | | // Connection pools. |
| | | factories[7][0] = Connections.newFixedConnectionPool(onlineServer, 10); |
| | | factories[7][0] = newFixedConnectionPool(onlineServer, 10); |
| | | |
| | | // Round robin. |
| | | factories[8][0] = |
| | |
| | | offlineServer1, 10), Connections.newFixedConnectionPool( |
| | | onlineServer, 10)))); |
| | | |
| | | factories[20][0] = Connections.newFixedConnectionPool(onlineServer, 10); |
| | | factories[20][0] = newFixedConnectionPool(onlineServer, 10); |
| | | |
| | | return factories; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Verifies that LDAP connections take into consideration changes to the |
| | | * default schema post creation. See OPENDJ-159. |
| | | * <p> |
| | | * This test is disabled because it cannot be run in parallel with rest of |
| | | * the test suite, because it modifies the global default schema. |
| | | * Verifies that LDAP connections take into consideration changes to the default schema post creation. See |
| | | * OPENDJ-159. |
| | | * <p/> |
| | | * This test is disabled because it cannot be run in parallel with rest of the test suite, because it modifies the |
| | | * global default schema. |
| | | * |
| | | * @throws Exception |
| | | * If an unexpected error occurred. |
| | | * If an unexpected error occurred. |
| | | */ |
| | | @Test(enabled = false) |
| | | public void testSchemaUsage() throws Exception { |
| | |
| | | * Tests connection pool closure. |
| | | * |
| | | * @throws Exception |
| | | * If an unexpected exception occurred. |
| | | * If an unexpected exception occurred. |
| | | */ |
| | | @Test |
| | | public void testConnectionPoolClose() throws Exception { |
| | |
| | | } |
| | | }); |
| | | |
| | | ConnectionPool pool = Connections.newFixedConnectionPool(mockFactory, size); |
| | | ConnectionPool pool = newFixedConnectionPool(mockFactory, size); |
| | | Connection[] pooledConnections = new Connection[size]; |
| | | for (int i = 0; i < size; i++) { |
| | | pooledConnections[i] = pool.getConnection(); |
| | |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | |
| | | private final LDAPListener server = createServer(); |
| | | private final InetSocketAddress socketAddress = server.getSocketAddress(); |
| | | public final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), |
| | | socketAddress.getPort(), Options.defaultOptions().set(TIMEOUT_IN_MILLISECONDS, 1L)); |
| | | socketAddress.getPort(), Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms"))); |
| | | private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10); |
| | | private volatile ServerConnection<Integer> serverConnection; |
| | | |
| | |
| | | public void testClientSideConnectTimeout() throws Exception { |
| | | // Use an non-local unreachable network address. |
| | | final ConnectionFactory factory = new LDAPConnectionFactory("10.20.30.40", 1389, |
| | | Options.defaultOptions().set(CONNECT_TIMEOUT_IN_MILLISECONDS, 1L)); |
| | | Options.defaultOptions().set(CONNECT_TIMEOUT, duration("1 ms"))); |
| | | try { |
| | | for (int i = 0; i < ITERATIONS; i++) { |
| | | final PromiseImpl<LdapException, NeverThrowsException> promise = PromiseImpl.create(); |
| | |
| | | @Test |
| | | public void testCreateLDAPConnectionFactoryWithCustomClassLoader() throws Exception { |
| | | // test no exception is thrown, which means transport provider is correctly loaded |
| | | Options options = |
| | | Options.defaultOptions().set(PROVIDER_CLASS_LOADER, Thread.currentThread().getContextClassLoader()); |
| | | Options options = Options.defaultOptions() |
| | | .set(TRANSPORT_PROVIDER_CLASS_LOADER, Thread.currentThread().getContextClassLoader()); |
| | | InetSocketAddress socketAddress = findFreeSocketAddress(); |
| | | LDAPConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), |
| | | socketAddress.getPort(), options); |
| | |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static org.fest.assertions.Assertions.assertThat; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.verifyZeroInteractions; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.TIMEOUT_IN_MILLISECONDS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.REQUEST_TIMEOUT; |
| | | 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.RequestHandler; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | |
| | | * Use a very long time out in order to prevent the timeout thread from |
| | | * triggering the timeout. |
| | | */ |
| | | LDAPConnectionFactory factory = new LDAPConnectionFactory(address.getHostName(), |
| | | address.getPort(), Options.defaultOptions().set( |
| | | TIMEOUT_IN_MILLISECONDS, TimeUnit.SECONDS.toMillis((long) 100))); |
| | | GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnection(); |
| | | GrizzlyLDAPConnectionFactory factory = new GrizzlyLDAPConnectionFactory(address.getHostName(), |
| | | address.getPort(), |
| | | Options.defaultOptions() |
| | | .set(REQUEST_TIMEOUT, duration("100 ms"))); |
| | | GrizzlyLDAPConnection connection = (GrizzlyLDAPConnection) factory.getConnectionAsync().getOrThrow(); |
| | | try { |
| | | SearchRequest request = |
| | | Requests.newSearchRequest("dc=test", SearchScope.BASE_OBJECT, "(objectClass=*)"); |
| | |
| | | SearchResultHandler searchHandler = mock(SearchResultHandler.class); |
| | | @SuppressWarnings("unchecked") |
| | | ExceptionHandler<LdapException> exceptionHandler = mock(ExceptionHandler.class); |
| | | connection.searchAsync(request, searchHandler).thenOnException(exceptionHandler); |
| | | connection.searchAsync(request, null, searchHandler).thenOnException(exceptionHandler); |
| | | |
| | | // Pass in a time which is guaranteed to trigger expiration. |
| | | connection.handleTimeout(System.currentTimeMillis() + 1000000); |
| | |
| | | |
| | | import static org.fest.assertions.Assertions.*; |
| | | import static org.fest.assertions.Fail.*; |
| | | import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool; |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.*; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.util.Options.defaultOptions; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | /** |
| | |
| | | @Test |
| | | public void testCreateLDAPListenerWithCustomClassLoader() throws Exception { |
| | | // test no exception is thrown, which means transport provider is correctly loaded |
| | | Options options = Options.defaultOptions().set(PROVIDER_CLASS_LOADER, |
| | | Thread.currentThread().getContextClassLoader()); |
| | | Options options = defaultOptions().set(TRANSPORT_PROVIDER_CLASS_LOADER, |
| | | Thread.currentThread().getContextClassLoader()); |
| | | LDAPListener listener = new LDAPListener(findFreeSocketAddress(), |
| | | mock(ServerConnectionFactory.class), options); |
| | | listener.close(); |
| | |
| | | @Test(expectedExceptions = { ProviderNotFoundException.class }, |
| | | expectedExceptionsMessageRegExp = "^The requested provider 'unknown' .*") |
| | | public void testCreateLDAPListenerFailureProviderNotFound() throws Exception { |
| | | Options options = Options.defaultOptions().set(TRANSPORT_PROVIDER, "unknown"); |
| | | Options options = defaultOptions().set(TRANSPORT_PROVIDER, "unknown"); |
| | | LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mock(ServerConnectionFactory.class), options); |
| | | listener.close(); |
| | | } |
| | |
| | | final MockServerConnection serverConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory factory = |
| | | new MockServerConnectionFactory(serverConnection); |
| | | final Options options = Options.defaultOptions().set(MAX_REQUEST_SIZE_BYTES, 2048); |
| | | final Options options = defaultOptions().set(REQUEST_MAX_SIZE_IN_BYTES, 2048); |
| | | final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options); |
| | | |
| | | Connection connection = null; |
| | |
| | | import org.forgerock.opendj.io.LDAPWriter; |
| | | import org.forgerock.util.Options; |
| | | import org.glassfish.grizzly.memory.HeapMemoryManager; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.DECODE_OPTIONS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS; |
| | | |
| | | /** |
| | | * Tests for LDAPWriter / LDAPReader classes using specific implementations of |
| | |
| | | |
| | | @Override |
| | | protected LDAPReader<? extends ASN1Reader> getLDAPReader() { |
| | | return GrizzlyUtils.createReader(Options.defaultOptions().get(DECODE_OPTIONS), 0, new HeapMemoryManager()); |
| | | return GrizzlyUtils.createReader(Options.defaultOptions().get(LDAP_DECODE_OPTIONS), 0, new HeapMemoryManager()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | package org.forgerock.opendj.examples; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.AUTHN_BIND_REQUEST; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.HEARTBEAT_ENABLED; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.LinkedList; |
| | |
| | | import org.forgerock.opendj.ldap.RequestHandlerFactory; |
| | | import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.util.Options; |
| | | |
| | | /** |
| | |
| | | // Create load balancer. |
| | | // --- JCite pools --- |
| | | final List<ConnectionFactory> factories = new LinkedList<>(); |
| | | final BindRequest bindRequest = newSimpleBindRequest(proxyDN, proxyPassword.toCharArray()); |
| | | final Options factoryOptions = Options.defaultOptions() |
| | | .set(HEARTBEAT_ENABLED, true) |
| | | .set(AUTHN_BIND_REQUEST, bindRequest); |
| | | |
| | | final List<ConnectionFactory> bindFactories = new LinkedList<>(); |
| | | final Options bindFactoryOptions = Options.defaultOptions().set(HEARTBEAT_ENABLED, true); |
| | | |
| | | for (int i = 4; i < args.length; i += 2) { |
| | | final String remoteAddress = args[i]; |
| | | final int remotePort = Integer.parseInt(args[i + 1]); |
| | | |
| | | 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)))); |
| | | factories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress, |
| | | remotePort, |
| | | factoryOptions))); |
| | | |
| | | bindFactories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress, |
| | | remotePort, |
| | | bindFactoryOptions))); |
| | | } |
| | | // --- JCite pools --- |
| | | |
| | |
| | | |
| | | // --- JCite listener --- |
| | | // Create listener. |
| | | final Options options = Options.defaultOptions().set(BACKLOG, 4096); |
| | | final Options options = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096); |
| | | LDAPListener listener = null; |
| | | try { |
| | | listener = new LDAPListener(localAddress, localPort, connectionHandler, options); |
| | |
| | | |
| | | package org.forgerock.opendj.examples; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPListener.*; |
| | | import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.HashSet; |
| | |
| | | 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.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.Modification; |
| | | import org.forgerock.opendj.ldap.RequestContext; |
| | | import org.forgerock.opendj.ldap.RequestHandler; |
| | | import org.forgerock.opendj.ldap.RequestHandlerFactory; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | |
| | | final int remotePort = Integer.parseInt(args[5]); |
| | | |
| | | // 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 Options factoryOptions = Options.defaultOptions() |
| | | .set(LDAPConnectionFactory.AUTHN_BIND_REQUEST, |
| | | newSimpleBindRequest(proxyDN, proxyPassword.toCharArray())); |
| | | final ConnectionFactory factory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress, |
| | | remotePort, |
| | | factoryOptions)); |
| | | final ConnectionFactory bindFactory = newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress, |
| | | remotePort)); |
| | | |
| | | /* |
| | | * Create a server connection adapter which will create a new proxy |
| | |
| | | Connections.newServerConnectionFactory(proxyFactory); |
| | | |
| | | // Create listener. |
| | | final Options options = Options.defaultOptions().set(BACKLOG, 4096); |
| | | final Options listenerOptions = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096); |
| | | LDAPListener listener = null; |
| | | try { |
| | | listener = new LDAPListener(localAddress, localPort, connectionHandler, options); |
| | | listener = new LDAPListener(localAddress, localPort, connectionHandler, listenerOptions); |
| | | System.out.println("Press any key to stop the server..."); |
| | | System.in.read(); |
| | | } catch (final IOException e) { |
| | |
| | | */ |
| | | package org.forgerock.opendj.examples; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.USE_STARTTLS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT; |
| | | |
| | | |
| | |
| | | SSLContext sslContext = |
| | | new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()).getSSLContext(); |
| | | options.set(SSL_CONTEXT, sslContext); |
| | | options.set(USE_STARTTLS, true); |
| | | options.set(SSL_USE_STARTTLS, true); |
| | | return options; |
| | | } |
| | | |
| | |
| | | |
| | | package org.forgerock.opendj.examples; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPListener.BACKLOG; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG; |
| | | |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | |
| | | // Create listener. |
| | | LDAPListener listener = null; |
| | | try { |
| | | final Options options = Options.defaultOptions().set(BACKLOG, 4096); |
| | | final Options options = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096); |
| | | |
| | | if (keyStoreFileName != null) { |
| | | // Configure SSL/TLS and enable it when connections are |
| | |
| | | options.set(SSL_CONTEXT, sslContext); |
| | | } |
| | | |
| | | options.set(USE_STARTTLS, useStartTLS); |
| | | options.set(SSL_USE_STARTTLS, useStartTLS); |
| | | |
| | | return options; |
| | | } |
| | |
| | | new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()) |
| | | .getSSLContext(); |
| | | options.set(SSL_CONTEXT, sslContext); |
| | | options.set(USE_STARTTLS, useStartTLS); |
| | | options.set(SSL_USE_STARTTLS, useStartTLS); |
| | | return options; |
| | | } |
| | | // --- JCite trust all --- |
| | |
| | | |
| | | package org.forgerock.opendj.examples; |
| | | |
| | | import static org.forgerock.opendj.ldap.TrustManagers.checkHostName; |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.USE_STARTTLS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | |
| | | final String storepass) { |
| | | Options options = Options.defaultOptions(); |
| | | if (useSSL || useStartTLS) { |
| | | TrustManager trustManager = null; |
| | | try { |
| | | trustManager = TrustManagers.checkValidityDates( |
| | | TrustManagers.checkHostName(hostname, |
| | | TrustManager trustManager = TrustManagers.checkValidityDates( |
| | | checkHostName(hostname, |
| | | TrustManagers.checkUsingTrustStore( |
| | | truststore, storepass.toCharArray(), null))); |
| | | if (trustManager != null) { |
| | |
| | | .setTrustManager(trustManager).getSSLContext(); |
| | | options.set(SSL_CONTEXT, sslContext); |
| | | } |
| | | options.set(SSL_USE_STARTTLS, useStartTLS); |
| | | } catch (Exception e) { |
| | | System.err.println(e.getMessage()); |
| | | System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue()); |
| | | return null; |
| | | } |
| | | options.set(USE_STARTTLS, useStartTLS); |
| | | } |
| | | return options; |
| | | } |
| | |
| | | * @return SSL context options to trust all certificates without checking. |
| | | */ |
| | | private static Options getTrustAllOptions() { |
| | | Options options = Options.defaultOptions(); |
| | | try { |
| | | Options options = Options.defaultOptions(); |
| | | SSLContext sslContext = |
| | | new SSLContextBuilder().setTrustManager(TrustManagers.trustAll()) |
| | | .getSSLContext(); |
| | | options.set(SSL_CONTEXT, sslContext); |
| | | options.set(USE_STARTTLS, useStartTLS); |
| | | options.set(SSL_USE_STARTTLS, useStartTLS); |
| | | return options; |
| | | } catch (GeneralSecurityException e) { |
| | | System.err.println(e.getMessage()); |
| | | System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue()); |
| | | return null; |
| | | } |
| | | return options; |
| | | } |
| | | |
| | | private static String host; |
| | |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | runner.setBindRequest(connectionFactoryProvider.getBindRequest()); |
| | | runner.validate(deleteMode, deleteSizeThreshold, deleteAgeThreshold, noPurgeArgument); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | |
| | | break; |
| | | } |
| | | |
| | | final BindRequest bindRequest = getBindRequest(); |
| | | if (bindRequest instanceof SimpleBindRequest) { |
| | | final SimpleBindRequest o = (SimpleBindRequest) bindRequest; |
| | | if (br == null) { |
| | |
| | | private SearchScope scope; |
| | | private DereferenceAliasesPolicy dereferencesAliasesPolicy; |
| | | private String[] attributes; |
| | | private BindRequest bindRequest; |
| | | private int invalidCredPercent; |
| | | |
| | | private BindPerformanceRunner(final PerformanceRunnerOptions options) |
| | |
| | | return 0; |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getConnectionFactory(); |
| | | connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory(); |
| | | runner.setBindRequest(connectionFactoryProvider.getBindRequest()); |
| | | runner.validate(); |
| | | |
| | | runner.bindRequest = connectionFactoryProvider.getBindRequest(); |
| | | if (runner.bindRequest == null) { |
| | | if (runner.getBindRequest() == null) { |
| | | throw new ArgumentException(LocalizableMessage |
| | | .raw("Authentication information must be provided to use this tool")); |
| | | } |
| | |
| | | import static com.forgerock.opendj.ldap.tools.ToolsMessages.*; |
| | | import static com.forgerock.opendj.cli.Utils.filterExitCode; |
| | | import static com.forgerock.opendj.cli.Utils.readBytesFromFile; |
| | | import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage; |
| | | import static com.forgerock.opendj.ldap.tools.Utils.printPasswordPolicyResults; |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | |
| | | import java.io.BufferedReader; |
| | |
| | | import org.forgerock.opendj.ldap.controls.AssertionRequestControl; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | |
| | | argParser.setShortToolDescription(REF_SHORT_DESC_LDAPCOMPARE.get()); |
| | | ConnectionFactoryProvider connectionFactoryProvider; |
| | | ConnectionFactory connectionFactory; |
| | | BindRequest bindRequest; |
| | | |
| | | BooleanArgument continueOnError; |
| | | BooleanArgument noop; |
| | |
| | | return 0; |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory(); |
| | | bindRequest = connectionFactoryProvider.getBindRequest(); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | | return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue(); |
| | |
| | | } |
| | | |
| | | Connection connection = null; |
| | | if (!noop.isPresent()) { |
| | | try { |
| | | connection = connectionFactory.getConnection(); |
| | | } catch (final LdapException ere) { |
| | | errPrintln(LocalizableMessage.raw(ere.getMessage())); |
| | | return ere.getResult().getResultCode().intValue(); |
| | | } |
| | | } |
| | | |
| | | try { |
| | | if (!noop.isPresent()) { |
| | | try { |
| | | connection = connectionFactory.getConnection(); |
| | | if (bindRequest != null) { |
| | | printPasswordPolicyResults(this, connection.bind(bindRequest)); |
| | | } |
| | | } catch (final LdapException ere) { |
| | | return printErrorMessage(this, ere); |
| | | } |
| | | } |
| | | |
| | | int result; |
| | | if (rdr == null) { |
| | | for (final String dn : dnStrings) { |
| | |
| | | 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.printPasswordPolicyResults; |
| | | 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; |
| | |
| | | printResult(opType, change.getName().toString(), r); |
| | | return r.getResultCode().intValue(); |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(LDAPModify.this, ere); |
| | | return printErrorMessage(LDAPModify.this, ere); |
| | | } |
| | | } |
| | | return ResultCode.SUCCESS.intValue(); |
| | |
| | | printResult(opType, change.getName().toString(), r); |
| | | return r.getResultCode().intValue(); |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(LDAPModify.this, ere); |
| | | return printErrorMessage(LDAPModify.this, ere); |
| | | } |
| | | } |
| | | return ResultCode.SUCCESS.intValue(); |
| | |
| | | printResult(opType, change.getName().toString(), r); |
| | | return r.getResultCode().intValue(); |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(LDAPModify.this, ere); |
| | | return printErrorMessage(LDAPModify.this, ere); |
| | | } |
| | | } |
| | | return ResultCode.SUCCESS.intValue(); |
| | |
| | | argParser.setShortToolDescription(REF_SHORT_DESC_LDAPMODIFY.get()); |
| | | ConnectionFactoryProvider connectionFactoryProvider; |
| | | ConnectionFactory connectionFactory; |
| | | BindRequest bindRequest; |
| | | |
| | | BooleanArgument continueOnError; |
| | | // TODO: Remove this due to new LDIF reader api? |
| | |
| | | return 0; |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory(); |
| | | bindRequest = connectionFactoryProvider.getBindRequest(); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | | return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue(); |
| | |
| | | return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue(); |
| | | } |
| | | |
| | | // modifyOptions.setShowOperations(noop.isPresent()); |
| | | // modifyOptions.setVerbose(verbose.isPresent()); |
| | | // modifyOptions.setContinueOnError(continueOnError.isPresent()); |
| | | // modifyOptions.setEncoding(encodingStr.getValue()); |
| | | // modifyOptions.setDefaultAdd(defaultAdd.isPresent()); |
| | | |
| | | controls = new LinkedList<>(); |
| | | if (controlStr.isPresent()) { |
| | | for (final String ctrlString : controlStr.getValues()) { |
| | |
| | | controls.add(control); |
| | | } |
| | | |
| | | if (!noop.isPresent()) { |
| | | try { |
| | | connection = connectionFactory.getConnection(); |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(this, ere); |
| | | } |
| | | } |
| | | |
| | | Utils.printPasswordPolicyResults(this, connection); |
| | | |
| | | writer = new LDIFEntryWriter(getOutputStream()); |
| | | final VisitorImpl visitor = new VisitorImpl(); |
| | | ChangeRecordReader reader = null; |
| | | try { |
| | | if (!noop.isPresent()) { |
| | | try { |
| | | connection = connectionFactory.getConnection(); |
| | | if (bindRequest != null) { |
| | | printPasswordPolicyResults(this, connection.bind(bindRequest)); |
| | | } |
| | | } catch (final LdapException ere) { |
| | | return printErrorMessage(this, ere); |
| | | } |
| | | } |
| | | |
| | | if (filename.isPresent()) { |
| | | try { |
| | | reader = new LDIFChangeRecordReader(new FileInputStream(filename.getValue())); |
| | |
| | | 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 com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl; |
| | | import com.forgerock.opendj.util.StaticUtils; |
| | | |
| | | import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage; |
| | | import static com.forgerock.opendj.ldap.tools.Utils.printPasswordPolicyResults; |
| | | import static org.forgerock.util.Utils.*; |
| | | |
| | | import static com.forgerock.opendj.cli.ArgumentConstants.*; |
| | |
| | | argParser.setShortToolDescription(REF_SHORT_DESC_LDAPSEARCH.get()); |
| | | ConnectionFactoryProvider connectionFactoryProvider; |
| | | ConnectionFactory connectionFactory; |
| | | BindRequest bindRequest; |
| | | |
| | | BooleanArgument countEntries; |
| | | BooleanArgument dontWrap; |
| | |
| | | return 0; |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | connectionFactory = connectionFactoryProvider.getUnauthenticatedConnectionFactory(); |
| | | bindRequest = connectionFactoryProvider.getBindRequest(); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | | return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue(); |
| | |
| | | return 0; |
| | | } |
| | | |
| | | Connection connection; |
| | | Connection connection = null; |
| | | try { |
| | | connection = connectionFactory.getConnection(); |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(this, ere); |
| | | } |
| | | if (bindRequest != null) { |
| | | printPasswordPolicyResults(this, connection.bind(bindRequest)); |
| | | } |
| | | |
| | | Utils.printPasswordPolicyResults(this, connection); |
| | | |
| | | try { |
| | | int filterIndex = 0; |
| | | ldifWriter = new LDIFEntryWriter(getOutputStream()).setWrapColumn(wrapColumn); |
| | | final LDAPSearchResultHandler resultHandler = new LDAPSearchResultHandler(); |
| | |
| | | println(); |
| | | } |
| | | } catch (final LdapException ere) { |
| | | return Utils.printErrorMessage(this, ere); |
| | | return printErrorMessage(this, ere); |
| | | } finally { |
| | | closeSilently(ldifWriter, connection); |
| | | } |
| | |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | runner.setBindRequest(connectionFactoryProvider.getBindRequest()); |
| | | runner.validate(); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | |
| | | */ |
| | | package com.forgerock.opendj.ldap.tools; |
| | | |
| | | import static com.forgerock.opendj.ldap.tools.Utils.printErrorMessage; |
| | | import static java.util.Locale.ENGLISH; |
| | | import static java.util.concurrent.TimeUnit.*; |
| | | |
| | |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.util.promise.Promise; |
| | | |
| | | 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 && bindRequest != null) { |
| | | try { |
| | | ac.rebindAsync().getOrThrow(); |
| | | connection.bindAsync(bindRequest).getOrThrow(); |
| | | } catch (final InterruptedException e) { |
| | | // Ignore and check stop requested |
| | | continue; |
| | |
| | | private long maxDurationTime; |
| | | private boolean isAsync; |
| | | private boolean noRebind; |
| | | private BindRequest bindRequest; |
| | | private int statsInterval; |
| | | private final IntegerArgument numThreadsArgument; |
| | | private final IntegerArgument maxDurationArgument; |
| | |
| | | stopRequested = true; |
| | | } catch (final LdapException e) { |
| | | stopRequested = true; |
| | | app.println(LocalizableMessage.raw(e.getResult().getDiagnosticMessage())); |
| | | printErrorMessage(app, e); |
| | | return e.getResult().getResultCode().intValue(); |
| | | } finally { |
| | | closeSilently(connections); |
| | | } |
| | |
| | | return 0; |
| | | } |
| | | |
| | | void setBindRequest(final BindRequest request) { |
| | | this.bindRequest = request; |
| | | } |
| | | |
| | | BindRequest getBindRequest() { |
| | | return bindRequest; |
| | | } |
| | | |
| | | protected void joinAllWorkerThreads() throws InterruptedException { |
| | | for (final Thread t : workerThreads) { |
| | | t.join(); |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Portions Copyright 2014 ForgeRock AS |
| | | * Portions Copyright 2014-2015 ForgeRock AS |
| | | */ |
| | | |
| | | package com.forgerock.opendj.ldap.tools; |
| | |
| | | private boolean supportsGeneratorArgument = true; |
| | | |
| | | PerformanceRunnerOptions(ArgumentParser argParser, ConsoleApplication app) { |
| | | super(); |
| | | this.argParser = argParser; |
| | | this.app = app; |
| | | } |
| | |
| | | } |
| | | |
| | | connectionFactory = connectionFactoryProvider.getAuthenticatedConnectionFactory(); |
| | | runner.setBindRequest(connectionFactoryProvider.getBindRequest()); |
| | | runner.validate(); |
| | | } catch (final ArgumentException ae) { |
| | | argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); |
| | |
| | | */ |
| | | 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 static com.forgerock.opendj.ldap.tools.ToolsMessages.*; |
| | | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | 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, final BindResult result) { |
| | | try { |
| | | final AuthorizationIdentityResponseControl control = result.getControl( |
| | | AuthorizationIdentityResponseControl.DECODER, new DecodeOptions()); |
| | | if (control != null) { |
| | | app.println(INFO_BIND_AUTHZID_RETURNED.get(control.getAuthorizationID())); |
| | | } |
| | | } 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) { |
| | | 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 PasswordExpiredResponseControl control = result.getControl(PasswordExpiredResponseControl.DECODER, |
| | | new DecodeOptions()); |
| | | if (control != null) { |
| | | app.println(INFO_BIND_PASSWORD_EXPIRED.get()); |
| | | } |
| | | } 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 PasswordExpiringResponseControl control = result.getControl(PasswordExpiringResponseControl.DECODER, |
| | | new DecodeOptions()); |
| | | if (control != null) { |
| | | final LocalizableMessage timeString = secondsToTimeString(control.getSecondsUntilExpiration()); |
| | | app.println(INFO_BIND_PASSWORD_EXPIRING.get(timeString)); |
| | | } |
| | | } 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) { |
| | | try { |
| | | final PasswordPolicyResponseControl control = result.getControl(PasswordPolicyResponseControl.DECODER, |
| | | new DecodeOptions()); |
| | | if (control != null) { |
| | | final PasswordPolicyErrorType errorType = control.getErrorType(); |
| | | if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) { |
| | | app.println(INFO_BIND_PASSWORD_EXPIRED.get()); |
| | | } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) { |
| | | app.println(INFO_BIND_ACCOUNT_LOCKED.get()); |
| | | } 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); |
| | | } |
| | | app.println(INFO_BIND_MUST_CHANGE_PASSWORD.get()); |
| | | } |
| | | } catch (final DecodeException e) { |
| | | app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage())); |
| | | |
| | | final PasswordPolicyWarningType warningType = control.getWarningType(); |
| | | if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) { |
| | | final LocalizableMessage timeString = secondsToTimeString(control.getWarningValue()); |
| | | app.println(INFO_BIND_PASSWORD_EXPIRING.get(timeString)); |
| | | } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) { |
| | | app.println(INFO_BIND_GRACE_LOGINS_REMAINING.get(control.getWarningValue())); |
| | | } |
| | | } |
| | | } catch (final DecodeException e) { |
| | | app.errPrintln(ERR_DECODE_CONTROL_FAILURE.get(e.getLocalizedMessage())); |
| | | } |
| | | } |
| | | |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2013-2014 ForgeRock AS. |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.opendj.ldap.tools; |
| | | |
| | |
| | | .getCanonicalPath(); |
| | | argParser.parseArguments(new String[] { "--useStartTLS", "--trustStorePath", trustStorePath }); |
| | | |
| | | ConnectionFactory factory = connectionFactoryProvider.getConnectionFactory(); |
| | | ConnectionFactory factory = connectionFactoryProvider.getUnauthenticatedConnectionFactory(); |
| | | |
| | | assertThat(factory).isNotNull(); |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.json.resource.ResourceException.newResourceException; |
| | | import static org.forgerock.services.context.SecurityContext.AUTHZID_DN; |
| | | import static org.forgerock.services.context.SecurityContext.AUTHZID_ID; |
| | | import static org.forgerock.opendj.ldap.Connections.uncloseable; |
| | |
| | | final char[] password; |
| | | if (headerUsername != null) { |
| | | if (headerPassword == null || headerUsername.isEmpty() || headerPassword.isEmpty()) { |
| | | throw ResourceException.getException(401); |
| | | throw newResourceException(401); |
| | | } |
| | | username = headerUsername; |
| | | password = headerPassword.toCharArray(); |
| | |
| | | final StringTokenizer st = new StringTokenizer(headerAuthorization); |
| | | final String method = st.nextToken(); |
| | | if (method == null || !"BASIC".equalsIgnoreCase(method)) { |
| | | throw ResourceException.getException(401); |
| | | throw newResourceException(401); |
| | | } |
| | | final String b64Credentials = st.nextToken(); |
| | | if (b64Credentials == null) { |
| | | throw ResourceException.getException(401); |
| | | throw newResourceException(401); |
| | | } |
| | | final String credentials = ByteString.valueOfBase64(b64Credentials).toString(); |
| | | final String[] usernameAndPassword = credentials.split(":"); |
| | | if (usernameAndPassword.length != 2) { |
| | | throw ResourceException.getException(401); |
| | | throw newResourceException(401); |
| | | } |
| | | username = usernameAndPassword[0]; |
| | | password = usernameAndPassword[1].toCharArray(); |
| | | } else { |
| | | throw ResourceException.getException(401); |
| | | throw newResourceException(401); |
| | | } |
| | | |
| | | // If we've got here then we have a username and password. |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.json.resource.ResourceException.newResourceException; |
| | | import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | 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 static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.security.GeneralSecurityException; |
| | |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ConnectionException; |
| | | 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.Entry; |
| | |
| | | import org.forgerock.opendj.ldap.schema.AttributeType; |
| | | import org.forgerock.opendj.ldap.schema.Schema; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.time.Duration; |
| | | |
| | | /** Provides core factory methods and builders for constructing LDAP resource collections. */ |
| | | public final class Rest2LDAP { |
| | |
| | | } catch (final Throwable tmp) { |
| | | resourceResultCode = ResourceException.INTERNAL_ERROR; |
| | | } |
| | | return ResourceException.getException(resourceResultCode, t.getMessage(), t); |
| | | return newResourceException(resourceResultCode, t.getMessage(), t); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | private static ConnectionFactory configureConnectionFactory(final JsonValue configuration, |
| | | final ClassLoader providerClassLoader) { |
| | | final long heartBeatIntervalSeconds = configuration.get("heartBeatIntervalSeconds").defaultTo(30L).asLong(); |
| | | final Duration heartBeatInterval = new Duration(Math.max(heartBeatIntervalSeconds, 1L), TimeUnit.SECONDS); |
| | | |
| | | final long heartBeatTimeoutMillis = configuration.get("heartBeatTimeoutMilliSeconds").defaultTo(500L).asLong(); |
| | | final Duration heartBeatTimeout = new Duration(Math.max(heartBeatTimeoutMillis, 100L), TimeUnit.MILLISECONDS); |
| | | |
| | | final Options options = Options.defaultOptions() |
| | | .set(TRANSPORT_PROVIDER_CLASS_LOADER, providerClassLoader) |
| | | .set(HEARTBEAT_ENABLED, true) |
| | | .set(HEARTBEAT_INTERVAL, heartBeatInterval) |
| | | .set(HEARTBEAT_TIMEOUT, heartBeatTimeout) |
| | | // Parse pool parameters, |
| | | final int connectionPoolSize = |
| | | 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); |
| | | |
| | | // 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 = |
| | | final BindRequest bindRequest = |
| | | Requests.newSimpleBindRequest(simple.get("bindDN").required().asString(), |
| | | simple.get("bindPassword").required().asString().toCharArray()); |
| | | options.set(AUTHN_BIND_REQUEST, bindRequest); |
| | | } 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 Options options = Options.defaultOptions().set(PROVIDER_CLASS_LOADER, providerClassLoader); |
| | | if (connectionSecurity != ConnectionSecurity.NONE) { |
| | | try { |
| | | // Configure SSL. |
| | |
| | | break; |
| | | } |
| | | options.set(SSL_CONTEXT, builder.getSSLContext()); |
| | | options.set(USE_STARTTLS, |
| | | connectionSecurity == ConnectionSecurity.STARTTLS); |
| | | options.set(SSL_USE_STARTTLS, |
| | | connectionSecurity == ConnectionSecurity.STARTTLS); |
| | | } catch (GeneralSecurityException | IOException e) { |
| | | // Rethrow as unchecked exception. |
| | | throw new IllegalArgumentException(e); |
| | |
| | | if (!primaryLDAPServers.isList() || primaryLDAPServers.size() == 0) { |
| | | throw new IllegalArgumentException("No primaryLDAPServers"); |
| | | } |
| | | final ConnectionFactory primary = |
| | | parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize, |
| | | heartBeatIntervalSeconds, heartBeatTimeoutMilliSeconds, options); |
| | | final ConnectionFactory primary = parseLDAPServers(primaryLDAPServers, connectionPoolSize, 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; |
| | | secondary = parseLDAPServers(secondaryLDAPServers, connectionPoolSize, 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 Options options) { |
| | | private static ConnectionFactory parseLDAPServers(JsonValue config, int poolSize, Options options) { |
| | | final List<ConnectionFactory> servers = new ArrayList<>(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); |
| | | final ConnectionFactory factory = new LDAPConnectionFactory(host, port, options); |
| | | if (poolSize > 1) { |
| | | servers.add(newCachedConnectionPool(factory, 0, poolSize, 60L, TimeUnit.SECONDS)); |
| | | } else { |
| | | servers.add(factory); |
| | | } |
| | | if (connectionPoolSize > 1) { |
| | | factory = |
| | | Connections.newCachedConnectionPool(factory, 0, connectionPoolSize, 60L, |
| | | TimeUnit.SECONDS); |
| | | } |
| | | servers.add(factory); |
| | | } |
| | | if (servers.size() > 1) { |
| | | return Connections.newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers, |