From fb304c58d6883199ed46df392a9d80c1f7ee1bf6 Mon Sep 17 00:00:00 2001 From: "Neil A. Wilson" Date: Mon, 22 Jan 2018 05:46:10 -0600 Subject: [PATCH] Improved ServerSet health check reliability Updated the LDAP SDK's ServerSet implementations so that they can perform bind and post-connect processing on the connections that they create. Previously, server sets were only intended to establish connections, but not to authenticate them, and not to perform any other post-processing on them (like using the StartTLS extended operation to convert an insecure connection to a secure one). It was up to the caller (which was often a connection pool) to perform that processing once it got the connection back from the server set. However, when asked to create a connection, a server set can be given an LDAPConnectionPoolHealthCheck to use to check the validity of the connection that it has created. If such a health check was provided, then it would have always been invoked on an unauthenticated connection, which could cause problems against servers that do not permit unauthenticated requests. Further, if some post-connect processing (for example, the StartTLS operation) is needed on those connections, then the health checking would have been performed before that processing had been completed, and that could also lead to erroneous behavior. With this update, server sets can now be created so that they themselves perform authentication and post-connect processing on connections before they make those connections available to the caller. More importantly, if provided with a health check, then any appropriate authentication and post-connect processing will have been performed on the connection before the health check is invoked. This makes the health check more reliable because the connection can be in a more useful state. When a server set configured with support for bind and post-connect processing is used in conjunction with a connection pool, the pool will now delegate that processing to the server set. If the associated server set is not configured to perform authentication and post-connect processing, then the connection pool will still perform those tasks. This update also fixes a potential problem that could lead to the connection pool being unable to obtain a connection for processing an operation. Most server set implementations can be used in conjunction with multiple servers, so that if it fails to establish a usable connection to one server, it can try to obtain a connection from a different server and shield the caller from the connection problem. However, this only applies to the portion of the processing that is performed within the server set itself. When the authentication and post-connect processing were left up to the caller of the server set, the caller was more likely to encounter a problem that it could not work around. Now that the additional processing is performed by the server set, its resiliency can be extended to this authentication and post-connect processing. --- docs/release-notes.html | 10 + src/com/unboundid/ldap/sdk/BindResult.java | 10 +- .../ldap/sdk/DNSSRVRecordServerSet.java | 123 +++++++++--- .../unboundid/ldap/sdk/FailoverServerSet.java | 131 ++++++++++-- .../ldap/sdk/FastestConnectServerSet.java | 84 +++++++- .../ldap/sdk/FastestConnectThread.java | 70 ++++--- .../ldap/sdk/FewestConnectionsServerSet.java | 113 +++++++++-- .../ldap/sdk/LDAPConnectionPool.java | 145 +++++++++++--- .../sdk/LDAPThreadLocalConnectionPool.java | 79 +++++--- .../ldap/sdk/RoundRobinDNSServerSet.java | 137 ++++++++++++- .../ldap/sdk/RoundRobinServerSet.java | 108 +++++++--- src/com/unboundid/ldap/sdk/ServerSet.java | 186 +++++++++++++++++- .../unboundid/ldap/sdk/SingleServerSet.java | 91 ++++++++- 13 files changed, 1106 insertions(+), 181 deletions(-) diff --git a/docs/release-notes.html b/docs/release-notes.html index 5520d9374..e27a25e3b 100644 --- a/docs/release-notes.html +++ b/docs/release-notes.html @@ -56,6 +56,16 @@

Version 4.0.4



+
  • + Updated the LDAP SDK's ServerSet implementations so that they can + perform authentication and post-connect processing. This can improve resiliency + against certain types of certain types of processing failures, and can allow + health checks against newly established connections to succeed in certain cases + in which they might have previously failed (for example, if the server is + configured to reject requests from unauthenticated clients). +

    +
  • +
  • Updated the GetEntryLDAPConnectionPoolHealthCheck class to provide support for invoking the health check after a pooled connection has been diff --git a/src/com/unboundid/ldap/sdk/BindResult.java b/src/com/unboundid/ldap/sdk/BindResult.java index 599442685..b159d5680 100644 --- a/src/com/unboundid/ldap/sdk/BindResult.java +++ b/src/com/unboundid/ldap/sdk/BindResult.java @@ -149,7 +149,15 @@ public BindResult(final LDAPException exception) { super(exception.toLDAPResult()); - serverSASLCredentials = null; + if (exception instanceof LDAPBindException) + { + serverSASLCredentials = + ((LDAPBindException) exception).getServerSASLCredentials(); + } + else + { + serverSASLCredentials = null; + } } diff --git a/src/com/unboundid/ldap/sdk/DNSSRVRecordServerSet.java b/src/com/unboundid/ldap/sdk/DNSSRVRecordServerSet.java index e222d8c63..c14d166fc 100644 --- a/src/com/unboundid/ldap/sdk/DNSSRVRecordServerSet.java +++ b/src/com/unboundid/ldap/sdk/DNSSRVRecordServerSet.java @@ -130,6 +130,10 @@ public final class DNSSRVRecordServerSet + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The properties that will be used to initialize the JNDI context. private final Hashtable jndiProperties; @@ -140,6 +144,10 @@ public final class DNSSRVRecordServerSet // information should be considered valid. private final long ttlMillis; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory that should be used to create connections. private final SocketFactory socketFactory; @@ -247,8 +255,65 @@ public DNSSRVRecordServerSet(final String recordName, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) { - this.socketFactory = socketFactory; + this(recordName, providerURL, jndiProperties, ttlMillis, socketFactory, + connectionOptions, null, null); + } + + + + /** + * Creates a new instance of this server set that will use the provided + * settings. + * + * @param recordName The name of the DNS SRV record to retrieve. + * If this is {@code null}, then a default + * record name of "_ldap._tcp" will be used. + * @param providerURL The JNDI provider URL that may be used to + * specify the DNS server(s) to use. If this is + * not specified, then a default URL of + * "dns:" will be used, which will attempt to + * determine the appropriate servers from the + * underlying system configuration. + * @param jndiProperties A set of JNDI-related properties that should + * be be used when initializing the context for + * interacting with the DNS server via JNDI. + * If this is {@code null}, then a default set + * of properties will be used. + * @param ttlMillis Specifies the maximum length of time in + * milliseconds that DNS information should be + * cached before it needs to be retrieved + * again. A value less than or equal to zero + * will use the default TTL of one hour. + * @param socketFactory The socket factory that will be used when + * creating connections. It may be + * {@code null} if the JVM-default socket + * factory should be used. + * @param connectionOptions The set of connection options that should be + * used for the connections that are created. + * It may be {@code null} if the default + * connection options should be used. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public DNSSRVRecordServerSet(final String recordName, + final String providerURL, + final Properties jndiProperties, + final long ttlMillis, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) + { + this.socketFactory = socketFactory; this.connectionOptions = connectionOptions; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; recordSet = null; @@ -388,6 +453,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -439,11 +526,13 @@ public LDAPConnection getConnection( LDAPException firstException = null; for (final SRVRecord r : recordSet.getOrderedRecords()) { - final LDAPConnection conn; try { - conn = new LDAPConnection(socketFactory, connectionOptions, - r.getAddress(), r.getPort()); + final LDAPConnection connection = new LDAPConnection(socketFactory, + connectionOptions, r.getAddress(), r.getPort()); + doBindPostConnectAndHealthCheckProcessing(connection, bindRequest, + postConnectProcessor, healthCheck); + return connection; } catch (final LDAPException le) { @@ -452,29 +541,7 @@ public LDAPConnection getConnection( { firstException = le; } - - continue; } - - if (healthCheck != null) - { - try - { - healthCheck.ensureNewConnectionValid(conn); - } - catch (final LDAPException le) - { - Debug.debugException(le); - if (firstException == null) - { - firstException = le; - } - - continue; - } - } - - return conn; } // If we've gotten here, then we couldn't connect to any of the servers. @@ -510,6 +577,10 @@ public void toString(final StringBuilder buffer) connectionOptions.toString(buffer); } + buffer.append(", includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); buffer.append(')'); } } diff --git a/src/com/unboundid/ldap/sdk/FailoverServerSet.java b/src/com/unboundid/ldap/sdk/FailoverServerSet.java index a57e3ea18..bae1c0607 100644 --- a/src/com/unboundid/ldap/sdk/FailoverServerSet.java +++ b/src/com/unboundid/ldap/sdk/FailoverServerSet.java @@ -31,6 +31,7 @@ import com.unboundid.util.ThreadSafetyLevel; import static com.unboundid.util.Debug.*; +import static com.unboundid.util.StaticUtils.*; import static com.unboundid.util.Validator.*; @@ -253,6 +254,46 @@ public FailoverServerSet(final String[] addresses, final int[] ports, public FailoverServerSet(final String[] addresses, final int[] ports, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(addresses, ports, socketFactory, connectionOptions, null, null); + } + + + + /** + * Creates a new failover server set with the specified set of directory + * server addresses and port numbers. It will use the provided socket factory + * to create the underlying sockets. + * + * @param addresses The addresses of the directory servers to + * which the connections should be established. + * It must not be {@code null} or empty. + * @param ports The ports of the directory servers to which + * the connections should be established. It + * must not be {@code null}, and it must have + * the same number of elements as the + * {@code addresses} array. The order of + * elements in the {@code addresses} array must + * correspond to the order of elements in the + * {@code ports} array. + * @param socketFactory The socket factory to use to create the + * underlying connections. + * @param connectionOptions The set of connection options to use for the + * underlying connections. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public FailoverServerSet(final String[] addresses, final int[] ports, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { ensureNotNull(addresses, ports); ensureTrue(addresses.length > 0, @@ -286,7 +327,8 @@ public FailoverServerSet(final String[] addresses, final int[] ports, serverSets = new ServerSet[addresses.length]; for (int i=0; i < serverSets.length; i++) { - serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co); + serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co, + bindRequest, postConnectProcessor); } } @@ -297,18 +339,16 @@ public FailoverServerSet(final String[] addresses, final int[] ports, * server sets. * * @param serverSets The server sets between which failover should occur. - * It must not be {@code null} or empty. + * It must not be {@code null} or empty. All of the + * provided sets must have the same return value for their + * {@link #includesAuthentication()} method, and all of + * the provided sets must have the same return value for + * their {@link #includesPostConnectProcessing()} + * method. */ public FailoverServerSet(final ServerSet... serverSets) { - ensureNotNull(serverSets); - ensureFalse(serverSets.length == 0, - "FailoverServerSet.serverSets must not be empty."); - - this.serverSets = serverSets; - - reOrderOnFailover = new AtomicBoolean(false); - maxFailoverConnectionAge = null; + this(toList(serverSets)); } @@ -318,7 +358,12 @@ public FailoverServerSet(final ServerSet... serverSets) * server sets. * * @param serverSets The server sets between which failover should occur. - * It must not be {@code null} or empty. + * It must not be {@code null} or empty. All of the + * provided sets must have the same return value for their + * {@link #includesAuthentication()} method, and all of + * the provided sets must have the same return value for + * their {@link #includesPostConnectProcessing()} + * method. */ public FailoverServerSet(final List serverSets) { @@ -329,6 +374,48 @@ public FailoverServerSet(final List serverSets) this.serverSets = new ServerSet[serverSets.size()]; serverSets.toArray(this.serverSets); + boolean anySupportsAuthentication = false; + boolean allSupportAuthentication = true; + boolean anySupportsPostConnectProcessing = false; + boolean allSupportPostConnectProcessing = true; + for (final ServerSet serverSet : this.serverSets) + { + if (serverSet.includesAuthentication()) + { + anySupportsAuthentication = true; + } + else + { + allSupportAuthentication = false; + } + + if (serverSet.includesPostConnectProcessing()) + { + anySupportsPostConnectProcessing = true; + } + else + { + allSupportPostConnectProcessing = false; + } + } + + if (anySupportsAuthentication) + { + ensureTrue(allSupportAuthentication, + "When creating a FailoverServerSet from a collection of server " + + "sets, either all of those sets must include authentication, " + + "or none of those sets may include authentication."); + } + + if (anySupportsPostConnectProcessing) + { + ensureTrue(allSupportPostConnectProcessing, + "When creating a FailoverServerSet from a collection of server " + + "sets, either all of those sets must include post-connect " + + "processing, or none of those sets may include post-connect " + + "processing."); + } + reOrderOnFailover = new AtomicBoolean(false); maxFailoverConnectionAge = null; } @@ -449,6 +536,28 @@ else if (maxFailoverConnectionAge > 0L) + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return serverSets[0].includesAuthentication(); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return serverSets[0].includesPostConnectProcessing(); + } + + + /** * {@inheritDoc} */ diff --git a/src/com/unboundid/ldap/sdk/FastestConnectServerSet.java b/src/com/unboundid/ldap/sdk/FastestConnectServerSet.java index 76998a205..95ab3ffbd 100644 --- a/src/com/unboundid/ldap/sdk/FastestConnectServerSet.java +++ b/src/com/unboundid/ldap/sdk/FastestConnectServerSet.java @@ -93,12 +93,20 @@ public final class FastestConnectServerSet extends ServerSet { + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The port numbers of the target servers. private final int[] ports; // The set of connection options to use for new connections. private final LDAPConnectionOptions connectionOptions; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory to use to establish connections. private final SocketFactory socketFactory; @@ -204,6 +212,46 @@ public FastestConnectServerSet(final String[] addresses, final int[] ports, public FastestConnectServerSet(final String[] addresses, final int[] ports, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(addresses, ports, socketFactory, connectionOptions, null, null); + } + + + + /** + * Creates a new fastest connect server set with the specified set of + * directory server addresses and port numbers. It will use the provided + * socket factory to create the underlying sockets. + * + * @param addresses The addresses of the directory servers to + * which the connections should be established. + * It must not be {@code null} or empty. + * @param ports The ports of the directory servers to which + * the connections should be established. It + * must not be {@code null}, and it must have + * the same number of elements as the + * {@code addresses} array. The order of + * elements in the {@code addresses} array must + * correspond to the order of elements in the + * {@code ports} array. + * @param socketFactory The socket factory to use to create the + * underlying connections. + * @param connectionOptions The set of connection options to use for the + * underlying connections. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public FastestConnectServerSet(final String[] addresses, final int[] ports, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { Validator.ensureNotNull(addresses, ports); Validator.ensureTrue(addresses.length > 0, @@ -213,7 +261,9 @@ public FastestConnectServerSet(final String[] addresses, final int[] ports, "size."); this.addresses = addresses; - this.ports = ports; + this.ports = ports; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; if (socketFactory == null) { @@ -290,6 +340,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -325,8 +397,8 @@ public LDAPConnection getConnection( for (int i=0; i < connectThreads.length; i++) { connectThreads[i] = new FastestConnectThread(addresses[i], ports[i], - socketFactory, connectionOptions, healthCheck, resultQueue, - connectionSelected); + socketFactory, connectionOptions, bindRequest, postConnectProcessor, + healthCheck, resultQueue, connectionSelected); } for (final FastestConnectThread t : connectThreads) @@ -428,6 +500,10 @@ public void toString(final StringBuilder buffer) buffer.append(ports[i]); } - buffer.append("})"); + buffer.append("}, includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); + buffer.append(')'); } } diff --git a/src/com/unboundid/ldap/sdk/FastestConnectThread.java b/src/com/unboundid/ldap/sdk/FastestConnectThread.java index 2a52a22d1..a7b785efe 100644 --- a/src/com/unboundid/ldap/sdk/FastestConnectThread.java +++ b/src/com/unboundid/ldap/sdk/FastestConnectThread.java @@ -44,6 +44,10 @@ final class FastestConnectThread // been selected by the server set. private final AtomicBoolean connectionSelected; + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The queue that should be used to return the result to the server set. private final BlockingQueue resultQueue; @@ -57,6 +61,10 @@ final class FastestConnectThread // connection. private final LDAPConnectionPoolHealthCheck healthCheck; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The address to which the connection should be established. private final String address; @@ -66,27 +74,37 @@ final class FastestConnectThread * Creates a new instance of this connect thread with the provided * information. * - * @param address The address of the server to which the - * connection should be established. - * @param port The port of the server to which the connection - * should be established. - * @param socketFactory The socket factory that should be used for the - * connection. - * @param connectionOptions The set of connection options that should be - * used for the connection. - * @param healthCheck The health check to use to evaluate the - * suitability of the established connection. It - * may be {@code null} if no health check is - * needed. - * @param resultQueue The queue that should be used to return the - * result to the server set. - * @param connectionSelected A flag that will be used to indicate whether a - * connection has already been selected by the - * associated server set. + * @param address The address of the server to which the + * connection should be established. + * @param port The port of the server to which the + * connection should be established. + * @param socketFactory The socket factory that should be used for + * the connection. + * @param connectionOptions The set of connection options that should be + * used for the connection. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + * @param healthCheck The health check to use to evaluate the + * suitability of the established connection. + * It may be {@code null} if no health check is + * needed. + * @param resultQueue The queue that should be used to return the + * result to the server set. + * @param connectionSelected A flag that will be used to indicate whether + * a connection has already been selected by the + * associated server set. */ FastestConnectThread(final String address, final int port, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor, final LDAPConnectionPoolHealthCheck healthCheck, final BlockingQueue resultQueue, final AtomicBoolean connectionSelected) @@ -94,10 +112,12 @@ final class FastestConnectThread super("Fastest Connect Thread for " + address + ':' + port); setDaemon(true); - this.address = address; - this.port = port; - this.healthCheck = healthCheck; - this.resultQueue = resultQueue; + this.address = address; + this.port = port; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; + this.healthCheck = healthCheck; + this.resultQueue = resultQueue; this.connectionSelected = connectionSelected; connection = new LDAPConnection(socketFactory, connectionOptions); @@ -117,12 +137,8 @@ public void run() try { connection.connect(address, port); - - if (healthCheck != null) - { - healthCheck.ensureNewConnectionValid(connection); - } - + ServerSet.doBindPostConnectAndHealthCheckProcessing(connection, + bindRequest, postConnectProcessor, healthCheck); returned = (connectionSelected.compareAndSet(false, true) && resultQueue.offer(connection)); } diff --git a/src/com/unboundid/ldap/sdk/FewestConnectionsServerSet.java b/src/com/unboundid/ldap/sdk/FewestConnectionsServerSet.java index 4c80b5098..bc3864bd8 100644 --- a/src/com/unboundid/ldap/sdk/FewestConnectionsServerSet.java +++ b/src/com/unboundid/ldap/sdk/FewestConnectionsServerSet.java @@ -94,6 +94,10 @@ public final class FewestConnectionsServerSet extends ServerSet { + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The port numbers of the target servers. private final int[] ports; @@ -104,6 +108,10 @@ public final class FewestConnectionsServerSet // set. private final List establishedConnections; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory to use to establish connections. private final SocketFactory socketFactory; @@ -209,6 +217,46 @@ public FewestConnectionsServerSet(final String[] addresses, final int[] ports, public FewestConnectionsServerSet(final String[] addresses, final int[] ports, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(addresses, ports, socketFactory, connectionOptions, null, null); + } + + + + /** + * Creates a new fewest connections server set with the specified set of + * directory server addresses and port numbers. It will use the provided + * socket factory to create the underlying sockets. + * + * @param addresses The addresses of the directory servers to + * which the connections should be established. + * It must not be {@code null} or empty. + * @param ports The ports of the directory servers to which + * the connections should be established. It + * must not be {@code null}, and it must have + * the same number of elements as the + * {@code addresses} array. The order of + * elements in the {@code addresses} array must + * correspond to the order of elements in the + * {@code ports} array. + * @param socketFactory The socket factory to use to create the + * underlying connections. + * @param connectionOptions The set of connection options to use for the + * underlying connections. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public FewestConnectionsServerSet(final String[] addresses, final int[] ports, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { ensureNotNull(addresses, ports); ensureTrue(addresses.length > 0, @@ -218,7 +266,9 @@ public FewestConnectionsServerSet(final String[] addresses, final int[] ports, "be the same size."); this.addresses = addresses; - this.ports = ports; + this.ports = ports; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; establishedConnections = new ArrayList(100); @@ -297,6 +347,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -381,20 +453,8 @@ public synchronized LDAPConnection getConnection( { final LDAPConnection conn = new LDAPConnection(socketFactory, connectionOptions, p.getFirst(), p.getSecond()); - if (healthCheck != null) - { - try - { - healthCheck.ensureNewConnectionValid(conn); - } - catch (final LDAPException le) - { - debugException(le); - conn.close(); - throw le; - } - } - + doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, + postConnectProcessor, healthCheck); establishedConnections.add(conn); return conn; } @@ -434,6 +494,27 @@ public void toString(final StringBuilder buffer) buffer.append(ports[i]); } - buffer.append("})"); + buffer.append("}, includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); + buffer.append(", establishedConnections="); + + synchronized (this) + { + final Iterator iterator = + establishedConnections.iterator(); + while (iterator.hasNext()) + { + final LDAPConnection conn = iterator.next(); + if (! conn.isConnected()) + { + iterator.remove(); + } + } + buffer.append(establishedConnections.size()); + } + + buffer.append(')'); } } diff --git a/src/com/unboundid/ldap/sdk/LDAPConnectionPool.java b/src/com/unboundid/ldap/sdk/LDAPConnectionPool.java index e1a792319..c2087cacc 100644 --- a/src/com/unboundid/ldap/sdk/LDAPConnectionPool.java +++ b/src/com/unboundid/ldap/sdk/LDAPConnectionPool.java @@ -626,7 +626,9 @@ public LDAPConnectionPool(final LDAPConnection connection, "LDAPConnectionPool.initialConnections must not be greater " + "than maxConnections."); - this.postConnectProcessor = postConnectProcessor; + // NOTE: The post-connect processor (if any) will be used in the server + // set that we create rather than in the connection pool itself. + this.postConnectProcessor = null; trySynchronousReadDuringHealthCheck = true; healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; @@ -656,11 +658,12 @@ public LDAPConnectionPool(final LDAPConnection connection, } + bindRequest = connection.getLastBindRequest(); serverSet = new SingleServerSet(connection.getConnectedAddress(), connection.getConnectedPort(), connection.getLastUsedSocketFactory(), - connection.getConnectionOptions()); - bindRequest = connection.getLastBindRequest(); + connection.getConnectionOptions(), + bindRequest, postConnectProcessor); final LDAPConnectionOptions opts = connection.getConnectionOptions(); if (opts.usePooledSchema()) @@ -773,7 +776,15 @@ public LDAPConnectionPool(final LDAPConnection connection, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if the + * server set is configured to perform + * authentication, this bind request should be the + * same bind request used by the server set. This is + * important because even though the server set may + * be used to perform the initial authentication on a + * newly established connection, this connection + * pool may still need to re-authenticate the + * connection. * @param numConnections The total number of connections that should be * created in the pool. It must be greater than or * equal to one. @@ -804,7 +815,15 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if the + * server set is configured to perform + * authentication, this bind request should be the + * same bind request used by the server set. + * This is important because even though the + * server set may be used to perform the initial + * authentication on a newly established + * connection, this connection pool may still + * need to re-authenticate the connection. * @param initialConnections The number of connections to initially * establish when the pool is created. It must be * greater than or equal to zero. @@ -844,7 +863,15 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if + * the server set is configured to perform + * authentication, this bind request should be + * the same bind request used by the server set. + * This is important because even though the + * server set may be used to perform the initial + * authentication on a newly established + * connection, this connection pool may still + * need to re-authenticate the connection. * @param initialConnections The number of connections to initially * establish when the pool is created. It must * be greater than or equal to zero. @@ -859,7 +886,11 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param postConnectProcessor A processor that should be used to perform * any post-connect processing for connections * in this pool. It may be {@code null} if no - * special processing is needed. + * special processing is needed. Note that if + * the server set is configured with a + * non-{@code null} post-connect processor, then + * the post-connect processor provided to the + * pool must be {@code null}. * * @throws LDAPException If a problem occurs while attempting to establish * any of the connections. If this is thrown, then @@ -890,7 +921,16 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if + * the server set is configured to perform + * authentication, this bind request should be + * the same bind request used by the server + * set. This is important because even + * though the server set may be used to + * perform the initial authentication on a + * newly established connection, this + * connection pool may still need to + * re-authenticate the connection. * @param initialConnections The number of connections to initially * establish when the pool is created. It must * be greater than or equal to zero. @@ -905,7 +945,11 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param postConnectProcessor A processor that should be used to perform * any post-connect processing for connections * in this pool. It may be {@code null} if no - * special processing is needed. + * special processing is needed. Note that if + * the server set is configured with a + * non-{@code null} post-connect processor, + * then the post-connect processor provided + * to the pool must be {@code null}. * @param throwOnConnectFailure If an exception should be thrown if a * problem is encountered while attempting to * create the specified initial number of @@ -947,7 +991,16 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if + * the server set is configured to perform + * authentication, this bind request should be + * the same bind request used by the server + * set. This is important because even + * though the server set may be used to + * perform the initial authentication on a + * newly established connection, this + * connection pool may still need to + * re-authenticate the connection. * @param initialConnections The number of connections to initially * establish when the pool is created. It must * be greater than or equal to zero. @@ -967,7 +1020,11 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param postConnectProcessor A processor that should be used to perform * any post-connect processing for connections * in this pool. It may be {@code null} if no - * special processing is needed. + * special processing is needed. Note that if + * the server set is configured with a + * non-{@code null} post-connect processor, + * then the post-connect processor provided + * to the pool must be {@code null}. * @param throwOnConnectFailure If an exception should be thrown if a * problem is encountered while attempting to * create the specified initial number of @@ -1011,7 +1068,16 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if + * the server set is configured to perform + * authentication, this bind request should be + * the same bind request used by the server + * set. This is important because even + * though the server set may be used to + * perform the initial authentication on a + * newly established connection, this + * connection pool may still need to + * re-authenticate the connection. * @param initialConnections The number of connections to initially * establish when the pool is created. It must * be greater than or equal to zero. @@ -1031,7 +1097,11 @@ public LDAPConnectionPool(final ServerSet serverSet, * @param postConnectProcessor A processor that should be used to perform * any post-connect processing for connections * in this pool. It may be {@code null} if no - * special processing is needed. + * special processing is needed. Note that if + * the server set is configured with a + * non-{@code null} post-connect processor, + * then the post-connect processor provided + * to the pool must be {@code null}. * @param throwOnConnectFailure If an exception should be thrown if a * problem is encountered while attempting to * create the specified initial number of @@ -1076,6 +1146,20 @@ public LDAPConnectionPool(final ServerSet serverSet, this.bindRequest = bindRequest; this.postConnectProcessor = postConnectProcessor; + if (serverSet.includesAuthentication()) + { + ensureTrue((bindRequest != null), + "LDAPConnectionPool.bindRequest must not be null if " + + "serverSet.includesAuthentication returns true"); + } + + if (serverSet.includesPostConnectProcessing()) + { + ensureTrue((postConnectProcessor == null), + "LDAPConnectionPool.postConnectProcessor must be null if " + + "serverSet.includesPostConnectProcessing returns true."); + } + trySynchronousReadDuringHealthCheck = false; healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; poolStatistics = new LDAPConnectionPoolStatistics(this); @@ -1259,30 +1343,31 @@ private LDAPConnection createConnection( // Authenticate the connection if appropriate. - BindResult bindResult = null; - try + if ((bindRequest != null) && (! serverSet.includesAuthentication())) { - if (bindRequest != null) + BindResult bindResult; + try { bindResult = c.bind(bindRequest.duplicate()); } - } - catch (final LDAPBindException lbe) - { - debugException(lbe); - bindResult = lbe.getBindResult(); - } - catch (final LDAPException le) - { - debugException(le); - bindResult = new BindResult(le); - } + catch (final LDAPBindException lbe) + { + debugException(lbe); + bindResult = lbe.getBindResult(); + } + catch (final LDAPException le) + { + debugException(le); + bindResult = new BindResult(le); + } - if (bindResult != null) - { try { - healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult); + if (healthCheck != null) + { + healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult); + } + if (bindResult.getResultCode() != ResultCode.SUCCESS) { throw new LDAPBindException(bindResult); diff --git a/src/com/unboundid/ldap/sdk/LDAPThreadLocalConnectionPool.java b/src/com/unboundid/ldap/sdk/LDAPThreadLocalConnectionPool.java index 1bf751a98..dcdcf19ea 100644 --- a/src/com/unboundid/ldap/sdk/LDAPThreadLocalConnectionPool.java +++ b/src/com/unboundid/ldap/sdk/LDAPThreadLocalConnectionPool.java @@ -197,7 +197,9 @@ public LDAPThreadLocalConnectionPool(final LDAPConnection connection, { ensureNotNull(connection); - this.postConnectProcessor = postConnectProcessor; + // NOTE: The post-connect processor (if any) will be used in the server + // set that we create rather than in the connection pool itself. + this.postConnectProcessor = null; healthCheck = new LDAPConnectionPoolHealthCheck(); healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; @@ -213,11 +215,12 @@ public LDAPThreadLocalConnectionPool(final LDAPConnection connection, } + bindRequest = connection.getLastBindRequest(); serverSet = new SingleServerSet(connection.getConnectedAddress(), connection.getConnectedPort(), connection.getLastUsedSocketFactory(), - connection.getConnectionOptions()); - bindRequest = connection.getLastBindRequest(); + connection.getConnectionOptions(), + bindRequest, postConnectProcessor); connections = new ConcurrentHashMap(); connections.put(Thread.currentThread(), connection); @@ -272,7 +275,15 @@ public LDAPThreadLocalConnectionPool(final LDAPConnection connection, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if the + * server set is configured to perform + * authentication, this bind request should be the + * same bind request used by the server set. This + * is important because even though the server set + * may be used to perform the initial authentication + * on a newly established connection, this connection + * pool may still need to re-authenticate the + * connection. */ public LDAPThreadLocalConnectionPool(final ServerSet serverSet, final BindRequest bindRequest) @@ -293,11 +304,24 @@ public LDAPThreadLocalConnectionPool(final ServerSet serverSet, * @param bindRequest The bind request to use to authenticate the * connections that are established. It may be * {@code null} if no authentication should be - * performed on the connections. + * performed on the connections. Note that if + * the server set is configured to perform + * authentication, this bind request should be + * the same bind request used by the server set. + * This is important because even though the + * server set may be used to perform the + * initial authentication on a newly + * established connection, this connection + * pool may still need to re-authenticate the + * connection. * @param postConnectProcessor A processor that should be used to perform * any post-connect processing for connections * in this pool. It may be {@code null} if no - * special processing is needed. + * special processing is needed. Note that if + * the server set is configured with a + * non-{@code null} post-connect processor, then + * the post-connect processor provided to the + * pool must be {@code null}. */ public LDAPThreadLocalConnectionPool(final ServerSet serverSet, final BindRequest bindRequest, @@ -309,6 +333,20 @@ public LDAPThreadLocalConnectionPool(final ServerSet serverSet, this.bindRequest = bindRequest; this.postConnectProcessor = postConnectProcessor; + if (serverSet.includesAuthentication()) + { + ensureTrue((bindRequest != null), + "LDAPThreadLocalConnectionPool.bindRequest must not be null if " + + "serverSet.includesAuthentication returns true"); + } + + if (serverSet.includesPostConnectProcessing()) + { + ensureTrue((postConnectProcessor == null), + "LDAPThreadLocalConnectionPool.postConnectProcessor must be null " + + "if serverSet.includesPostConnectProcessing returns true."); + } + healthCheck = new LDAPConnectionPoolHealthCheck(); healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; poolStatistics = new LDAPConnectionPoolStatistics(this); @@ -403,27 +441,24 @@ private LDAPConnection createConnection() // Authenticate the connection if appropriate. - BindResult bindResult = null; - try + if ((bindRequest != null) && (! serverSet.includesAuthentication())) { - if (bindRequest != null) + BindResult bindResult; + try { bindResult = c.bind(bindRequest.duplicate()); } - } - catch (final LDAPBindException lbe) - { - debugException(lbe); - bindResult = lbe.getBindResult(); - } - catch (final LDAPException le) - { - debugException(le); - bindResult = new BindResult(le); - } + catch (final LDAPBindException lbe) + { + debugException(lbe); + bindResult = lbe.getBindResult(); + } + catch (final LDAPException le) + { + debugException(le); + bindResult = new BindResult(le); + } - if (bindResult != null) - { try { healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult); diff --git a/src/com/unboundid/ldap/sdk/RoundRobinDNSServerSet.java b/src/com/unboundid/ldap/sdk/RoundRobinDNSServerSet.java index 9d833f8d1..a3597de61 100644 --- a/src/com/unboundid/ldap/sdk/RoundRobinDNSServerSet.java +++ b/src/com/unboundid/ldap/sdk/RoundRobinDNSServerSet.java @@ -155,6 +155,10 @@ public enum AddressSelectionMode private final AtomicReference> resolvedAddressesWithTimeout; + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The properties that will be used to initialize the JNDI context, if any. private final Hashtable jndiProperties; @@ -167,6 +171,10 @@ public enum AddressSelectionMode // The maximum length of time, in milliseconds, to cache resolved addresses. private final long cacheTimeoutMillis; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory to use to establish connections. private final SocketFactory socketFactory; @@ -302,15 +310,106 @@ public RoundRobinDNSServerSet(final String hostname, final int port, final String[] dnsRecordTypes, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL, + jndiProperties, dnsRecordTypes, socketFactory, connectionOptions, null, + null); + } + + + + /** + * Creates a new round-robin DNS server set with the provided information. + * + * @param hostname The hostname to be resolved to one or more + * addresses. It must not be {@code null}. + * @param port The port to use to connect to the server. + * Note that even if the provided hostname + * resolves to multiple addresses, the same + * port must be used for all addresses. + * @param selectionMode The selection mode that should be used if the + * hostname resolves to multiple addresses. It + * must not be {@code null}. + * @param cacheTimeoutMillis The maximum length of time in milliseconds to + * cache addresses resolved from the provided + * hostname. Caching resolved addresses can + * result in better performance and can reduce + * the number of requests to the name service. + * A that is less than or equal to zero + * indicates that no caching should be used. + * @param providerURL The JNDI provider URL that should be used + * when communicating with the DNS server. If + * both {@code providerURL} and + * {@code jndiProperties} are {@code null}, + * then then JNDI will not be used to interact + * with DNS and the hostname resolution will be + * performed via the underlying system's name + * service mechanism (which may make use of + * other services instead of or in addition to + * DNS). If this is non-{@code null}, then only + * DNS will be used to perform the name + * resolution. A value of "dns:" indicates that + * the underlying system's DNS configuration + * should be used. + * @param jndiProperties A set of JNDI-related properties that should + * be used when initializing the context for + * interacting with the DNS server via JNDI. If + * both {@code providerURL} and + * {@code jndiProperties} are {@code null}, then + * JNDI will not be used to interact with DNS + * and the hostname resolution will be + * performed via the underlying system's name + * service mechanism (which may make use of + * other services instead of or in addition to + * DNS). If {@code providerURL} is + * {@code null} and {@code jndiProperties} is + * non-{@code null}, then the provided + * properties must specify the URL. + * @param dnsRecordTypes Specifies the types of DNS records that will + * be used to obtain the addresses for the + * specified hostname. This will only be used + * if at least one of {@code providerURL} and + * {@code jndiProperties} is non-{@code null}. + * If this is {@code null} or empty, then a + * default record type of "A" (indicating IPv4 + * addresses) will be used. + * @param socketFactory The socket factory to use to establish the + * connections. It may be {@code null} if the + * JVM-default socket factory should be used. + * @param connectionOptions The set of connection options that should be + * used for the connections. It may be + * {@code null} if a default set of connection + * options should be used. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public RoundRobinDNSServerSet(final String hostname, final int port, + final AddressSelectionMode selectionMode, + final long cacheTimeoutMillis, + final String providerURL, + final Properties jndiProperties, + final String[] dnsRecordTypes, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { Validator.ensureNotNull(hostname); Validator.ensureTrue((port >= 1) && (port <= 65535)); Validator.ensureNotNull(selectionMode); - this.hostname = hostname; - this.port = port; + this.hostname = hostname; + this.port = port; this.selectionMode = selectionMode; - this.providerURL = providerURL; + this.providerURL = providerURL; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; if (jndiProperties == null) { @@ -526,6 +625,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -557,10 +678,8 @@ public synchronized LDAPConnection getConnection( { conn.connect(hostname, a, port, connectionOptions.getConnectTimeoutMillis()); - if (healthCheck != null) - { - healthCheck.ensureNewConnectionValid(conn); - } + doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, + postConnectProcessor, healthCheck); close = false; return conn; } @@ -824,6 +943,10 @@ public void toString(final StringBuilder buffer) buffer.append('\''); } + buffer.append(", includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); buffer.append(')'); } } diff --git a/src/com/unboundid/ldap/sdk/RoundRobinServerSet.java b/src/com/unboundid/ldap/sdk/RoundRobinServerSet.java index 91ad659ca..89d6efe77 100644 --- a/src/com/unboundid/ldap/sdk/RoundRobinServerSet.java +++ b/src/com/unboundid/ldap/sdk/RoundRobinServerSet.java @@ -85,12 +85,20 @@ public final class RoundRobinServerSet extends ServerSet { + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The port numbers of the target servers. private final int[] ports; // The set of connection options to use for new connections. private final LDAPConnectionOptions connectionOptions; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory to use to establish connections. private final SocketFactory socketFactory; @@ -200,6 +208,46 @@ public RoundRobinServerSet(final String[] addresses, final int[] ports, public RoundRobinServerSet(final String[] addresses, final int[] ports, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(addresses, ports, socketFactory, connectionOptions, null, null); + } + + + + /** + * Creates a new round robin server set with the specified set of directory + * server addresses and port numbers. It will use the provided socket factory + * to create the underlying sockets. + * + * @param addresses The addresses of the directory servers to + * which the connections should be established. + * It must not be {@code null} or empty. + * @param ports The ports of the directory servers to which + * the connections should be established. It + * must not be {@code null}, and it must have + * the same number of elements as the + * {@code addresses} array. The order of + * elements in the {@code addresses} array must + * correspond to the order of elements in the + * {@code ports} array. + * @param socketFactory The socket factory to use to create the + * underlying connections. + * @param connectionOptions The set of connection options to use for the + * underlying connections. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public RoundRobinServerSet(final String[] addresses, final int[] ports, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { ensureNotNull(addresses, ports); ensureTrue(addresses.length > 0, @@ -209,7 +257,9 @@ public RoundRobinServerSet(final String[] addresses, final int[] ports, "same size."); this.addresses = addresses; - this.ports = ports; + this.ports = ports; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; if (socketFactory == null) { @@ -288,6 +338,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -320,18 +392,8 @@ public synchronized LDAPConnection getConnection( final LDAPConnection c = new LDAPConnection(socketFactory, connectionOptions, addresses[initialSlotNumber], ports[initialSlotNumber]); - if (healthCheck != null) - { - try - { - healthCheck.ensureNewConnectionValid(c); - } - catch (final LDAPException le) - { - c.close(); - throw le; - } - } + doBindPostConnectAndHealthCheckProcessing(c, bindRequest, + postConnectProcessor, healthCheck); return c; } catch (final LDAPException le) @@ -351,18 +413,8 @@ public synchronized LDAPConnection getConnection( { final LDAPConnection c = new LDAPConnection(socketFactory, connectionOptions, addresses[slotNumber], ports[slotNumber]); - if (healthCheck != null) - { - try - { - healthCheck.ensureNewConnectionValid(c); - } - catch (final LDAPException le2) - { - c.close(); - throw le2; - } - } + doBindPostConnectAndHealthCheckProcessing(c, bindRequest, + postConnectProcessor, healthCheck); return c; } catch (final LDAPException le2) @@ -400,6 +452,10 @@ public void toString(final StringBuilder buffer) buffer.append(ports[i]); } - buffer.append("})"); + buffer.append("}, includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); + buffer.append(')'); } } diff --git a/src/com/unboundid/ldap/sdk/ServerSet.java b/src/com/unboundid/ldap/sdk/ServerSet.java index 873d468e1..3f396b588 100644 --- a/src/com/unboundid/ldap/sdk/ServerSet.java +++ b/src/com/unboundid/ldap/sdk/ServerSet.java @@ -55,11 +55,44 @@ protected ServerSet() + /** + * Indicates whether connections created by this server set will be + * authenticated. + * + * @return {@code true} if connections created by this server set will be + * authenticated, or {@code false} if not. + */ + public boolean includesAuthentication() + { + return false; + } + + + + /** + * Indicates whether connections created by this server set will have + * post-connect processing performed. + * + * @return {@code true} if connections created by this server set will have + * post-connect processing performed, or {@code false} if not. + */ + public boolean includesPostConnectProcessing() + { + return false; + } + + + /** * Attempts to establish a connection to one of the directory servers in this - * server set. The connection should be established but unauthenticated. The - * caller may determine the server to which the connection is established - * using the {@link LDAPConnection#getConnectedAddress} and + * server set. The connection that is returned must be established. The + * {@link #includesAuthentication()} must return true if and only if the + * connection will also be authenticated, and the + * {@link #includesPostConnectProcessing()} method must return true if and + * only if pre-authentication and post-authentication post-connect processing + * will have been performed. The caller may determine the server to which the + * connection is established using the + * {@link LDAPConnection#getConnectedAddress} and * {@link LDAPConnection#getConnectedPort} methods. * * @return An {@code LDAPConnection} object that is established to one of the @@ -76,14 +109,29 @@ public abstract LDAPConnection getConnection() /** * Attempts to establish a connection to one of the directory servers in this * server set, using the provided health check to further validate the - * connection. The connection should be established but unauthenticated. - * The caller may determine the server to which the connection is established - * using the {@link LDAPConnection#getConnectedAddress} and + * connection. The connection that is returned must be established. The + * {@link #includesAuthentication()} must return true if and only if the + * connection will also be authenticated, and the + * {@link #includesPostConnectProcessing()} method must return true if and + * only if pre-authentication and post-authentication post-connect processing + * will have been performed. The caller may determine the server to which the + * connection is established using the + * {@link LDAPConnection#getConnectedAddress} and * {@link LDAPConnection#getConnectedPort} methods. * - * @param healthCheck The health check to use to make the determination, or - * {@code null} if no additional health check should be - * performed. + * @param healthCheck The health check to use to verify the health of the + * newly-created connection. It may be {@code null} if + * no additional health check should be performed. If it + * is non-{@code null} and this server set performs + * authentication, then the health check's + * {@code ensureConnectionValidAfterAuthentication} + * method will be invoked immediately after the bind + * operation is processed (regardless of whether the bind + * was successful or not). And regardless of whether + * this server set performs authentication, the + * health check's {@code ensureNewConnectionValid} + * method must be invoked on the connection to ensure + * that it is valid immediately before it is returned. * * @return An {@code LDAPConnection} object that is established to one of the * servers in this server set. @@ -116,6 +164,126 @@ public LDAPConnection getConnection( + /** + * Performs the appropriate bind, post-connect, and health check processing + * for the provided connection, in the provided order. The processing + * performed will include: + *
      + *
    1. + * If the provided {@code postConnectProcessor} is not {@code null}, then + * invoke its {@code processPreAuthenticatedConnection} method on the + * provided connection. If this method throws an {@code LDAPException}, + * then it will propagated up to the caller of this method. + *
    2. + *
    3. + * If the provided {@code bindRequest} is not {@code null}, then + * authenticate the connection using that request. If the provided + * {@code healthCheck} is also not {@code null}, then invoke its + * {@code ensureConnectionValidAfterAuthentication} method on the + * connection, even if the bind was not successful. If the health check + * throws an {@code LDAPException}, then it will be propagated up to the + * caller of this method. If there is no health check or if it did not + * throw an exception but the bind attempt did throw an exception, then + * propagate that exception instead. + *
    4. + *
    5. + * If the provided {@code postConnectProcessor} is not {@code null}, then + * invoke its {@code processPostAuthenticatedConnection} method on the + * provided connection. If this method throws an {@code LDAPException}, + * then it will propagated up to the caller of this method. + *
    6. + *
    7. + * If the provided {@code healthCheck} is not {@code null}, then invoke + * its {@code ensureNewConnectionValid} method on the provided connection. + * If this method throws an {@code LDAPException}, then it will be + * propagated up to the caller of this method. + *
    8. + *
    + * + * @param connection The connection to be processed. It must not + * be {@code null}, and it must be established. + * Note that if an {@code LDAPException} is + * thrown by this method or anything that it + * calls, then the connection will have been + * closed before that exception has been + * propagated up to the caller of this method. + * @param bindRequest The bind request to use to authenticate the + * connection. It may be {@code null} if no + * authentication should be performed. + * @param postConnectProcessor The post-connect processor to invoke on the + * provided connection. It may be {@code null} + * if no post-connect processing should be + * performed. + * @param healthCheck The health check to use to verify the health + * of connection. It may be {@code null} if no + * health check processing should be performed. + * + * @throws LDAPException If a problem is encountered during any of the + * processing performed by this method. If an + * exception is thrown, then the provided connection + * will have been closed. + */ + protected static void doBindPostConnectAndHealthCheckProcessing( + final LDAPConnection connection, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor, + final LDAPConnectionPoolHealthCheck healthCheck) + throws LDAPException + { + try + { + if (postConnectProcessor != null) + { + postConnectProcessor.processPreAuthenticatedConnection(connection); + } + + if (bindRequest != null) + { + BindResult bindResult; + LDAPException bindException = null; + try + { + bindResult = connection.bind(bindRequest.duplicate()); + } + catch (final LDAPException le) + { + debugException(le); + bindException = le; + bindResult = new BindResult(le); + } + + if (healthCheck != null) + { + healthCheck.ensureConnectionValidAfterAuthentication(connection, + bindResult); + } + + if (bindException != null) + { + throw bindException; + } + } + + if (postConnectProcessor != null) + { + postConnectProcessor.processPostAuthenticatedConnection(connection); + } + + if (healthCheck != null) + { + healthCheck.ensureNewConnectionValid(connection); + } + } + catch (final LDAPException le) + { + debugException(le); + connection.closeWithoutUnbind(); + throw le; + } + } + + + /** * Retrieves a string representation of this server set. * diff --git a/src/com/unboundid/ldap/sdk/SingleServerSet.java b/src/com/unboundid/ldap/sdk/SingleServerSet.java index 0ed3e1fcd..53d6d750f 100644 --- a/src/com/unboundid/ldap/sdk/SingleServerSet.java +++ b/src/com/unboundid/ldap/sdk/SingleServerSet.java @@ -42,12 +42,20 @@ public final class SingleServerSet extends ServerSet { + // The bind request to use to authenticate connections created by this + // server set. + private final BindRequest bindRequest; + // The port number of the target server. private final int port; // The set of connection options to use. private final LDAPConnectionOptions connectionOptions; + // The post-connect processor to invoke against connections created by this + // server set. + private final PostConnectProcessor postConnectProcessor; + // The socket factory to use to establish connections. private final SocketFactory socketFactory; @@ -136,13 +144,49 @@ public SingleServerSet(final String address, final int port, public SingleServerSet(final String address, final int port, final SocketFactory socketFactory, final LDAPConnectionOptions connectionOptions) + { + this(address, port, socketFactory, connectionOptions, null, null); + } + + + + /** + * Creates a new single server set with the specified address and port, and + * using the provided socket factory. + * + * @param address The address of the directory server to which + * the connections should be established. It + * must not be {@code null}. + * @param port The port of the directory server to which the + * connections should be established. It must + * be between 1 and 65535, inclusive. + * @param socketFactory The socket factory to use to create the + * underlying connections. + * @param connectionOptions The set of connection options to use for the + * underlying connections. + * @param bindRequest The bind request that should be used to + * authenticate newly-established connections. + * It may be {@code null} if this server set + * should not perform any authentication. + * @param postConnectProcessor The post-connect processor that should be + * invoked on newly-established connections. It + * may be {@code null} if this server set should + * not perform any post-connect processing. + */ + public SingleServerSet(final String address, final int port, + final SocketFactory socketFactory, + final LDAPConnectionOptions connectionOptions, + final BindRequest bindRequest, + final PostConnectProcessor postConnectProcessor) { ensureNotNull(address); ensureTrue((port > 0) && (port < 65536), "SingleServerSet.port must be between 1 and 65535."); this.address = address; - this.port = port; + this.port = port; + this.bindRequest = bindRequest; + this.postConnectProcessor = postConnectProcessor; if (socketFactory == null) { @@ -219,6 +263,28 @@ public LDAPConnectionOptions getConnectionOptions() + /** + * {@inheritDoc} + */ + @Override() + public boolean includesAuthentication() + { + return (bindRequest != null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public boolean includesPostConnectProcessing() + { + return (postConnectProcessor != null); + } + + + /** * {@inheritDoc} */ @@ -226,7 +292,24 @@ public LDAPConnectionOptions getConnectionOptions() public LDAPConnection getConnection() throws LDAPException { - return new LDAPConnection(socketFactory, connectionOptions, address, port); + return getConnection(null); + } + + + + /** + * {@inheritDoc} + */ + @Override() + public LDAPConnection getConnection( + final LDAPConnectionPoolHealthCheck healthCheck) + throws LDAPException + { + final LDAPConnection connection = + new LDAPConnection(socketFactory, connectionOptions, address, port); + doBindPostConnectAndHealthCheckProcessing(connection, bindRequest, + postConnectProcessor, healthCheck); + return connection; } @@ -241,6 +324,10 @@ public void toString(final StringBuilder buffer) buffer.append(address); buffer.append(':'); buffer.append(port); + buffer.append(", includesAuthentication="); + buffer.append(bindRequest != null); + buffer.append(", includesPostConnectProcessing="); + buffer.append(postConnectProcessor != null); buffer.append(')'); } }