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(')'); } }