All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.LDAPThreadLocalConnectionPool Maven / Gradle / Ivy

/*
 * Copyright 2009-2020 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2009-2020 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2009-2020 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk;



import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;

import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.Debug;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;

import static com.unboundid.ldap.sdk.LDAPMessages.*;



/**
 * This class provides an implementation of an LDAP connection pool which
 * maintains a dedicated connection for each thread using the connection pool.
 * Connections will be created on an on-demand basis, so that if a thread
 * attempts to use this connection pool for the first time then a new connection
 * will be created by that thread.  This implementation eliminates the need to
 * determine how best to size the connection pool, and it can eliminate
 * contention among threads when trying to access a shared set of connections.
 * All connections will be properly closed when the connection pool itself is
 * closed, but if any thread which had previously used the connection pool stops
 * running before the connection pool is closed, then the connection associated
 * with that thread will also be closed by the Java finalizer.
 * 

* If a thread obtains a connection to this connection pool, then that * connection should not be made available to any other thread. Similarly, if * a thread attempts to check out multiple connections from the pool, then the * same connection instance will be returned each time. *

* The capabilities offered by this class are generally the same as those * provided by the {@link LDAPConnectionPool} class, as is the manner in which * applications should interact with it. See the class-level documentation for * the {@code LDAPConnectionPool} class for additional information and examples. *

* One difference between this connection pool implementation and that provided * by the {@link LDAPConnectionPool} class is that this implementation does not * currently support periodic background health checks. You can define health * checks that will be invoked when a new connection is created, just before it * is checked out for use, just after it is released, and if an error occurs * while using the connection, but it will not maintain a separate background * thread */ @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class LDAPThreadLocalConnectionPool extends AbstractConnectionPool { /** * The default health check interval for this connection pool, which is set to * 60000 milliseconds (60 seconds). */ private static final long DEFAULT_HEALTH_CHECK_INTERVAL = 60_000L; // The types of operations that should be retried if they fail in a manner // that may be the result of a connection that is no longer valid. private final AtomicReference> retryOperationTypes; // Indicates whether this connection pool has been closed. private volatile boolean closed; // The bind request to use to perform authentication whenever a new connection // is established. private volatile BindRequest bindRequest; // The map of connections maintained for this connection pool. private final ConcurrentHashMap connections; // The health check implementation that should be used for this connection // pool. private LDAPConnectionPoolHealthCheck healthCheck; // The thread that will be used to perform periodic background health checks // for this connection pool. private final LDAPConnectionPoolHealthCheckThread healthCheckThread; // The statistics for this connection pool. private final LDAPConnectionPoolStatistics poolStatistics; // The length of time in milliseconds between periodic health checks against // the available connections in this pool. private volatile long healthCheckInterval; // The time that the last expired connection was closed. private volatile long lastExpiredDisconnectTime; // The maximum length of time in milliseconds that a connection should be // allowed to be established before terminating and re-establishing the // connection. private volatile long maxConnectionAge; // The minimum length of time in milliseconds that must pass between // disconnects of connections that have exceeded the maximum connection age. private volatile long minDisconnectInterval; // The schema that should be shared for connections in this pool, along with // its expiration time. private volatile ObjectPair pooledSchema; // The post-connect processor for this connection pool, if any. private final PostConnectProcessor postConnectProcessor; // The server set to use for establishing connections for use by this pool. private volatile ServerSet serverSet; // The user-friendly name assigned to this connection pool. private String connectionPoolName; /** * Creates a new LDAP thread-local connection pool in which all connections * will be clones of the provided connection. * * @param connection The connection to use to provide the template for the * other connections to be created. This connection will * be included in the pool. It must not be {@code null}, * and it must be established to the target server. It * does not necessarily need to be authenticated if all * connections in the pool are to be unauthenticated. * * @throws LDAPException If the provided connection cannot be used to * initialize the pool. If this is thrown, then all * connections associated with the pool (including the * one provided as an argument) will be closed. */ public LDAPThreadLocalConnectionPool(final LDAPConnection connection) throws LDAPException { this(connection, null); } /** * Creates a new LDAP thread-local connection pool in which all connections * will be clones of the provided connection. * * @param connection The connection to use to provide the template * for the other connections to be created. * This connection will be included in the pool. * It must not be {@code null}, and it must be * established to the target server. It does * not necessarily need to be authenticated if * all connections in the pool are to be * unauthenticated. * @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. Note that this * processing will not be invoked on the * provided connection that will be used as the * first connection in the pool. * * @throws LDAPException If the provided connection cannot be used to * initialize the pool. If this is thrown, then all * connections associated with the pool (including the * one provided as an argument) will be closed. */ public LDAPThreadLocalConnectionPool(final LDAPConnection connection, final PostConnectProcessor postConnectProcessor) throws LDAPException { Validator.ensureNotNull(connection); // 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; poolStatistics = new LDAPConnectionPoolStatistics(this); connectionPoolName = null; retryOperationTypes = new AtomicReference<>( Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); if (! connection.isConnected()) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_POOL_CONN_NOT_ESTABLISHED.get()); } bindRequest = connection.getLastBindRequest(); serverSet = new SingleServerSet(connection.getConnectedAddress(), connection.getConnectedPort(), connection.getLastUsedSocketFactory(), connection.getConnectionOptions(), null, postConnectProcessor); connections = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); connections.put(Thread.currentThread(), connection); lastExpiredDisconnectTime = 0L; maxConnectionAge = 0L; closed = false; minDisconnectInterval = 0L; healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); healthCheckThread.start(); 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.MAX_VALUE, schema); } else { pooledSchema = new ObjectPair<>(timeout+currentTime, schema); } } } catch (final Exception e) { Debug.debugException(e); } } } /** * Creates a new LDAP thread-local connection pool which will use the provided * server set and bind request for creating new connections. * * @param serverSet The server set to use to create the connections. * It is acceptable for the server set to create the * connections across multiple servers. * @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. 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) { this(serverSet, bindRequest, null); } /** * Creates a new LDAP thread-local connection pool which will use the provided * server set and bind request for creating new connections. * * @param serverSet The server set to use to create the * connections. It is acceptable for the server * set to create the connections across multiple * servers. * @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. 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. 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, final PostConnectProcessor postConnectProcessor) { Validator.ensureNotNull(serverSet); this.serverSet = serverSet; this.bindRequest = bindRequest; this.postConnectProcessor = postConnectProcessor; if (serverSet.includesAuthentication()) { Validator.ensureTrue((bindRequest != null), "LDAPThreadLocalConnectionPool.bindRequest must not be null if " + "serverSet.includesAuthentication returns true"); } if (serverSet.includesPostConnectProcessing()) { Validator.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); connectionPoolName = null; retryOperationTypes = new AtomicReference<>( Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); connections = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); lastExpiredDisconnectTime = 0L; maxConnectionAge = 0L; minDisconnectInterval = 0L; closed = false; healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); healthCheckThread.start(); } /** * Creates a new LDAP connection for use in this pool. * * @return A new connection created for use in this pool. * * @throws LDAPException If a problem occurs while attempting to establish * the connection. If a connection had been created, * it will be closed. */ @SuppressWarnings("deprecation") private LDAPConnection createConnection() throws LDAPException { final LDAPConnection c; try { c = serverSet.getConnection(healthCheck); } catch (final LDAPException le) { Debug.debugException(le); poolStatistics.incrementNumFailedConnectionAttempts(); Debug.debugConnectionPool(Level.SEVERE, this, null, "Unable to create a new pooled connection", le); throw le; } c.setConnectionPool(this); // Auto-reconnect must be disabled for pooled connections, so turn it off // if the associated connection options have it enabled for some reason. LDAPConnectionOptions opts = c.getConnectionOptions(); if (opts.autoReconnect()) { opts = opts.duplicate(); opts.setAutoReconnect(false); c.setConnectionOptions(opts); } // Invoke pre-authentication post-connect processing. if (postConnectProcessor != null) { try { postConnectProcessor.processPreAuthenticatedConnection(c); } catch (final Exception e) { Debug.debugException(e); try { poolStatistics.incrementNumFailedConnectionAttempts(); Debug.debugConnectionPool(Level.SEVERE, this, c, "Exception in pre-authentication post-connect processing", e); c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); c.setClosed(); } catch (final Exception e2) { Debug.debugException(e2); } if (e instanceof LDAPException) { throw ((LDAPException) e); } else { throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_POST_CONNECT_ERROR.get( StaticUtils.getExceptionMessage(e)), e); } } } // Authenticate the connection if appropriate. if ((bindRequest != null) && (! serverSet.includesAuthentication())) { BindResult bindResult; try { bindResult = c.bind(bindRequest.duplicate()); } catch (final LDAPBindException lbe) { Debug.debugException(lbe); bindResult = lbe.getBindResult(); } catch (final LDAPException le) { Debug.debugException(le); bindResult = new BindResult(le); } try { healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult); if (bindResult.getResultCode() != ResultCode.SUCCESS) { throw new LDAPBindException(bindResult); } } catch (final LDAPException le) { Debug.debugException(le); try { poolStatistics.incrementNumFailedConnectionAttempts(); if (bindResult.getResultCode() != ResultCode.SUCCESS) { Debug.debugConnectionPool(Level.SEVERE, this, c, "Failed to authenticate a new pooled connection", le); } else { Debug.debugConnectionPool(Level.SEVERE, this, c, "A new pooled connection failed its post-authentication " + "health check", le); } c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); c.setClosed(); } catch (final Exception e) { Debug.debugException(e); } throw le; } } // Invoke post-authentication post-connect processing. if (postConnectProcessor != null) { try { postConnectProcessor.processPostAuthenticatedConnection(c); } catch (final Exception e) { Debug.debugException(e); try { poolStatistics.incrementNumFailedConnectionAttempts(); Debug.debugConnectionPool(Level.SEVERE, this, c, "Exception in post-authentication post-connect processing", e); c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); c.setClosed(); } catch (final Exception e2) { Debug.debugException(e2); } if (e instanceof LDAPException) { throw ((LDAPException) e); } else { throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_POST_CONNECT_ERROR.get( StaticUtils.getExceptionMessage(e)), e); } } } // Get the pooled schema if appropriate. 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.MAX_VALUE, schema); } else { pooledSchema = new ObjectPair<>((currentTime+timeout), schema); } } } catch (final Exception e) { Debug.debugException(e); // There was a problem retrieving the schema from the server, but if // we have an earlier copy then we can assume it's still valid. if (pooledSchema != null) { c.setCachedSchema(pooledSchema.getSecond()); } } } else { c.setCachedSchema(pooledSchema.getSecond()); } } // Finish setting up the connection. c.setConnectionPoolName(connectionPoolName); poolStatistics.incrementNumSuccessfulConnectionAttempts(); Debug.debugConnectionPool(Level.INFO, this, c, "Successfully created a new pooled connection", null); return c; } /** * {@inheritDoc} */ @Override() public void close() { close(true, 1); } /** * {@inheritDoc} */ @Override() public void close(final boolean unbind, final int numThreads) { try { final boolean healthCheckThreadAlreadySignaled = closed; closed = true; healthCheckThread.stopRunning(! healthCheckThreadAlreadySignaled); if (numThreads > 1) { final ArrayList connList = new ArrayList<>(connections.size()); final Iterator iterator = connections.values().iterator(); while (iterator.hasNext()) { connList.add(iterator.next()); iterator.remove(); } if (! connList.isEmpty()) { final ParallelPoolCloser closer = new ParallelPoolCloser(connList, unbind, numThreads); closer.closeConnections(); } } else { final Iterator> iterator = connections.entrySet().iterator(); while (iterator.hasNext()) { final LDAPConnection conn = iterator.next().getValue(); iterator.remove(); poolStatistics.incrementNumConnectionsClosedUnneeded(); Debug.debugConnectionPool(Level.INFO, this, conn, "Closed a connection as part of closing the connection pool", null); conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null); if (unbind) { conn.terminate(null); } else { conn.setClosed(); } } } } finally { Debug.debugConnectionPool(Level.INFO, this, null, "Closed the connection pool", null); } } /** * {@inheritDoc} */ @Override() public boolean isClosed() { return closed; } /** * Processes a simple bind using a connection from this connection pool, and * then reverts that authentication by re-binding as the same user used to * authenticate new connections. If new connections are unauthenticated, then * the subsequent bind will be an anonymous simple bind. This method attempts * to ensure that processing the provided bind operation does not have a * lasting impact the authentication state of the connection used to process * it. *

* If the second bind attempt (the one used to restore the authentication * identity) fails, the connection will be closed as defunct so that a new * connection will be created to take its place. * * @param bindDN The bind DN for the simple bind request. * @param password The password for the simple bind request. * @param controls The optional set of controls for the simple bind request. * * @return The result of processing the provided bind operation. * * @throws LDAPException If the server rejects the bind request, or if a * problem occurs while sending the request or reading * the response. */ public BindResult bindAndRevertAuthentication(final String bindDN, final String password, final Control... controls) throws LDAPException { return bindAndRevertAuthentication( new SimpleBindRequest(bindDN, password, controls)); } /** * Processes the provided bind request using a connection from this connection * pool, and then reverts that authentication by re-binding as the same user * used to authenticate new connections. If new connections are * unauthenticated, then the subsequent bind will be an anonymous simple bind. * This method attempts to ensure that processing the provided bind operation * does not have a lasting impact the authentication state of the connection * used to process it. *

* If the second bind attempt (the one used to restore the authentication * identity) fails, the connection will be closed as defunct so that a new * connection will be created to take its place. * * @param bindRequest The bind request to be processed. It must not be * {@code null}. * * @return The result of processing the provided bind operation. * * @throws LDAPException If the server rejects the bind request, or if a * problem occurs while sending the request or reading * the response. */ 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) { Debug.debugException(t); if (t instanceof LDAPException) { final LDAPException le = (LDAPException) t; boolean shouldThrow; try { healthCheck.ensureConnectionValidAfterException(conn, le); // The above call will throw an exception if the connection doesn't // seem to be valid, so if we've gotten here then we should assume // that it is valid and we will pass the exception onto the client // without retrying the operation. releaseAndReAuthenticateConnection(conn); shouldThrow = true; } catch (final Exception e) { Debug.debugException(e); // This implies that the connection is not valid. If the pool is // configured to re-try bind operations on a newly-established // connection, then that will be done later in this method. // Otherwise, release the connection as defunct and pass the bind // exception onto the client. if (! getOperationTypesToRetryDueToInvalidConnections().contains( OperationType.BIND)) { releaseDefunctConnection(conn); shouldThrow = true; } else { shouldThrow = false; } } if (shouldThrow) { throw le; } } else { releaseDefunctConnection(conn); StaticUtils.rethrowIfError(t); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); } } // If we've gotten here, then the bind operation should be re-tried on a // newly-established connection. conn = replaceDefunctConnection(conn); try { final BindResult result = conn.bind(bindRequest); releaseAndReAuthenticateConnection(conn); return result; } catch (final Throwable t) { Debug.debugException(t); if (t instanceof LDAPException) { final LDAPException le = (LDAPException) t; try { healthCheck.ensureConnectionValidAfterException(conn, le); releaseAndReAuthenticateConnection(conn); } catch (final Exception e) { Debug.debugException(e); releaseDefunctConnection(conn); } throw le; } else { releaseDefunctConnection(conn); StaticUtils.rethrowIfError(t); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); } } } /** * {@inheritDoc} */ @Override() public LDAPConnection getConnection() throws LDAPException { final Thread t = Thread.currentThread(); LDAPConnection conn = connections.get(t); if (closed) { if (conn != null) { conn.terminate(null); connections.remove(t); } poolStatistics.incrementNumFailedCheckouts(); Debug.debugConnectionPool(Level.SEVERE, this, null, "Failed to get a connection to a closed connection pool", null); throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); } boolean created = false; if ((conn == null) || (! conn.isConnected())) { conn = createConnection(); connections.put(t, conn); created = true; } try { healthCheck.ensureConnectionValidForCheckout(conn); if (created) { poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); Debug.debugConnectionPool(Level.INFO, this, conn, "Checked out a newly created pooled connection", null); } else { poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); Debug.debugConnectionPool(Level.INFO, this, conn, "Checked out an existing pooled connection", null); } return conn; } catch (final LDAPException le) { Debug.debugException(le); conn.setClosed(); connections.remove(t); if (created) { poolStatistics.incrementNumFailedCheckouts(); Debug.debugConnectionPool(Level.SEVERE, this, conn, "Failed to check out a connection because a newly created " + "connection failed the checkout health check", le); throw le; } } try { conn = createConnection(); healthCheck.ensureConnectionValidForCheckout(conn); connections.put(t, conn); poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); Debug.debugConnectionPool(Level.INFO, this, conn, "Checked out a newly created pooled connection", null); return conn; } catch (final LDAPException le) { Debug.debugException(le); poolStatistics.incrementNumFailedCheckouts(); if (conn == null) { Debug.debugConnectionPool(Level.SEVERE, this, conn, "Unable to check out a connection because an error occurred " + "while establishing the connection", le); } else { Debug.debugConnectionPool(Level.SEVERE, this, conn, "Unable to check out a newly created connection because it " + "failed the checkout health check", le); conn.setClosed(); } throw le; } } /** * {@inheritDoc} */ @Override() public void releaseConnection(final LDAPConnection connection) { if (connection == null) { return; } connection.setConnectionPoolName(connectionPoolName); if (connectionIsExpired(connection)) { try { final LDAPConnection newConnection = createConnection(); connections.put(Thread.currentThread(), newConnection); connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, null, null); connection.terminate(null); poolStatistics.incrementNumConnectionsClosedExpired(); Debug.debugConnectionPool(Level.WARNING, this, connection, "Closing a released connection because it is expired", null); lastExpiredDisconnectTime = System.currentTimeMillis(); } catch (final LDAPException le) { Debug.debugException(le); } } try { healthCheck.ensureConnectionValidForRelease(connection); } catch (final LDAPException le) { releaseDefunctConnection(connection); return; } poolStatistics.incrementNumReleasedValid(); Debug.debugConnectionPool(Level.INFO, this, connection, "Released a connection back to the pool", null); if (closed) { close(); } } /** * Performs a bind on the provided connection before releasing it back to the * pool, so that it will be authenticated as the same user as * newly-established connections. If newly-established connections are * unauthenticated, then this method will perform an anonymous simple bind to * ensure that the resulting connection is unauthenticated. * * Releases the provided connection back to this pool. * * @param connection The connection to be released back to the pool after * being re-authenticated. */ public void releaseAndReAuthenticateConnection( final LDAPConnection connection) { if (connection == null) { return; } try { BindResult bindResult; try { if (bindRequest == null) { bindResult = connection.bind("", ""); } else { bindResult = connection.bind(bindRequest.duplicate()); } } catch (final LDAPBindException lbe) { Debug.debugException(lbe); bindResult = lbe.getBindResult(); } try { healthCheck.ensureConnectionValidAfterAuthentication(connection, bindResult); if (bindResult.getResultCode() != ResultCode.SUCCESS) { throw new LDAPBindException(bindResult); } } catch (final LDAPException le) { Debug.debugException(le); try { connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); connection.terminate(null); releaseDefunctConnection(connection); } catch (final Exception e) { Debug.debugException(e); } throw le; } releaseConnection(connection); } catch (final Exception e) { Debug.debugException(e); releaseDefunctConnection(connection); } } /** * {@inheritDoc} */ @Override() public void releaseDefunctConnection(final LDAPConnection connection) { if (connection == null) { return; } connection.setConnectionPoolName(connectionPoolName); poolStatistics.incrementNumConnectionsClosedDefunct(); Debug.debugConnectionPool(Level.WARNING, this, connection, "Releasing a defunct connection", null); handleDefunctConnection(connection); } /** * Performs the real work of terminating a defunct connection and replacing it * with a new connection if possible. * * @param connection The defunct connection to be replaced. */ private void handleDefunctConnection(final LDAPConnection connection) { final Thread t = Thread.currentThread(); connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, null); connection.setClosed(); connections.remove(t); if (closed) { return; } try { final LDAPConnection conn = createConnection(); connections.put(t, conn); } catch (final LDAPException le) { Debug.debugException(le); } } /** * {@inheritDoc} */ @Override() public LDAPConnection replaceDefunctConnection( final LDAPConnection connection) throws LDAPException { poolStatistics.incrementNumConnectionsClosedDefunct(); Debug.debugConnectionPool(Level.WARNING, this, connection, "Releasing a defunct connection that is to be replaced", null); connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, null); connection.setClosed(); connections.remove(Thread.currentThread(), connection); if (closed) { throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); } final LDAPConnection newConnection = createConnection(); connections.put(Thread.currentThread(), newConnection); return newConnection; } /** * {@inheritDoc} */ @Override() public Set getOperationTypesToRetryDueToInvalidConnections() { return retryOperationTypes.get(); } /** * {@inheritDoc} */ @Override() public void setRetryFailedOperationsDueToInvalidConnections( final Set operationTypes) { if ((operationTypes == null) || operationTypes.isEmpty()) { retryOperationTypes.set( Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); } else { final EnumSet s = EnumSet.noneOf(OperationType.class); s.addAll(operationTypes); retryOperationTypes.set(Collections.unmodifiableSet(s)); } } /** * Indicates whether the provided connection should be considered expired. * * @param connection The connection for which to make the determination. * * @return {@code true} if the provided connection should be considered * expired, or {@code false} if not. */ private boolean connectionIsExpired(final LDAPConnection connection) { // If connection expiration is not enabled, then there is nothing to do. if (maxConnectionAge <= 0L) { return false; } // If there is a minimum disconnect interval, then make sure that we have // not closed another expired connection too recently. final long currentTime = System.currentTimeMillis(); if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) { return false; } // Get the age of the connection and see if it is expired. final long connectionAge = currentTime - connection.getConnectTime(); return (connectionAge > maxConnectionAge); } /** * Specifies the bind request that will be used to authenticate subsequent new * connections that are established by this connection pool. The * authentication state for existing connections will not be altered unless * one of the {@code bindAndRevertAuthentication} or * {@code releaseAndReAuthenticateConnection} methods are invoked on those * connections. * * @param bindRequest The bind request that will be used to authenticate new * connections that are established by this pool, or * that will be applied to existing connections via the * {@code bindAndRevertAuthentication} or * {@code releaseAndReAuthenticateConnection} method. It * may be {@code null} if new connections should be * unauthenticated. */ public void setBindRequest(final BindRequest bindRequest) { this.bindRequest = bindRequest; } /** * Specifies the server set that should be used to establish new connections * for use in this connection pool. Existing connections will not be * affected. * * @param serverSet The server set that should be used to establish new * connections for use in this connection pool. It must * not be {@code null}. */ public void setServerSet(final ServerSet serverSet) { Validator.ensureNotNull(serverSet); this.serverSet = serverSet; } /** * {@inheritDoc} */ @Override() public String getConnectionPoolName() { return connectionPoolName; } /** * {@inheritDoc} */ @Override() public void setConnectionPoolName(final String connectionPoolName) { this.connectionPoolName = connectionPoolName; } /** * Retrieves the maximum length of time in milliseconds that a connection in * this pool may be established before it is closed and replaced with another * connection. * * @return The maximum length of time in milliseconds that a connection in * this pool may be established before it is closed and replaced with * another connection, or {@code 0L} if no maximum age should be * enforced. */ public long getMaxConnectionAgeMillis() { return maxConnectionAge; } /** * Specifies the maximum length of time in milliseconds that a connection in * this pool may be established before it should be closed and replaced with * another connection. * * @param maxConnectionAge The maximum length of time in milliseconds that a * connection in this pool may be established before * it should be closed and replaced with another * connection. A value of zero indicates that no * maximum age should be enforced. */ public void setMaxConnectionAgeMillis(final long maxConnectionAge) { if (maxConnectionAge > 0L) { this.maxConnectionAge = maxConnectionAge; } else { this.maxConnectionAge = 0L; } } /** * Retrieves the minimum length of time in milliseconds that should pass * between connections closed because they have been established for longer * than the maximum connection age. * * @return The minimum length of time in milliseconds that should pass * between connections closed because they have been established for * longer than the maximum connection age, or {@code 0L} if expired * connections may be closed as quickly as they are identified. */ public long getMinDisconnectIntervalMillis() { return minDisconnectInterval; } /** * Specifies the minimum length of time in milliseconds that should pass * between connections closed because they have been established for longer * than the maximum connection age. * * @param minDisconnectInterval The minimum length of time in milliseconds * that should pass between connections closed * because they have been established for * longer than the maximum connection age. A * value less than or equal to zero indicates * that no minimum time should be enforced. */ public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) { if (minDisconnectInterval > 0) { this.minDisconnectInterval = minDisconnectInterval; } else { this.minDisconnectInterval = 0L; } } /** * {@inheritDoc} */ @Override() public LDAPConnectionPoolHealthCheck getHealthCheck() { return healthCheck; } /** * Sets the health check implementation for this connection pool. * * @param healthCheck The health check implementation for this connection * pool. It must not be {@code null}. */ public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck) { Validator.ensureNotNull(healthCheck); this.healthCheck = healthCheck; } /** * {@inheritDoc} */ @Override() public long getHealthCheckIntervalMillis() { return healthCheckInterval; } /** * {@inheritDoc} */ @Override() public void setHealthCheckIntervalMillis(final long healthCheckInterval) { Validator.ensureTrue(healthCheckInterval > 0L, "LDAPConnectionPool.healthCheckInterval must be greater than 0."); this.healthCheckInterval = healthCheckInterval; healthCheckThread.wakeUp(); } /** * {@inheritDoc} */ @Override() protected void doHealthCheck() { final Iterator> iterator = connections.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry e = iterator.next(); final Thread t = e.getKey(); final LDAPConnection c = e.getValue(); if (! t.isAlive()) { c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null); c.terminate(null); iterator.remove(); } } } /** * {@inheritDoc} */ @Override() public int getCurrentAvailableConnections() { return -1; } /** * {@inheritDoc} */ @Override() public int getMaximumAvailableConnections() { return -1; } /** * {@inheritDoc} */ @Override() public LDAPConnectionPoolStatistics getConnectionPoolStatistics() { return poolStatistics; } /** * Closes this connection pool in the event that it becomes unreferenced. * * @throws Throwable If an unexpected problem occurs. */ @Override() protected void finalize() throws Throwable { super.finalize(); close(); } /** * {@inheritDoc} */ @Override() public void toString(final StringBuilder buffer) { buffer.append("LDAPThreadLocalConnectionPool("); final String name = connectionPoolName; if (name != null) { buffer.append("name='"); buffer.append(name); buffer.append("', "); } buffer.append("serverSet="); serverSet.toString(buffer); buffer.append(')'); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy