+ 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