package com.hwlcn.ldap.ldap.sdk;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.hwlcn.ldap.ldap.protocol.LDAPResponse;
import com.hwlcn.ldap.ldap.sdk.schema.Schema;
import com.hwlcn.ldap.util.ObjectPair;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*;
import static com.hwlcn.ldap.util.Debug.*;
import static com.hwlcn.ldap.util.StaticUtils.*;
import static com.hwlcn.ldap.util.Validator.*;
/**
* This class provides an implementation of an LDAP connection pool, which is a
* structure that can hold multiple connections established to a given server
* that can be reused for multiple operations rather than creating and
* destroying connections for each operation. This connection pool
* implementation provides traditional methods for checking out and releasing
* connections, but it also provides wrapper methods that make it easy to
* perform operations using pooled connections without the need to explicitly
* check out or release the connections.
* <BR><BR>
* Note that both the {@code LDAPConnectionPool} class and the
* {@link com.hwlcn.ldap.ldap.sdk.LDAPConnection} class implement the {@link com.hwlcn.ldap.ldap.sdk.LDAPInterface} interface.
* This is a common interface that defines a number of common methods for
* processing LDAP requests. This means that in many cases, an application can
* use an object of type {@link com.hwlcn.ldap.ldap.sdk.LDAPInterface} rather than
* {@link com.hwlcn.ldap.ldap.sdk.LDAPConnection}, which makes it possible to work with either a single
* standalone connection or with a connection pool.
* <BR><BR>
* <H2>Creating a Connection Pool</H2>
* An LDAP connection pool can be created from either a single
* {@link com.hwlcn.ldap.ldap.sdk.LDAPConnection} (for which an appropriate number of copies will be
* created to fill out the pool) or using a {@link com.hwlcn.ldap.ldap.sdk.ServerSet} to create
* connections that may span multiple servers. For example:
* <BR><BR>
* <PRE>
* // Create a new LDAP connection pool with ten connections established and
* // authenticated to the same server:
* LDAPConnection connection = new LDAPConnection(address, port);
* BindResult bindResult = connection.bind(bindDN, password);
* LDAPConnectionPool connectionPool = new LDAPConnectionPool(connection, 10);
*
* // Create a new LDAP connection pool with 10 connections spanning multiple
* // servers using a server set.
* RoundRobinServerSet serverSet = new RoundRobinServerSet(addresses, ports);
* SimpleBindRequest bindRequest = new SimpleBindRequest(bindDN, password);
* LDAPConnectionPool connectionPool =
* new LDAPConnectionPool(serverSet, bindRequest, 10);
* </PRE>
* Note that in some cases, such as when using StartTLS, it may be necessary to
* perform some additional processing when a new connection is created for use
* in the connection pool. In this case, a {@link PostConnectProcessor} should
* be provided to accomplish this. See the documentation for the
* {@link StartTLSPostConnectProcessor} class for an example that demonstrates
* its use for creating a connection pool with connections secured using
* StartTLS.
* <BR><BR>
* <H2>Processing Operations with a Connection Pool</H2>
* If a single operation is to be processed using a connection from the
* connection pool, then it can be used without the need to check out or release
* a connection or perform any validity checking on the connection. This can
* be accomplished via the {@link com.hwlcn.ldap.ldap.sdk.LDAPInterface} interface that allows a
* connection pool to be treated like a single connection. For example, to
* perform a search using a pooled connection:
* <PRE>
* SearchResult searchResult =
* connectionPool.search("dc=example,dc=com", SearchScope.SUB,
* "(uid=john.doe)");
* </PRE>
* If an application needs to process multiple operations using a single
* connection, then it may be beneficial to obtain a connection from the pool
* to use for processing those operations and then return it back to the pool
* when it is no longer needed. This can be done using the
* {@link #getConnection} and {@link #releaseConnection} methods. If during
* processing it is determined that the connection is no longer valid, then the
* connection should be released back to the pool using the
* {@link #releaseDefunctConnection} method, which will ensure that the
* connection is closed and a new connection will be established to take its
* place in the pool.
* <BR><BR>
* Note that it is also possible to process multiple operations on a single
* connection using the {@link #processRequests} method. This may be useful if
* a fixed set of operations should be processed over the same connection and
* none of the subsequent requests depend upon the results of the earlier
* operations.
* <BR><BR>
* Connection pools should generally not be used when performing operations that
* may change the state of the underlying connections. This is particularly
* true for bind operations and the StartTLS extended operation, but it may
* apply to other types of operations as well.
* <BR><BR>
* Performing a bind operation using a connection from the pool will invalidate
* any previous authentication on that connection, and if that connection is
* released back to the pool without first being re-authenticated as the
* original user, then subsequent operation attempts may fail or be processed in
* an incorrect manner. Bind operations should only be performed in a
* connection pool if the pool is to be used exclusively for processing binds,
* if the bind request is specially crafted so that it will not change the
* identity of the associated connection (e.g., by including the retain identity
* request control in the bind request if using the Commercial Edition of the
* LDAP SDK with an UnboundID Directory Server), or if the code using the
* connection pool makes sure to re-authenticate the connection as the
* appropriate user whenever its identity has been changed.
* <BR><BR>
* The StartTLS extended operation should never be invoked on a connection which
* is part of a connection pool. It is acceptable for the pool to maintain
* connections which have been configured with StartTLS security prior to being
* added to the pool (via the use of the {@link StartTLSPostConnectProcessor}).
*/
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class LDAPConnectionPool
extends AbstractConnectionPool
{
private static final long DEFAULT_HEALTH_CHECK_INTERVAL = 60000L;
private final AtomicInteger failedReplaceCount;
private final AtomicReference<Set<OperationType>> retryOperationTypes;
private volatile boolean closed;
private boolean createIfNecessary;
private volatile boolean trySynchronousReadDuringHealthCheck;
private final BindRequest bindRequest;
private final int numConnections;
private LDAPConnectionPoolHealthCheck healthCheck;
private final LDAPConnectionPoolHealthCheckThread healthCheckThread;
private final LDAPConnectionPoolStatistics poolStatistics;
private final LinkedBlockingQueue<LDAPConnection> availableConnections;
private volatile long healthCheckInterval;
private volatile long lastExpiredDisconnectTime;
private volatile long maxConnectionAge;
private long maxWaitTime;
private volatile long minDisconnectInterval;
private volatile ObjectPair<Long,Schema> pooledSchema;
private final PostConnectProcessor postConnectProcessor;
private final ServerSet serverSet;
private String connectionPoolName;
public LDAPConnectionPool(final LDAPConnection connection,
final int numConnections)
throws LDAPException
{
this(connection, 1, numConnections, null);
}
public LDAPConnectionPool(final LDAPConnection connection,
final int initialConnections,
final int maxConnections)
throws LDAPException
{
this(connection, initialConnections, maxConnections, null);
}
public LDAPConnectionPool(final LDAPConnection connection,
final int initialConnections,
final int maxConnections,
final PostConnectProcessor postConnectProcessor)
throws LDAPException
{
this(connection, initialConnections, maxConnections, postConnectProcessor,
true);
}
public LDAPConnectionPool(final LDAPConnection connection,
final int initialConnections,
final int maxConnections,
final PostConnectProcessor postConnectProcessor,
final boolean throwOnConnectFailure)
throws LDAPException
{
this(connection, initialConnections, maxConnections, 1,
postConnectProcessor, throwOnConnectFailure);
}
public LDAPConnectionPool(final LDAPConnection connection,
final int initialConnections,
final int maxConnections,
final int initialConnectThreads,
final PostConnectProcessor postConnectProcessor,
final boolean throwOnConnectFailure)
throws LDAPException
{
ensureNotNull(connection);
ensureTrue(initialConnections >= 1,
"LDAPConnectionPool.initialConnections must be at least 1.");
ensureTrue(maxConnections >= initialConnections,
"LDAPConnectionPool.initialConnections must not be greater " +
"than maxConnections.");
this.postConnectProcessor = postConnectProcessor;
trySynchronousReadDuringHealthCheck = true;
healthCheck = new LDAPConnectionPoolHealthCheck();
healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;
poolStatistics = new LDAPConnectionPoolStatistics(this);
pooledSchema = null;
connectionPoolName = null;
retryOperationTypes = new AtomicReference<Set<OperationType>>(
Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
numConnections = maxConnections;
availableConnections =
new LinkedBlockingQueue<LDAPConnection>(numConnections);
if (! connection.isConnected())
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_POOL_CONN_NOT_ESTABLISHED.get());
}
serverSet = new SingleServerSet(connection.getConnectedAddress(),
connection.getConnectedPort(),
connection.getLastUsedSocketFactory(),
connection.getConnectionOptions());
bindRequest = connection.getLastBindRequest();
final LDAPConnectionOptions opts = connection.getConnectionOptions();
if (opts.usePooledSchema())
{
try
{
final Schema schema = connection.getSchema();
if (schema != null)
{
connection.setCachedSchema(schema);
final long currentTime = System.currentTimeMillis();
final long timeout = opts.getPooledSchemaTimeoutMillis();
if ((timeout <= 0L) || (timeout+currentTime <= 0L))
{
pooledSchema = new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
}
else
{
pooledSchema =
new ObjectPair<Long,Schema>(timeout+currentTime, schema);
}
}
}
catch (final Exception e)
{
debugException(e);
}
}
final List<LDAPConnection> connList;
if (initialConnectThreads > 1)
{
connList = Collections.synchronizedList(
new ArrayList<LDAPConnection>(initialConnections));
final ParallelPoolConnector connector = new ParallelPoolConnector(this,
connList, initialConnections, initialConnectThreads,
throwOnConnectFailure);
connector.establishConnections();
}
else
{
connList = new ArrayList<LDAPConnection>(initialConnections);
connection.setConnectionName(null);
connection.setConnectionPool(this);
connList.add(connection);
for (int i=1; i < initialConnections; i++)
{
try
{
connList.add(createConnection());
}
catch (LDAPException le)
{
debugException(le);
if (throwOnConnectFailure)
{
for (final LDAPConnection c : connList)
{
try
{
c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null,
le);
c.terminate(null);
}
catch (Exception e)
{
debugException(e);
}
}
throw le;
}
}
}
}
availableConnections.addAll(connList);
failedReplaceCount =
new AtomicInteger(maxConnections - availableConnections.size());
createIfNecessary = true;
maxConnectionAge = 0L;
minDisconnectInterval = 0L;
lastExpiredDisconnectTime = 0L;
maxWaitTime = 5000L;
closed = false;
healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
healthCheckThread.start();
}
public LDAPConnectionPool(final ServerSet serverSet,
final BindRequest bindRequest,
final int numConnections)
throws LDAPException
{
this(serverSet, bindRequest, 1, numConnections, null);
}
public LDAPConnectionPool(final ServerSet serverSet,
final BindRequest bindRequest,
final int initialConnections,
final int maxConnections)
throws LDAPException
{
this(serverSet, bindRequest, initialConnections, maxConnections, null);
}
public LDAPConnectionPool(final ServerSet serverSet,
final BindRequest bindRequest,
final int initialConnections,
final int maxConnections,
final PostConnectProcessor postConnectProcessor)
throws LDAPException
{
this(serverSet, bindRequest, initialConnections, maxConnections,
postConnectProcessor, true);
}
public LDAPConnectionPool(final ServerSet serverSet,
final BindRequest bindRequest,
final int initialConnections,
final int maxConnections,
final PostConnectProcessor postConnectProcessor,
final boolean throwOnConnectFailure)
throws LDAPException
{
this(serverSet, bindRequest, initialConnections, maxConnections, 1,
postConnectProcessor, throwOnConnectFailure);
}
public LDAPConnectionPool(final ServerSet serverSet,
final BindRequest bindRequest,
final int initialConnections,
final int maxConnections,
final int initialConnectThreads,
final PostConnectProcessor postConnectProcessor,
final boolean throwOnConnectFailure)
throws LDAPException
{
ensureNotNull(serverSet);
ensureTrue(initialConnections >= 0,
"LDAPConnectionPool.initialConnections must be greater than " +
"or equal to 0.");
ensureTrue(maxConnections > 0,
"LDAPConnectionPool.maxConnections must be greater than 0.");
ensureTrue(maxConnections >= initialConnections,
"LDAPConnectionPool.initialConnections must not be greater " +
"than maxConnections.");
this.serverSet = serverSet;
this.bindRequest = bindRequest;
this.postConnectProcessor = postConnectProcessor;
healthCheck = new LDAPConnectionPoolHealthCheck();
healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;
poolStatistics = new LDAPConnectionPoolStatistics(this);
connectionPoolName = null;
retryOperationTypes = new AtomicReference<Set<OperationType>>(
Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
final List<LDAPConnection> connList;
if (initialConnectThreads > 1)
{
connList = Collections.synchronizedList(
new ArrayList<LDAPConnection>(initialConnections));
final ParallelPoolConnector connector = new ParallelPoolConnector(this,
connList, initialConnections, initialConnectThreads,
throwOnConnectFailure);
connector.establishConnections();
}
else
{
connList = new ArrayList<LDAPConnection>(initialConnections);
for (int i=0; i < initialConnections; i++)
{
try
{
connList.add(createConnection());
}
catch (LDAPException le)
{
debugException(le);
if (throwOnConnectFailure)
{
for (final LDAPConnection c : connList)
{
try
{
c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null,
le);
c.terminate(null);
} catch (Exception e)
{
debugException(e);
}
}
throw le;
}
}
}
}
numConnections = maxConnections;
availableConnections =
new LinkedBlockingQueue<LDAPConnection>(numConnections);
availableConnections.addAll(connList);
failedReplaceCount =
new AtomicInteger(maxConnections - availableConnections.size());
createIfNecessary = true;
maxConnectionAge = 0L;
minDisconnectInterval = 0L;
lastExpiredDisconnectTime = 0L;
maxWaitTime = 5000L;
closed = false;
healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
healthCheckThread.start();
}
LDAPConnection createConnection()
throws LDAPException
{
final LDAPConnection c = serverSet.getConnection(healthCheck);
c.setConnectionPool(this);
LDAPConnectionOptions opts = c.getConnectionOptions();
if (opts.autoReconnect())
{
opts = opts.duplicate();
opts.setAutoReconnect(false);
c.setConnectionOptions(opts);
}
if (postConnectProcessor != null)
{
try
{
postConnectProcessor.processPreAuthenticatedConnection(c);
}
catch (Exception e)
{
debugException(e);
try
{
poolStatistics.incrementNumFailedConnectionAttempts();
c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
c.terminate(null);
}
catch (Exception e2)
{
debugException(e2);
}
if (e instanceof LDAPException)
{
throw ((LDAPException) e);
}
else
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
}
}
}
try
{
if (bindRequest != null)
{
c.bind(bindRequest.duplicate());
}
}
catch (Exception e)
{
debugException(e);
try
{
poolStatistics.incrementNumFailedConnectionAttempts();
c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e);
c.terminate(null);
}
catch (Exception e2)
{
debugException(e2);
}
if (e instanceof LDAPException)
{
throw ((LDAPException) e);
}
else
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_CONNECT_ERROR.get(getExceptionMessage(e)), e);
}
}
if (postConnectProcessor != null)
{
try
{
postConnectProcessor.processPostAuthenticatedConnection(c);
}
catch (Exception e)
{
debugException(e);
try
{
poolStatistics.incrementNumFailedConnectionAttempts();
c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
c.terminate(null);
}
catch (Exception e2)
{
debugException(e2);
}
if (e instanceof LDAPException)
{
throw ((LDAPException) e);
}
else
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
}
}
}
if (opts.usePooledSchema())
{
final long currentTime = System.currentTimeMillis();
if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst()))
{
try
{
final Schema schema = c.getSchema();
if (schema != null)
{
c.setCachedSchema(schema);
final long timeout = opts.getPooledSchemaTimeoutMillis();
if ((timeout <= 0L) || (currentTime + timeout <= 0L))
{
pooledSchema =
new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
}
else
{
pooledSchema =
new ObjectPair<Long,Schema>((currentTime+timeout), schema);
}
}
}
catch (final Exception e)
{
debugException(e);
if (pooledSchema != null)
{
c.setCachedSchema(pooledSchema.getSecond());
}
}
}
else
{
c.setCachedSchema(pooledSchema.getSecond());
}
}
c.setConnectionPoolName(connectionPoolName);
poolStatistics.incrementNumSuccessfulConnectionAttempts();
return c;
}
@Override()
public void close()
{
close(true, 1);
}
@Override()
public void close(final boolean unbind, final int numThreads)
{
closed = true;
healthCheckThread.stopRunning();
if (numThreads > 1)
{
final ArrayList<LDAPConnection> connList =
new ArrayList<LDAPConnection>(availableConnections.size());
availableConnections.drainTo(connList);
final ParallelPoolCloser closer =
new ParallelPoolCloser(connList, unbind, numThreads);
closer.closeConnections();
}
else
{
while (true)
{
final LDAPConnection conn = availableConnections.poll();
if (conn == null)
{
return;
}
else
{
poolStatistics.incrementNumConnectionsClosedUnneeded();
conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
if (unbind)
{
conn.terminate(null);
}
else
{
conn.setClosed();
}
}
}
}
}
@Override()
public boolean isClosed()
{
return closed;
}
public BindResult bindAndRevertAuthentication(final String bindDN,
final String password,
final Control... controls)
throws LDAPException
{
return bindAndRevertAuthentication(
new SimpleBindRequest(bindDN, password, controls));
}
public BindResult bindAndRevertAuthentication(final BindRequest bindRequest)
throws LDAPException
{
LDAPConnection conn = getConnection();
try
{
final BindResult result = conn.bind(bindRequest);
releaseAndReAuthenticateConnection(conn);
return result;
}
catch (final Throwable t)
{
debugException(t);
if (t instanceof LDAPException)
{
final LDAPException le = (LDAPException) t;
boolean shouldThrow;
try
{
healthCheck.ensureConnectionValidAfterException(conn, le);
releaseAndReAuthenticateConnection(conn);
shouldThrow = true;
}
catch (final Exception e)
{
debugException(e);
if (! getOperationTypesToRetryDueToInvalidConnections().contains(
OperationType.BIND))
{
releaseDefunctConnection(conn);
shouldThrow = true;
}
else
{
shouldThrow = false;
}
}
if (shouldThrow)
{
throw le;
}
}
else
{
releaseDefunctConnection(conn);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
}
}
conn = replaceDefunctConnection(conn);
try
{
final BindResult result = conn.bind(bindRequest);
releaseAndReAuthenticateConnection(conn);
return result;
}
catch (final Throwable t)
{
debugException(t);
if (t instanceof LDAPException)
{
final LDAPException le = (LDAPException) t;
try
{
healthCheck.ensureConnectionValidAfterException(conn, le);
releaseAndReAuthenticateConnection(conn);
}
catch (final Exception e)
{
debugException(e);
releaseDefunctConnection(conn);
}
throw le;
}
else
{
releaseDefunctConnection(conn);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
}
}
}
@Override()
public LDAPConnection getConnection()
throws LDAPException
{
if (closed)
{
poolStatistics.incrementNumFailedCheckouts();
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_CLOSED.get());
}
LDAPConnection conn = availableConnections.poll();
if (conn != null)
{
if (conn.isConnected())
{
try
{
healthCheck.ensureConnectionValidForCheckout(conn);
poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
return conn;
}
catch (LDAPException le)
{
debugException(le);
}
}
handleDefunctConnection(conn);
for (int i=0; i < numConnections; i++)
{
conn = availableConnections.poll();
if (conn == null)
{
break;
}
else if (conn.isConnected())
{
try
{
healthCheck.ensureConnectionValidForCheckout(conn);
poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
return conn;
}
catch (LDAPException le)
{
debugException(le);
handleDefunctConnection(conn);
}
}
else
{
handleDefunctConnection(conn);
}
}
}
if (failedReplaceCount.get() > 0)
{
final int newReplaceCount = failedReplaceCount.getAndDecrement();
if (newReplaceCount > 0)
{
try
{
conn = createConnection();
poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
return conn;
}
catch (LDAPException le)
{
debugException(le);
failedReplaceCount.incrementAndGet();
poolStatistics.incrementNumFailedCheckouts();
throw le;
}
}
else
{
failedReplaceCount.incrementAndGet();
poolStatistics.incrementNumFailedCheckouts();
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_NO_CONNECTIONS.get());
}
}
if (maxWaitTime > 0)
{
try
{
conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS);
if (conn != null)
{
try
{
healthCheck.ensureConnectionValidForCheckout(conn);
poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting();
return conn;
}
catch (LDAPException le)
{
debugException(le);
handleDefunctConnection(conn);
}
}
}
catch (InterruptedException ie)
{
debugException(ie);
}
}
if (createIfNecessary)
{
try
{
conn = createConnection();
poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
return conn;
}
catch (LDAPException le)
{
debugException(le);
poolStatistics.incrementNumFailedCheckouts();
throw le;
}
}
else
{
poolStatistics.incrementNumFailedCheckouts();
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_POOL_NO_CONNECTIONS.get());
}
}
@Override()
public void releaseConnection(final LDAPConnection connection)
{
if (connection == null)
{
return;
}
connection.setConnectionPoolName(connectionPoolName);
if (connectionIsExpired(connection))
{
try
{
final LDAPConnection newConnection = createConnection();
if (availableConnections.offer(newConnection))
{
connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
null, null);
connection.terminate(null);
poolStatistics.incrementNumConnectionsClosedExpired();
lastExpiredDisconnectTime = System.currentTimeMillis();
return;
}
else
{
newConnection.setDisconnectInfo(
DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
newConnection.terminate(null);
poolStatistics.incrementNumConnectionsClosedUnneeded();
}
}
catch (final LDAPException le)
{
debugException(le);
}
}
try
{
healthCheck.ensureConnectionValidForRelease(connection);
}
catch (LDAPException le)
{
releaseDefunctConnection(connection);
return;
}
if (availableConnections.offer(connection))
{
poolStatistics.incrementNumReleasedValid();
}
else
{
connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
null, null);
poolStatistics.incrementNumConnectionsClosedUnneeded();
connection.terminate(null);
return;
}
if (closed)
{
close();
}
}
public void releaseAndReAuthenticateConnection(
final LDAPConnection connection)
{
if (connection == null)
{
return;
}
try
{
if (bindRequest == null)
{
connection.bind("", "");
}
else
{
connection.bind(bindRequest);
}
releaseConnection(connection);
}
catch (final Exception e)
{
debugException(e);
releaseDefunctConnection(connection);
}
}
@Override()
public void releaseDefunctConnection(final LDAPConnection connection)
{
if (connection == null)
{
return;
}
connection.setConnectionPoolName(connectionPoolName);
poolStatistics.incrementNumConnectionsClosedDefunct();
handleDefunctConnection(connection);
}
private LDAPConnection handleDefunctConnection(
final LDAPConnection connection)
{
connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
null);
connection.terminate(null);
if (closed)
{
return null;
}
if (createIfNecessary && (availableConnections.remainingCapacity() <= 0))
{
return null;
}
try
{
final LDAPConnection conn = createConnection();
if (! availableConnections.offer(conn))
{
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
null, null);
conn.terminate(null);
return null;
}
return conn;
}
catch (LDAPException le)
{
debugException(le);
failedReplaceCount.incrementAndGet();
return null;
}
}
@Override()
public LDAPConnection replaceDefunctConnection(
final LDAPConnection connection)
throws LDAPException
{
connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
null);
connection.terminate(null);
if (closed)
{
throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
}
return createConnection();
}
@Override()
public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
{
return retryOperationTypes.get();
}
@Override()
public void setRetryFailedOperationsDueToInvalidConnections(
final Set<OperationType> operationTypes)
{
if ((operationTypes == null) || operationTypes.isEmpty())
{
retryOperationTypes.set(
Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
}
else
{
final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
s.addAll(operationTypes);
retryOperationTypes.set(Collections.unmodifiableSet(s));
}
}
private boolean connectionIsExpired(final LDAPConnection connection)
{
if (maxConnectionAge <= 0L)
{
return false;
}
final long currentTime = System.currentTimeMillis();
if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
{
return false;
}
final long connectionAge = currentTime - connection.getConnectTime();
return (connectionAge > maxConnectionAge);
}
@Override()
public String getConnectionPoolName()
{
return connectionPoolName;
}
@Override()
public void setConnectionPoolName(final String connectionPoolName)
{
this.connectionPoolName = connectionPoolName;
for (final LDAPConnection c : availableConnections)
{
c.setConnectionPoolName(connectionPoolName);
}
}
public boolean getCreateIfNecessary()
{
return createIfNecessary;
}
public void setCreateIfNecessary(final boolean createIfNecessary)
{
this.createIfNecessary = createIfNecessary;
}
public long getMaxWaitTimeMillis()
{
return maxWaitTime;
}
public void setMaxWaitTimeMillis(final long maxWaitTime)
{
if (maxWaitTime > 0L)
{
this.maxWaitTime = maxWaitTime;
}
else
{
this.maxWaitTime = 0L;
}
}
public long getMaxConnectionAgeMillis()
{
return maxConnectionAge;
}
public void setMaxConnectionAgeMillis(final long maxConnectionAge)
{
if (maxConnectionAge > 0L)
{
this.maxConnectionAge = maxConnectionAge;
}
else
{
this.maxConnectionAge = 0L;
}
}
public long getMinDisconnectIntervalMillis()
{
return minDisconnectInterval;
}
public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
{
if (minDisconnectInterval > 0)
{
this.minDisconnectInterval = minDisconnectInterval;
}
else
{
this.minDisconnectInterval = 0L;
}
}
@Override()
public LDAPConnectionPoolHealthCheck getHealthCheck()
{
return healthCheck;
}
public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
{
ensureNotNull(healthCheck);
this.healthCheck = healthCheck;
}
@Override()
public long getHealthCheckIntervalMillis()
{
return healthCheckInterval;
}
@Override()
public void setHealthCheckIntervalMillis(final long healthCheckInterval)
{
ensureTrue(healthCheckInterval > 0L,
"LDAPConnectionPool.healthCheckInterval must be greater than 0.");
this.healthCheckInterval = healthCheckInterval;
healthCheckThread.wakeUp();
}
public boolean trySynchronousReadDuringHealthCheck()
{
return trySynchronousReadDuringHealthCheck;
}
public void setTrySynchronousReadDuringHealthCheck(
final boolean trySynchronousReadDuringHealthCheck)
{
this.trySynchronousReadDuringHealthCheck =
trySynchronousReadDuringHealthCheck;
}
@Override()
protected void doHealthCheck()
{
final HashSet<LDAPConnection> examinedConnections =
new HashSet<LDAPConnection>(numConnections);
for (int i=0; i < numConnections; i++)
{
LDAPConnection conn = availableConnections.poll();
if (conn == null)
{
break;
}
else if (examinedConnections.contains(conn))
{
if (! availableConnections.offer(conn))
{
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
null, null);
poolStatistics.incrementNumConnectionsClosedUnneeded();
conn.terminate(null);
}
break;
}
if (! conn.isConnected())
{
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
}
else
{
if (connectionIsExpired(conn))
{
try
{
final LDAPConnection newConnection = createConnection();
if (availableConnections.offer(newConnection))
{
examinedConnections.add(newConnection);
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
null, null);
conn.terminate(null);
poolStatistics.incrementNumConnectionsClosedExpired();
lastExpiredDisconnectTime = System.currentTimeMillis();
continue;
}
else
{
newConnection.setDisconnectInfo(
DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
newConnection.terminate(null);
poolStatistics.incrementNumConnectionsClosedUnneeded();
}
}
catch (final LDAPException le)
{
debugException(le);
}
}
if (trySynchronousReadDuringHealthCheck && conn.synchronousMode())
{
int previousTimeout = Integer.MIN_VALUE;
Socket s = null;
try
{
s = conn.getConnectionInternals(true).getSocket();
previousTimeout = s.getSoTimeout();
s.setSoTimeout(1);
final LDAPResponse response = conn.readResponse(0);
if (response instanceof ConnectionClosedResponse)
{
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
poolStatistics.incrementNumConnectionsClosedDefunct();
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
continue;
}
else if (response instanceof ExtendedResult)
{
final UnsolicitedNotificationHandler h = conn.
getConnectionOptions().getUnsolicitedNotificationHandler();
if (h != null)
{
h.handleUnsolicitedNotification(conn,
(ExtendedResult) response);
}
}
else if (response instanceof LDAPResult)
{
final LDAPResult r = (LDAPResult) response;
if (r.getResultCode() == ResultCode.SERVER_DOWN)
{
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
poolStatistics.incrementNumConnectionsClosedDefunct();
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
continue;
}
}
}
catch (final LDAPException le)
{
if (le.getResultCode() == ResultCode.TIMEOUT)
{
debugException(Level.FINEST, le);
}
else
{
debugException(le);
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
getExceptionMessage(le)), le);
poolStatistics.incrementNumConnectionsClosedDefunct();
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
continue;
}
}
catch (final Exception e)
{
debugException(e);
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(getExceptionMessage(e)),
e);
poolStatistics.incrementNumConnectionsClosedDefunct();
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
continue;
}
finally
{
if (previousTimeout != Integer.MIN_VALUE)
{
try
{
s.setSoTimeout(previousTimeout);
}
catch (final Exception e)
{
debugException(e);
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
null, e);
poolStatistics.incrementNumConnectionsClosedDefunct();
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
continue;
}
}
}
}
try
{
healthCheck.ensureConnectionValidForContinuedUse(conn);
if (availableConnections.offer(conn))
{
examinedConnections.add(conn);
}
else
{
conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
null, null);
poolStatistics.incrementNumConnectionsClosedUnneeded();
conn.terminate(null);
}
}
catch (Exception e)
{
debugException(e);
conn = handleDefunctConnection(conn);
if (conn != null)
{
examinedConnections.add(conn);
}
}
}
}
}
@Override()
public int getCurrentAvailableConnections()
{
return availableConnections.size();
}
@Override()
public int getMaximumAvailableConnections()
{
return numConnections;
}
@Override()
public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
{
return poolStatistics;
}
@Override()
protected void finalize()
throws Throwable
{
super.finalize();
close();
}
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("LDAPConnectionPool(");
final String name = connectionPoolName;
if (name != null)
{
buffer.append("name='");
buffer.append(name);
buffer.append("', ");
}
buffer.append("serverSet=");
serverSet.toString(buffer);
buffer.append(", maxConnections=");
buffer.append(numConnections);
buffer.append(')');
}
}