001    /*
002     * Copyright 2007-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.net.Socket;
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.EnumSet;
029    import java.util.HashSet;
030    import java.util.List;
031    import java.util.Set;
032    import java.util.logging.Level;
033    import java.util.concurrent.LinkedBlockingQueue;
034    import java.util.concurrent.TimeUnit;
035    import java.util.concurrent.atomic.AtomicInteger;
036    import java.util.concurrent.atomic.AtomicReference;
037    
038    import com.unboundid.ldap.protocol.LDAPResponse;
039    import com.unboundid.ldap.sdk.schema.Schema;
040    import com.unboundid.util.ObjectPair;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    
044    import static com.unboundid.ldap.sdk.LDAPMessages.*;
045    import static com.unboundid.util.Debug.*;
046    import static com.unboundid.util.StaticUtils.*;
047    import static com.unboundid.util.Validator.*;
048    
049    
050    
051    /**
052     * This class provides an implementation of an LDAP connection pool, which is a
053     * structure that can hold multiple connections established to a given server
054     * that can be reused for multiple operations rather than creating and
055     * destroying connections for each operation.  This connection pool
056     * implementation provides traditional methods for checking out and releasing
057     * connections, but it also provides wrapper methods that make it easy to
058     * perform operations using pooled connections without the need to explicitly
059     * check out or release the connections.
060     * <BR><BR>
061     * Note that both the {@code LDAPConnectionPool} class and the
062     * {@link LDAPConnection} class implement the {@link LDAPInterface} interface.
063     * This is a common interface that defines a number of common methods for
064     * processing LDAP requests.  This means that in many cases, an application can
065     * use an object of type {@link LDAPInterface} rather than
066     * {@link LDAPConnection}, which makes it possible to work with either a single
067     * standalone connection or with a connection pool.
068     * <BR><BR>
069     * <H2>Creating a Connection Pool</H2>
070     * An LDAP connection pool can be created from either a single
071     * {@link LDAPConnection} (for which an appropriate number of copies will be
072     * created to fill out the pool) or using a {@link ServerSet} to create
073     * connections that may span multiple servers.  For example:
074     * <BR><BR>
075     * <PRE>
076     *   // Create a new LDAP connection pool with ten connections established and
077     *   // authenticated to the same server:
078     *   LDAPConnection connection = new LDAPConnection(address, port);
079     *   BindResult bindResult = connection.bind(bindDN, password);
080     *   LDAPConnectionPool connectionPool = new LDAPConnectionPool(connection, 10);
081     *
082     *   // Create a new LDAP connection pool with 10 connections spanning multiple
083     *   // servers using a server set.
084     *   RoundRobinServerSet serverSet = new RoundRobinServerSet(addresses, ports);
085     *   SimpleBindRequest bindRequest = new SimpleBindRequest(bindDN, password);
086     *   LDAPConnectionPool connectionPool =
087     *        new LDAPConnectionPool(serverSet, bindRequest, 10);
088     * </PRE>
089     * Note that in some cases, such as when using StartTLS, it may be necessary to
090     * perform some additional processing when a new connection is created for use
091     * in the connection pool.  In this case, a {@link PostConnectProcessor} should
092     * be provided to accomplish this.  See the documentation for the
093     * {@link StartTLSPostConnectProcessor} class for an example that demonstrates
094     * its use for creating a connection pool with connections secured using
095     * StartTLS.
096     * <BR><BR>
097     * <H2>Processing Operations with a Connection Pool</H2>
098     * If a single operation is to be processed using a connection from the
099     * connection pool, then it can be used without the need to check out or release
100     * a connection or perform any validity checking on the connection.  This can
101     * be accomplished via the {@link LDAPInterface} interface that allows a
102     * connection pool to be treated like a single connection.  For example, to
103     * perform a search using a pooled connection:
104     * <PRE>
105     *   SearchResult searchResult =
106     *        connectionPool.search("dc=example,dc=com", SearchScope.SUB,
107     *                              "(uid=john.doe)");
108     * </PRE>
109     * If an application needs to process multiple operations using a single
110     * connection, then it may be beneficial to obtain a connection from the pool
111     * to use for processing those operations and then return it back to the pool
112     * when it is no longer needed.  This can be done using the
113     * {@link #getConnection} and {@link #releaseConnection} methods.  If during
114     * processing it is determined that the connection is no longer valid, then the
115     * connection should be released back to the pool using the
116     * {@link #releaseDefunctConnection} method, which will ensure that the
117     * connection is closed and a new connection will be established to take its
118     * place in the pool.
119     * <BR><BR>
120     * Note that it is also possible to process multiple operations on a single
121     * connection using the {@link #processRequests} method.  This may be useful if
122     * a fixed set of operations should be processed over the same connection and
123     * none of the subsequent requests depend upon the results of the earlier
124     * operations.
125     * <BR><BR>
126     * Connection pools should generally not be used when performing operations that
127     * may change the state of the underlying connections.  This is particularly
128     * true for bind operations and the StartTLS extended operation, but it may
129     * apply to other types of operations as well.
130     * <BR><BR>
131     * Performing a bind operation using a connection from the pool will invalidate
132     * any previous authentication on that connection, and if that connection is
133     * released back to the pool without first being re-authenticated as the
134     * original user, then subsequent operation attempts may fail or be processed in
135     * an incorrect manner.  Bind operations should only be performed in a
136     * connection pool if the pool is to be used exclusively for processing binds,
137     * if the bind request is specially crafted so that it will not change the
138     * identity of the associated connection (e.g., by including the retain identity
139     * request control in the bind request if using the Commercial Edition of the
140     * LDAP SDK with an UnboundID Directory Server), or if the code using the
141     * connection pool makes sure to re-authenticate the connection as the
142     * appropriate user whenever its identity has been changed.
143     * <BR><BR>
144     * The StartTLS extended operation should never be invoked on a connection which
145     * is part of a connection pool.  It is acceptable for the pool to maintain
146     * connections which have been configured with StartTLS security prior to being
147     * added to the pool (via the use of the {@link StartTLSPostConnectProcessor}).
148     */
149    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
150    public final class LDAPConnectionPool
151           extends AbstractConnectionPool
152    {
153      /**
154       * The default health check interval for this connection pool, which is set to
155       * 60000 milliseconds (60 seconds).
156       */
157      private static final long DEFAULT_HEALTH_CHECK_INTERVAL = 60000L;
158    
159    
160    
161      /**
162       * The name of the connection property that may be used to indicate that a
163       * particular connection should have a different maximum connection age than
164       * the default for this pool.
165       */
166      static final String ATTACHMENT_NAME_MAX_CONNECTION_AGE =
167           LDAPConnectionPool.class.getName() + ".maxConnectionAge";
168    
169    
170    
171      // A counter used to keep track of the number of times that the pool failed to
172      // replace a defunct connection.  It may also be initialized to the difference
173      // between the initial and maximum number of connections that should be
174      // included in the pool.
175      private final AtomicInteger failedReplaceCount;
176    
177      // The types of operations that should be retried if they fail in a manner
178      // that may be the result of a connection that is no longer valid.
179      private final AtomicReference<Set<OperationType>> retryOperationTypes;
180    
181      // Indicates whether this connection pool has been closed.
182      private volatile boolean closed;
183    
184      // Indicates whether to create a new connection if necessary rather than
185      // waiting for a connection to become available.
186      private boolean createIfNecessary;
187    
188      // Indicates whether to check the connection age when releasing a connection
189      // back to the pool.
190      private volatile boolean checkConnectionAgeOnRelease;
191    
192      // Indicates whether health check processing for connections in synchronous
193      // mode should include attempting to read with a very short timeout to attempt
194      // to detect closures and unsolicited notifications in a more timely manner.
195      private volatile boolean trySynchronousReadDuringHealthCheck;
196    
197      // The bind request to use to perform authentication whenever a new connection
198      // is established.
199      private final BindRequest bindRequest;
200    
201      // The number of connections to be held in this pool.
202      private final int numConnections;
203    
204      // The health check implementation that should be used for this connection
205      // pool.
206      private LDAPConnectionPoolHealthCheck healthCheck;
207    
208      // The thread that will be used to perform periodic background health checks
209      // for this connection pool.
210      private final LDAPConnectionPoolHealthCheckThread healthCheckThread;
211    
212      // The statistics for this connection pool.
213      private final LDAPConnectionPoolStatistics poolStatistics;
214    
215      // The set of connections that are currently available for use.
216      private final LinkedBlockingQueue<LDAPConnection> availableConnections;
217    
218      // The length of time in milliseconds between periodic health checks against
219      // the available connections in this pool.
220      private volatile long healthCheckInterval;
221    
222      // The time that the last expired connection was closed.
223      private volatile long lastExpiredDisconnectTime;
224    
225      // The maximum length of time in milliseconds that a connection should be
226      // allowed to be established before terminating and re-establishing the
227      // connection.
228      private volatile long maxConnectionAge;
229    
230      // The maximum connection age that should be used for connections created to
231      // replace connections that are released as defunct.
232      private volatile Long maxDefunctReplacementConnectionAge;
233    
234      // The maximum length of time in milliseconds to wait for a connection to be
235      // available.
236      private long maxWaitTime;
237    
238      // The minimum length of time in milliseconds that must pass between
239      // disconnects of connections that have exceeded the maximum connection age.
240      private volatile long minDisconnectInterval;
241    
242      // The schema that should be shared for connections in this pool, along with
243      // its expiration time.
244      private volatile ObjectPair<Long,Schema> pooledSchema;
245    
246      // The post-connect processor for this connection pool, if any.
247      private final PostConnectProcessor postConnectProcessor;
248    
249      // The server set to use for establishing connections for use by this pool.
250      private final ServerSet serverSet;
251    
252      // The user-friendly name assigned to this connection pool.
253      private String connectionPoolName;
254    
255    
256    
257    
258      /**
259       * Creates a new LDAP connection pool with up to the specified number of
260       * connections, created as clones of the provided connection.  Initially, only
261       * the provided connection will be included in the pool, but additional
262       * connections will be created as needed until the pool has reached its full
263       * capacity, at which point the create if necessary and max wait time settings
264       * will be used to determine how to behave if a connection is requested but
265       * none are available.
266       *
267       * @param  connection      The connection to use to provide the template for
268       *                         the other connections to be created.  This
269       *                         connection will be included in the pool.  It must
270       *                         not be {@code null}, and it must be established to
271       *                         the target server.  It does not necessarily need to
272       *                         be authenticated if all connections in the pool are
273       *                         to be unauthenticated.
274       * @param  numConnections  The total number of connections that should be
275       *                         created in the pool.  It must be greater than or
276       *                         equal to one.
277       *
278       * @throws  LDAPException  If the provided connection cannot be used to
279       *                         initialize the pool, or if a problem occurs while
280       *                         attempting to establish any of the connections.  If
281       *                         this is thrown, then all connections associated
282       *                         with the pool (including the one provided as an
283       *                         argument) will be closed.
284       */
285      public LDAPConnectionPool(final LDAPConnection connection,
286                                final int numConnections)
287             throws LDAPException
288      {
289        this(connection, 1, numConnections, null);
290      }
291    
292    
293    
294      /**
295       * Creates a new LDAP connection pool with the specified number of
296       * connections, created as clones of the provided connection.
297       *
298       * @param  connection          The connection to use to provide the template
299       *                             for the other connections to be created.  This
300       *                             connection will be included in the pool.  It
301       *                             must not be {@code null}, and it must be
302       *                             established to the target server.  It does not
303       *                             necessarily need to be authenticated if all
304       *                             connections in the pool are to be
305       *                             unauthenticated.
306       * @param  initialConnections  The number of connections to initially
307       *                             establish when the pool is created.  It must be
308       *                             greater than or equal to one.
309       * @param  maxConnections      The maximum number of connections that should
310       *                             be maintained in the pool.  It must be greater
311       *                             than or equal to the initial number of
312       *                             connections.
313       *
314       * @throws  LDAPException  If the provided connection cannot be used to
315       *                         initialize the pool, or if a problem occurs while
316       *                         attempting to establish any of the connections.  If
317       *                         this is thrown, then all connections associated
318       *                         with the pool (including the one provided as an
319       *                         argument) will be closed.
320       */
321      public LDAPConnectionPool(final LDAPConnection connection,
322                                final int initialConnections,
323                                final int maxConnections)
324             throws LDAPException
325      {
326        this(connection, initialConnections, maxConnections, null);
327      }
328    
329    
330    
331      /**
332       * Creates a new LDAP connection pool with the specified number of
333       * connections, created as clones of the provided connection.
334       *
335       * @param  connection            The connection to use to provide the template
336       *                               for the other connections to be created.
337       *                               This connection will be included in the pool.
338       *                               It must not be {@code null}, and it must be
339       *                               established to the target server.  It does
340       *                               not necessarily need to be authenticated if
341       *                               all connections in the pool are to be
342       *                               unauthenticated.
343       * @param  initialConnections    The number of connections to initially
344       *                               establish when the pool is created.  It must
345       *                               be greater than or equal to one.
346       * @param  maxConnections        The maximum number of connections that should
347       *                               be maintained in the pool.  It must be
348       *                               greater than or equal to the initial number
349       *                               of connections.
350       * @param  postConnectProcessor  A processor that should be used to perform
351       *                               any post-connect processing for connections
352       *                               in this pool.  It may be {@code null} if no
353       *                               special processing is needed.  Note that this
354       *                               processing will not be invoked on the
355       *                               provided connection that will be used as the
356       *                               first connection in the pool.
357       *
358       * @throws  LDAPException  If the provided connection cannot be used to
359       *                         initialize the pool, or if a problem occurs while
360       *                         attempting to establish any of the connections.  If
361       *                         this is thrown, then all connections associated
362       *                         with the pool (including the one provided as an
363       *                         argument) will be closed.
364       */
365      public LDAPConnectionPool(final LDAPConnection connection,
366                                final int initialConnections,
367                                final int maxConnections,
368                                final PostConnectProcessor postConnectProcessor)
369             throws LDAPException
370      {
371        this(connection, initialConnections, maxConnections,  postConnectProcessor,
372             true);
373      }
374    
375    
376    
377      /**
378       * Creates a new LDAP connection pool with the specified number of
379       * connections, created as clones of the provided connection.
380       *
381       * @param  connection             The connection to use to provide the
382       *                                template for the other connections to be
383       *                                created.  This connection will be included
384       *                                in the pool.  It must not be {@code null},
385       *                                and it must be established to the target
386       *                                server.  It does not necessarily need to be
387       *                                authenticated if all connections in the pool
388       *                                are to be unauthenticated.
389       * @param  initialConnections     The number of connections to initially
390       *                                establish when the pool is created.  It must
391       *                                be greater than or equal to one.
392       * @param  maxConnections         The maximum number of connections that
393       *                                should be maintained in the pool.  It must
394       *                                be greater than or equal to the initial
395       *                                number of connections.
396       * @param  postConnectProcessor   A processor that should be used to perform
397       *                                any post-connect processing for connections
398       *                                in this pool.  It may be {@code null} if no
399       *                                special processing is needed.  Note that
400       *                                this processing will not be invoked on the
401       *                                provided connection that will be used as the
402       *                                first connection in the pool.
403       * @param  throwOnConnectFailure  If an exception should be thrown if a
404       *                                problem is encountered while attempting to
405       *                                create the specified initial number of
406       *                                connections.  If {@code true}, then the
407       *                                attempt to create the pool will fail.if any
408       *                                connection cannot be established.  If
409       *                                {@code false}, then the pool will be created
410       *                                but may have fewer than the initial number
411       *                                of connections (or possibly no connections).
412       *
413       * @throws  LDAPException  If the provided connection cannot be used to
414       *                         initialize the pool, or if a problem occurs while
415       *                         attempting to establish any of the connections.  If
416       *                         this is thrown, then all connections associated
417       *                         with the pool (including the one provided as an
418       *                         argument) will be closed.
419       */
420      public LDAPConnectionPool(final LDAPConnection connection,
421                                final int initialConnections,
422                                final int maxConnections,
423                                final PostConnectProcessor postConnectProcessor,
424                                final boolean throwOnConnectFailure)
425             throws LDAPException
426      {
427        this(connection, initialConnections, maxConnections, 1,
428             postConnectProcessor, throwOnConnectFailure);
429      }
430    
431    
432    
433      /**
434       * Creates a new LDAP connection pool with the specified number of
435       * connections, created as clones of the provided connection.
436       *
437       * @param  connection             The connection to use to provide the
438       *                                template for the other connections to be
439       *                                created.  This connection will be included
440       *                                in the pool.  It must not be {@code null},
441       *                                and it must be established to the target
442       *                                server.  It does not necessarily need to be
443       *                                authenticated if all connections in the pool
444       *                                are to be unauthenticated.
445       * @param  initialConnections     The number of connections to initially
446       *                                establish when the pool is created.  It must
447       *                                be greater than or equal to one.
448       * @param  maxConnections         The maximum number of connections that
449       *                                should be maintained in the pool.  It must
450       *                                be greater than or equal to the initial
451       *                                number of connections.
452       * @param  initialConnectThreads  The number of concurrent threads to use to
453       *                                establish the initial set of connections.
454       *                                A value greater than one indicates that the
455       *                                attempt to establish connections should be
456       *                                parallelized.
457       * @param  postConnectProcessor   A processor that should be used to perform
458       *                                any post-connect processing for connections
459       *                                in this pool.  It may be {@code null} if no
460       *                                special processing is needed.  Note that
461       *                                this processing will not be invoked on the
462       *                                provided connection that will be used as the
463       *                                first connection in the pool.
464       * @param  throwOnConnectFailure  If an exception should be thrown if a
465       *                                problem is encountered while attempting to
466       *                                create the specified initial number of
467       *                                connections.  If {@code true}, then the
468       *                                attempt to create the pool will fail.if any
469       *                                connection cannot be established.  If
470       *                                {@code false}, then the pool will be created
471       *                                but may have fewer than the initial number
472       *                                of connections (or possibly no connections).
473       *
474       * @throws  LDAPException  If the provided connection cannot be used to
475       *                         initialize the pool, or if a problem occurs while
476       *                         attempting to establish any of the connections.  If
477       *                         this is thrown, then all connections associated
478       *                         with the pool (including the one provided as an
479       *                         argument) will be closed.
480       */
481      public LDAPConnectionPool(final LDAPConnection connection,
482                                final int initialConnections,
483                                final int maxConnections,
484                                final int initialConnectThreads,
485                                final PostConnectProcessor postConnectProcessor,
486                                final boolean throwOnConnectFailure)
487             throws LDAPException
488      {
489        this(connection, initialConnections, maxConnections, initialConnectThreads,
490             postConnectProcessor, throwOnConnectFailure, null);
491      }
492    
493    
494    
495      /**
496       * Creates a new LDAP connection pool with the specified number of
497       * connections, created as clones of the provided connection.
498       *
499       * @param  connection             The connection to use to provide the
500       *                                template for the other connections to be
501       *                                created.  This connection will be included
502       *                                in the pool.  It must not be {@code null},
503       *                                and it must be established to the target
504       *                                server.  It does not necessarily need to be
505       *                                authenticated if all connections in the pool
506       *                                are to be unauthenticated.
507       * @param  initialConnections     The number of connections to initially
508       *                                establish when the pool is created.  It must
509       *                                be greater than or equal to one.
510       * @param  maxConnections         The maximum number of connections that
511       *                                should be maintained in the pool.  It must
512       *                                be greater than or equal to the initial
513       *                                number of connections.
514       * @param  initialConnectThreads  The number of concurrent threads to use to
515       *                                establish the initial set of connections.
516       *                                A value greater than one indicates that the
517       *                                attempt to establish connections should be
518       *                                parallelized.
519       * @param  postConnectProcessor   A processor that should be used to perform
520       *                                any post-connect processing for connections
521       *                                in this pool.  It may be {@code null} if no
522       *                                special processing is needed.  Note that
523       *                                this processing will not be invoked on the
524       *                                provided connection that will be used as the
525       *                                first connection in the pool.
526       * @param  throwOnConnectFailure  If an exception should be thrown if a
527       *                                problem is encountered while attempting to
528       *                                create the specified initial number of
529       *                                connections.  If {@code true}, then the
530       *                                attempt to create the pool will fail.if any
531       *                                connection cannot be established.  If
532       *                                {@code false}, then the pool will be created
533       *                                but may have fewer than the initial number
534       *                                of connections (or possibly no connections).
535       * @param  healthCheck            The health check that should be used for
536       *                                connections in this pool.  It may be
537       *                                {@code null} if the default health check
538       *                                should be used.
539       *
540       * @throws  LDAPException  If the provided connection cannot be used to
541       *                         initialize the pool, or if a problem occurs while
542       *                         attempting to establish any of the connections.  If
543       *                         this is thrown, then all connections associated
544       *                         with the pool (including the one provided as an
545       *                         argument) will be closed.
546       */
547      public LDAPConnectionPool(final LDAPConnection connection,
548                                final int initialConnections,
549                                final int maxConnections,
550                                final int initialConnectThreads,
551                                final PostConnectProcessor postConnectProcessor,
552                                final boolean throwOnConnectFailure,
553                                final LDAPConnectionPoolHealthCheck healthCheck)
554             throws LDAPException
555      {
556        ensureNotNull(connection);
557        ensureTrue(initialConnections >= 1,
558                   "LDAPConnectionPool.initialConnections must be at least 1.");
559        ensureTrue(maxConnections >= initialConnections,
560                   "LDAPConnectionPool.initialConnections must not be greater " +
561                        "than maxConnections.");
562    
563        this.postConnectProcessor = postConnectProcessor;
564    
565        trySynchronousReadDuringHealthCheck = true;
566        healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;
567        poolStatistics      = new LDAPConnectionPoolStatistics(this);
568        pooledSchema        = null;
569        connectionPoolName  = null;
570        retryOperationTypes = new AtomicReference<Set<OperationType>>(
571             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
572        numConnections            = maxConnections;
573        availableConnections      =
574             new LinkedBlockingQueue<LDAPConnection>(numConnections);
575    
576        if (! connection.isConnected())
577        {
578          throw new LDAPException(ResultCode.PARAM_ERROR,
579                                  ERR_POOL_CONN_NOT_ESTABLISHED.get());
580        }
581    
582        if (healthCheck == null)
583        {
584          this.healthCheck = new LDAPConnectionPoolHealthCheck();
585        }
586        else
587        {
588          this.healthCheck = healthCheck;
589        }
590    
591    
592        serverSet = new SingleServerSet(connection.getConnectedAddress(),
593                                        connection.getConnectedPort(),
594                                        connection.getLastUsedSocketFactory(),
595                                        connection.getConnectionOptions());
596        bindRequest = connection.getLastBindRequest();
597    
598        final LDAPConnectionOptions opts = connection.getConnectionOptions();
599        if (opts.usePooledSchema())
600        {
601          try
602          {
603            final Schema schema = connection.getSchema();
604            if (schema != null)
605            {
606              connection.setCachedSchema(schema);
607    
608              final long currentTime = System.currentTimeMillis();
609              final long timeout = opts.getPooledSchemaTimeoutMillis();
610              if ((timeout <= 0L) || (timeout+currentTime <= 0L))
611              {
612                pooledSchema = new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
613              }
614              else
615              {
616                pooledSchema =
617                     new ObjectPair<Long,Schema>(timeout+currentTime, schema);
618              }
619            }
620          }
621          catch (final Exception e)
622          {
623            debugException(e);
624          }
625        }
626    
627        final List<LDAPConnection> connList;
628        if (initialConnectThreads > 1)
629        {
630          connList = Collections.synchronizedList(
631               new ArrayList<LDAPConnection>(initialConnections));
632          final ParallelPoolConnector connector = new ParallelPoolConnector(this,
633               connList, initialConnections, initialConnectThreads,
634               throwOnConnectFailure);
635          connector.establishConnections();
636        }
637        else
638        {
639          connList = new ArrayList<LDAPConnection>(initialConnections);
640          connection.setConnectionName(null);
641          connection.setConnectionPool(this);
642          connList.add(connection);
643          for (int i=1; i < initialConnections; i++)
644          {
645            try
646            {
647              connList.add(createConnection());
648            }
649            catch (LDAPException le)
650            {
651              debugException(le);
652    
653              if (throwOnConnectFailure)
654              {
655                for (final LDAPConnection c : connList)
656                {
657                  try
658                  {
659                    c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null,
660                         le);
661                    c.terminate(null);
662                  }
663                  catch (Exception e)
664                  {
665                    debugException(e);
666                  }
667                }
668    
669                throw le;
670              }
671            }
672          }
673        }
674    
675        availableConnections.addAll(connList);
676    
677        failedReplaceCount                 =
678             new AtomicInteger(maxConnections - availableConnections.size());
679        createIfNecessary                  = true;
680        checkConnectionAgeOnRelease        = false;
681        maxConnectionAge                   = 0L;
682        maxDefunctReplacementConnectionAge = null;
683        minDisconnectInterval              = 0L;
684        lastExpiredDisconnectTime          = 0L;
685        maxWaitTime                        = 5000L;
686        closed                             = false;
687    
688        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
689        healthCheckThread.start();
690      }
691    
692    
693    
694      /**
695       * Creates a new LDAP connection pool with the specified number of
696       * connections, created using the provided server set.  Initially, only
697       * one will be created and included in the pool, but additional connections
698       * will be created as needed until the pool has reached its full capacity, at
699       * which point the create if necessary and max wait time settings will be used
700       * to determine how to behave if a connection is requested but none are
701       * available.
702       *
703       * @param  serverSet       The server set to use to create the connections.
704       *                         It is acceptable for the server set to create the
705       *                         connections across multiple servers.
706       * @param  bindRequest     The bind request to use to authenticate the
707       *                         connections that are established.  It may be
708       *                         {@code null} if no authentication should be
709       *                         performed on the connections.
710       * @param  numConnections  The total number of connections that should be
711       *                         created in the pool.  It must be greater than or
712       *                         equal to one.
713       *
714       * @throws  LDAPException  If a problem occurs while attempting to establish
715       *                         any of the connections.  If this is thrown, then
716       *                         all connections associated with the pool will be
717       *                         closed.
718       */
719      public LDAPConnectionPool(final ServerSet serverSet,
720                                final BindRequest bindRequest,
721                                final int numConnections)
722             throws LDAPException
723      {
724        this(serverSet, bindRequest, 1, numConnections, null);
725      }
726    
727    
728    
729      /**
730       * Creates a new LDAP connection pool with the specified number of
731       * connections, created using the provided server set.
732       *
733       * @param  serverSet           The server set to use to create the
734       *                             connections.  It is acceptable for the server
735       *                             set to create the connections across multiple
736       *                             servers.
737       * @param  bindRequest         The bind request to use to authenticate the
738       *                             connections that are established.  It may be
739       *                             {@code null} if no authentication should be
740       *                             performed on the connections.
741       * @param  initialConnections  The number of connections to initially
742       *                             establish when the pool is created.  It must be
743       *                             greater than or equal to zero.
744       * @param  maxConnections      The maximum number of connections that should
745       *                             be maintained in the pool.  It must be greater
746       *                             than or equal to the initial number of
747       *                             connections, and must not be zero.
748       *
749       * @throws  LDAPException  If a problem occurs while attempting to establish
750       *                         any of the connections.  If this is thrown, then
751       *                         all connections associated with the pool will be
752       *                         closed.
753       */
754      public LDAPConnectionPool(final ServerSet serverSet,
755                                final BindRequest bindRequest,
756                                final int initialConnections,
757                                final int maxConnections)
758             throws LDAPException
759      {
760        this(serverSet, bindRequest, initialConnections, maxConnections, null);
761      }
762    
763    
764    
765      /**
766       * Creates a new LDAP connection pool with the specified number of
767       * connections, created using the provided server set.
768       *
769       * @param  serverSet             The server set to use to create the
770       *                               connections.  It is acceptable for the server
771       *                               set to create the connections across multiple
772       *                               servers.
773       * @param  bindRequest           The bind request to use to authenticate the
774       *                               connections that are established.  It may be
775       *                               {@code null} if no authentication should be
776       *                               performed on the connections.
777       * @param  initialConnections    The number of connections to initially
778       *                               establish when the pool is created.  It must
779       *                               be greater than or equal to zero.
780       * @param  maxConnections        The maximum number of connections that should
781       *                               be maintained in the pool.  It must be
782       *                               greater than or equal to the initial number
783       *                               of connections, and must not be zero.
784       * @param  postConnectProcessor  A processor that should be used to perform
785       *                               any post-connect processing for connections
786       *                               in this pool.  It may be {@code null} if no
787       *                               special processing is needed.
788       *
789       * @throws  LDAPException  If a problem occurs while attempting to establish
790       *                         any of the connections.  If this is thrown, then
791       *                         all connections associated with the pool will be
792       *                         closed.
793       */
794      public LDAPConnectionPool(final ServerSet serverSet,
795                                final BindRequest bindRequest,
796                                final int initialConnections,
797                                final int maxConnections,
798                                final PostConnectProcessor postConnectProcessor)
799             throws LDAPException
800      {
801        this(serverSet, bindRequest, initialConnections, maxConnections,
802             postConnectProcessor, true);
803      }
804    
805    
806    
807      /**
808       * Creates a new LDAP connection pool with the specified number of
809       * connections, created using the provided server set.
810       *
811       * @param  serverSet              The server set to use to create the
812       *                                connections.  It is acceptable for the
813       *                                server set to create the connections across
814       *                                multiple servers.
815       * @param  bindRequest            The bind request to use to authenticate the
816       *                                connections that are established.  It may be
817       *                                {@code null} if no authentication should be
818       *                                performed on the connections.
819       * @param  initialConnections     The number of connections to initially
820       *                                establish when the pool is created.  It must
821       *                                be greater than or equal to zero.
822       * @param  maxConnections         The maximum number of connections that
823       *                                should be maintained in the pool.  It must
824       *                                be greater than or equal to the initial
825       *                                number of connections, and must not be zero.
826       * @param  postConnectProcessor   A processor that should be used to perform
827       *                                any post-connect processing for connections
828       *                                in this pool.  It may be {@code null} if no
829       *                                special processing is needed.
830       * @param  throwOnConnectFailure  If an exception should be thrown if a
831       *                                problem is encountered while attempting to
832       *                                create the specified initial number of
833       *                                connections.  If {@code true}, then the
834       *                                attempt to create the pool will fail.if any
835       *                                connection cannot be established.  If
836       *                                {@code false}, then the pool will be created
837       *                                but may have fewer than the initial number
838       *                                of connections (or possibly no connections).
839       *
840       * @throws  LDAPException  If a problem occurs while attempting to establish
841       *                         any of the connections and
842       *                         {@code throwOnConnectFailure} is true.  If this is
843       *                         thrown, then all connections associated with the
844       *                         pool will be closed.
845       */
846      public LDAPConnectionPool(final ServerSet serverSet,
847                                final BindRequest bindRequest,
848                                final int initialConnections,
849                                final int maxConnections,
850                                final PostConnectProcessor postConnectProcessor,
851                                final boolean throwOnConnectFailure)
852             throws LDAPException
853      {
854        this(serverSet, bindRequest, initialConnections, maxConnections, 1,
855             postConnectProcessor, throwOnConnectFailure);
856      }
857    
858    
859    
860      /**
861       * Creates a new LDAP connection pool with the specified number of
862       * connections, created using the provided server set.
863       *
864       * @param  serverSet              The server set to use to create the
865       *                                connections.  It is acceptable for the
866       *                                server set to create the connections across
867       *                                multiple servers.
868       * @param  bindRequest            The bind request to use to authenticate the
869       *                                connections that are established.  It may be
870       *                                {@code null} if no authentication should be
871       *                                performed on the connections.
872       * @param  initialConnections     The number of connections to initially
873       *                                establish when the pool is created.  It must
874       *                                be greater than or equal to zero.
875       * @param  maxConnections         The maximum number of connections that
876       *                                should be maintained in the pool.  It must
877       *                                be greater than or equal to the initial
878       *                                number of connections, and must not be zero.
879       * @param  initialConnectThreads  The number of concurrent threads to use to
880       *                                establish the initial set of connections.
881       *                                A value greater than one indicates that the
882       *                                attempt to establish connections should be
883       *                                parallelized.
884       * @param  postConnectProcessor   A processor that should be used to perform
885       *                                any post-connect processing for connections
886       *                                in this pool.  It may be {@code null} if no
887       *                                special processing is needed.
888       * @param  throwOnConnectFailure  If an exception should be thrown if a
889       *                                problem is encountered while attempting to
890       *                                create the specified initial number of
891       *                                connections.  If {@code true}, then the
892       *                                attempt to create the pool will fail.if any
893       *                                connection cannot be established.  If
894       *                                {@code false}, then the pool will be created
895       *                                but may have fewer than the initial number
896       *                                of connections (or possibly no connections).
897       *
898       * @throws  LDAPException  If a problem occurs while attempting to establish
899       *                         any of the connections and
900       *                         {@code throwOnConnectFailure} is true.  If this is
901       *                         thrown, then all connections associated with the
902       *                         pool will be closed.
903       */
904      public LDAPConnectionPool(final ServerSet serverSet,
905                                final BindRequest bindRequest,
906                                final int initialConnections,
907                                final int maxConnections,
908                                final int initialConnectThreads,
909                                final PostConnectProcessor postConnectProcessor,
910                                final boolean throwOnConnectFailure)
911             throws LDAPException
912      {
913        this(serverSet, bindRequest, initialConnections, maxConnections,
914             initialConnectThreads, postConnectProcessor, throwOnConnectFailure,
915             null);
916      }
917    
918    
919    
920      /**
921       * Creates a new LDAP connection pool with the specified number of
922       * connections, created using the provided server set.
923       *
924       * @param  serverSet              The server set to use to create the
925       *                                connections.  It is acceptable for the
926       *                                server set to create the connections across
927       *                                multiple servers.
928       * @param  bindRequest            The bind request to use to authenticate the
929       *                                connections that are established.  It may be
930       *                                {@code null} if no authentication should be
931       *                                performed on the connections.
932       * @param  initialConnections     The number of connections to initially
933       *                                establish when the pool is created.  It must
934       *                                be greater than or equal to zero.
935       * @param  maxConnections         The maximum number of connections that
936       *                                should be maintained in the pool.  It must
937       *                                be greater than or equal to the initial
938       *                                number of connections, and must not be zero.
939       * @param  initialConnectThreads  The number of concurrent threads to use to
940       *                                establish the initial set of connections.
941       *                                A value greater than one indicates that the
942       *                                attempt to establish connections should be
943       *                                parallelized.
944       * @param  postConnectProcessor   A processor that should be used to perform
945       *                                any post-connect processing for connections
946       *                                in this pool.  It may be {@code null} if no
947       *                                special processing is needed.
948       * @param  throwOnConnectFailure  If an exception should be thrown if a
949       *                                problem is encountered while attempting to
950       *                                create the specified initial number of
951       *                                connections.  If {@code true}, then the
952       *                                attempt to create the pool will fail.if any
953       *                                connection cannot be established.  If
954       *                                {@code false}, then the pool will be created
955       *                                but may have fewer than the initial number
956       *                                of connections (or possibly no connections).
957       * @param  healthCheck            The health check that should be used for
958       *                                connections in this pool.  It may be
959       *                                {@code null} if the default health check
960       *                                should be used.
961       *
962       * @throws  LDAPException  If a problem occurs while attempting to establish
963       *                         any of the connections and
964       *                         {@code throwOnConnectFailure} is true.  If this is
965       *                         thrown, then all connections associated with the
966       *                         pool will be closed.
967       */
968      public LDAPConnectionPool(final ServerSet serverSet,
969                                final BindRequest bindRequest,
970                                final int initialConnections,
971                                final int maxConnections,
972                                final int initialConnectThreads,
973                                final PostConnectProcessor postConnectProcessor,
974                                final boolean throwOnConnectFailure,
975                                final LDAPConnectionPoolHealthCheck healthCheck)
976             throws LDAPException
977      {
978        ensureNotNull(serverSet);
979        ensureTrue(initialConnections >= 0,
980                   "LDAPConnectionPool.initialConnections must be greater than " +
981                        "or equal to 0.");
982        ensureTrue(maxConnections > 0,
983                   "LDAPConnectionPool.maxConnections must be greater than 0.");
984        ensureTrue(maxConnections >= initialConnections,
985                   "LDAPConnectionPool.initialConnections must not be greater " +
986                        "than maxConnections.");
987    
988        this.serverSet            = serverSet;
989        this.bindRequest          = bindRequest;
990        this.postConnectProcessor = postConnectProcessor;
991    
992        trySynchronousReadDuringHealthCheck = false;
993        healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;
994        poolStatistics      = new LDAPConnectionPoolStatistics(this);
995        pooledSchema        = null;
996        connectionPoolName  = null;
997        retryOperationTypes = new AtomicReference<Set<OperationType>>(
998             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
999    
1000        if (healthCheck == null)
1001        {
1002          this.healthCheck = new LDAPConnectionPoolHealthCheck();
1003        }
1004        else
1005        {
1006          this.healthCheck = healthCheck;
1007        }
1008    
1009        final List<LDAPConnection> connList;
1010        if (initialConnectThreads > 1)
1011        {
1012          connList = Collections.synchronizedList(
1013               new ArrayList<LDAPConnection>(initialConnections));
1014          final ParallelPoolConnector connector = new ParallelPoolConnector(this,
1015               connList, initialConnections, initialConnectThreads,
1016               throwOnConnectFailure);
1017          connector.establishConnections();
1018        }
1019        else
1020        {
1021          connList = new ArrayList<LDAPConnection>(initialConnections);
1022          for (int i=0; i < initialConnections; i++)
1023          {
1024            try
1025            {
1026              connList.add(createConnection());
1027            }
1028            catch (LDAPException le)
1029            {
1030              debugException(le);
1031    
1032              if (throwOnConnectFailure)
1033              {
1034                for (final LDAPConnection c : connList)
1035                {
1036                  try
1037                  {
1038                    c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null,
1039                         le);
1040                    c.terminate(null);
1041                  } catch (Exception e)
1042                  {
1043                    debugException(e);
1044                  }
1045                }
1046    
1047                throw le;
1048              }
1049            }
1050          }
1051        }
1052    
1053        numConnections = maxConnections;
1054    
1055        availableConnections =
1056             new LinkedBlockingQueue<LDAPConnection>(numConnections);
1057        availableConnections.addAll(connList);
1058    
1059        failedReplaceCount                 =
1060             new AtomicInteger(maxConnections - availableConnections.size());
1061        createIfNecessary                  = true;
1062        checkConnectionAgeOnRelease        = false;
1063        maxConnectionAge                   = 0L;
1064        maxDefunctReplacementConnectionAge = null;
1065        minDisconnectInterval              = 0L;
1066        lastExpiredDisconnectTime          = 0L;
1067        maxWaitTime                        = 5000L;
1068        closed                             = false;
1069    
1070        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
1071        healthCheckThread.start();
1072      }
1073    
1074    
1075    
1076      /**
1077       * Creates a new LDAP connection for use in this pool.
1078       *
1079       * @return  A new connection created for use in this pool.
1080       *
1081       * @throws  LDAPException  If a problem occurs while attempting to establish
1082       *                         the connection.  If a connection had been created,
1083       *                         it will be closed.
1084       */
1085      LDAPConnection createConnection()
1086                     throws LDAPException
1087      {
1088        final LDAPConnection c = serverSet.getConnection(healthCheck);
1089        c.setConnectionPool(this);
1090    
1091        // Auto-reconnect must be disabled for pooled connections, so turn it off
1092        // if the associated connection options have it enabled for some reason.
1093        LDAPConnectionOptions opts = c.getConnectionOptions();
1094        if (opts.autoReconnect())
1095        {
1096          opts = opts.duplicate();
1097          opts.setAutoReconnect(false);
1098          c.setConnectionOptions(opts);
1099        }
1100    
1101        if (postConnectProcessor != null)
1102        {
1103          try
1104          {
1105            postConnectProcessor.processPreAuthenticatedConnection(c);
1106          }
1107          catch (Exception e)
1108          {
1109            debugException(e);
1110    
1111            try
1112            {
1113              poolStatistics.incrementNumFailedConnectionAttempts();
1114              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
1115              c.terminate(null);
1116            }
1117            catch (Exception e2)
1118            {
1119              debugException(e2);
1120            }
1121    
1122            if (e instanceof LDAPException)
1123            {
1124              throw ((LDAPException) e);
1125            }
1126            else
1127            {
1128              throw new LDAPException(ResultCode.CONNECT_ERROR,
1129                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
1130            }
1131          }
1132        }
1133    
1134        try
1135        {
1136          if (bindRequest != null)
1137          {
1138            c.bind(bindRequest.duplicate());
1139          }
1140        }
1141        catch (Exception e)
1142        {
1143          debugException(e);
1144          try
1145          {
1146            poolStatistics.incrementNumFailedConnectionAttempts();
1147            c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e);
1148            c.terminate(null);
1149          }
1150          catch (Exception e2)
1151          {
1152            debugException(e2);
1153          }
1154    
1155          if (e instanceof LDAPException)
1156          {
1157            throw ((LDAPException) e);
1158          }
1159          else
1160          {
1161            throw new LDAPException(ResultCode.CONNECT_ERROR,
1162                 ERR_POOL_CONNECT_ERROR.get(getExceptionMessage(e)), e);
1163          }
1164        }
1165    
1166        if (postConnectProcessor != null)
1167        {
1168          try
1169          {
1170            postConnectProcessor.processPostAuthenticatedConnection(c);
1171          }
1172          catch (Exception e)
1173          {
1174            debugException(e);
1175            try
1176            {
1177              poolStatistics.incrementNumFailedConnectionAttempts();
1178              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
1179              c.terminate(null);
1180            }
1181            catch (Exception e2)
1182            {
1183              debugException(e2);
1184            }
1185    
1186            if (e instanceof LDAPException)
1187            {
1188              throw ((LDAPException) e);
1189            }
1190            else
1191            {
1192              throw new LDAPException(ResultCode.CONNECT_ERROR,
1193                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
1194            }
1195          }
1196        }
1197    
1198        if (opts.usePooledSchema())
1199        {
1200          final long currentTime = System.currentTimeMillis();
1201          if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst()))
1202          {
1203            try
1204            {
1205              final Schema schema = c.getSchema();
1206              if (schema != null)
1207              {
1208                c.setCachedSchema(schema);
1209    
1210                final long timeout = opts.getPooledSchemaTimeoutMillis();
1211                if ((timeout <= 0L) || (currentTime + timeout <= 0L))
1212                {
1213                  pooledSchema =
1214                       new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
1215                }
1216                else
1217                {
1218                  pooledSchema =
1219                       new ObjectPair<Long,Schema>((currentTime+timeout), schema);
1220                }
1221              }
1222            }
1223            catch (final Exception e)
1224            {
1225              debugException(e);
1226    
1227              // There was a problem retrieving the schema from the server, but if
1228              // we have an earlier copy then we can assume it's still valid.
1229              if (pooledSchema != null)
1230              {
1231                c.setCachedSchema(pooledSchema.getSecond());
1232              }
1233            }
1234          }
1235          else
1236          {
1237            c.setCachedSchema(pooledSchema.getSecond());
1238          }
1239        }
1240    
1241        c.setConnectionPoolName(connectionPoolName);
1242        poolStatistics.incrementNumSuccessfulConnectionAttempts();
1243    
1244        return c;
1245      }
1246    
1247    
1248    
1249      /**
1250       * {@inheritDoc}
1251       */
1252      @Override()
1253      public void close()
1254      {
1255        close(true, 1);
1256      }
1257    
1258    
1259    
1260      /**
1261       * {@inheritDoc}
1262       */
1263      @Override()
1264      public void close(final boolean unbind, final int numThreads)
1265      {
1266        closed = true;
1267        healthCheckThread.stopRunning();
1268    
1269        if (numThreads > 1)
1270        {
1271          final ArrayList<LDAPConnection> connList =
1272               new ArrayList<LDAPConnection>(availableConnections.size());
1273          availableConnections.drainTo(connList);
1274    
1275          if (! connList.isEmpty())
1276          {
1277            final ParallelPoolCloser closer =
1278                 new ParallelPoolCloser(connList, unbind, numThreads);
1279            closer.closeConnections();
1280          }
1281        }
1282        else
1283        {
1284          while (true)
1285          {
1286            final LDAPConnection conn = availableConnections.poll();
1287            if (conn == null)
1288            {
1289              return;
1290            }
1291            else
1292            {
1293              poolStatistics.incrementNumConnectionsClosedUnneeded();
1294              conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
1295              if (unbind)
1296              {
1297                conn.terminate(null);
1298              }
1299              else
1300              {
1301                conn.setClosed();
1302              }
1303            }
1304          }
1305        }
1306      }
1307    
1308    
1309    
1310      /**
1311       * {@inheritDoc}
1312       */
1313      @Override()
1314      public boolean isClosed()
1315      {
1316        return closed;
1317      }
1318    
1319    
1320    
1321      /**
1322       * Processes a simple bind using a connection from this connection pool, and
1323       * then reverts that authentication by re-binding as the same user used to
1324       * authenticate new connections.  If new connections are unauthenticated, then
1325       * the subsequent bind will be an anonymous simple bind.  This method attempts
1326       * to ensure that processing the provided bind operation does not have a
1327       * lasting impact the authentication state of the connection used to process
1328       * it.
1329       * <BR><BR>
1330       * If the second bind attempt (the one used to restore the authentication
1331       * identity) fails, the connection will be closed as defunct so that a new
1332       * connection will be created to take its place.
1333       *
1334       * @param  bindDN    The bind DN for the simple bind request.
1335       * @param  password  The password for the simple bind request.
1336       * @param  controls  The optional set of controls for the simple bind request.
1337       *
1338       * @return  The result of processing the provided bind operation.
1339       *
1340       * @throws  LDAPException  If the server rejects the bind request, or if a
1341       *                         problem occurs while sending the request or reading
1342       *                         the response.
1343       */
1344      public BindResult bindAndRevertAuthentication(final String bindDN,
1345                                                    final String password,
1346                                                    final Control... controls)
1347             throws LDAPException
1348      {
1349        return bindAndRevertAuthentication(
1350             new SimpleBindRequest(bindDN, password, controls));
1351      }
1352    
1353    
1354    
1355      /**
1356       * Processes the provided bind request using a connection from this connection
1357       * pool, and then reverts that authentication by re-binding as the same user
1358       * used to authenticate new connections.  If new connections are
1359       * unauthenticated, then the subsequent bind will be an anonymous simple bind.
1360       * This method attempts to ensure that processing the provided bind operation
1361       * does not have a lasting impact the authentication state of the connection
1362       * used to process it.
1363       * <BR><BR>
1364       * If the second bind attempt (the one used to restore the authentication
1365       * identity) fails, the connection will be closed as defunct so that a new
1366       * connection will be created to take its place.
1367       *
1368       * @param  bindRequest  The bind request to be processed.  It must not be
1369       *                      {@code null}.
1370       *
1371       * @return  The result of processing the provided bind operation.
1372       *
1373       * @throws  LDAPException  If the server rejects the bind request, or if a
1374       *                         problem occurs while sending the request or reading
1375       *                         the response.
1376       */
1377      public BindResult bindAndRevertAuthentication(final BindRequest bindRequest)
1378             throws LDAPException
1379      {
1380        LDAPConnection conn = getConnection();
1381    
1382        try
1383        {
1384          final BindResult result = conn.bind(bindRequest);
1385          releaseAndReAuthenticateConnection(conn);
1386          return result;
1387        }
1388        catch (final Throwable t)
1389        {
1390          debugException(t);
1391    
1392          if (t instanceof LDAPException)
1393          {
1394            final LDAPException le = (LDAPException) t;
1395    
1396            boolean shouldThrow;
1397            try
1398            {
1399              healthCheck.ensureConnectionValidAfterException(conn, le);
1400    
1401              // The above call will throw an exception if the connection doesn't
1402              // seem to be valid, so if we've gotten here then we should assume
1403              // that it is valid and we will pass the exception onto the client
1404              // without retrying the operation.
1405              releaseAndReAuthenticateConnection(conn);
1406              shouldThrow = true;
1407            }
1408            catch (final Exception e)
1409            {
1410              debugException(e);
1411    
1412              // This implies that the connection is not valid.  If the pool is
1413              // configured to re-try bind operations on a newly-established
1414              // connection, then that will be done later in this method.
1415              // Otherwise, release the connection as defunct and pass the bind
1416              // exception onto the client.
1417              if (! getOperationTypesToRetryDueToInvalidConnections().contains(
1418                         OperationType.BIND))
1419              {
1420                releaseDefunctConnection(conn);
1421                shouldThrow = true;
1422              }
1423              else
1424              {
1425                shouldThrow = false;
1426              }
1427            }
1428    
1429            if (shouldThrow)
1430            {
1431              throw le;
1432            }
1433          }
1434          else
1435          {
1436            releaseDefunctConnection(conn);
1437            throw new LDAPException(ResultCode.LOCAL_ERROR,
1438                 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
1439          }
1440        }
1441    
1442    
1443        // If we've gotten here, then the bind operation should be re-tried on a
1444        // newly-established connection.
1445        conn = replaceDefunctConnection(conn);
1446    
1447        try
1448        {
1449          final BindResult result = conn.bind(bindRequest);
1450          releaseAndReAuthenticateConnection(conn);
1451          return result;
1452        }
1453        catch (final Throwable t)
1454        {
1455          debugException(t);
1456    
1457          if (t instanceof LDAPException)
1458          {
1459            final LDAPException le = (LDAPException) t;
1460    
1461            try
1462            {
1463              healthCheck.ensureConnectionValidAfterException(conn, le);
1464              releaseAndReAuthenticateConnection(conn);
1465            }
1466            catch (final Exception e)
1467            {
1468              debugException(e);
1469              releaseDefunctConnection(conn);
1470            }
1471    
1472            throw le;
1473          }
1474          else
1475          {
1476            releaseDefunctConnection(conn);
1477            throw new LDAPException(ResultCode.LOCAL_ERROR,
1478                 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
1479          }
1480        }
1481      }
1482    
1483    
1484    
1485      /**
1486       * {@inheritDoc}
1487       */
1488      @Override()
1489      public LDAPConnection getConnection()
1490             throws LDAPException
1491      {
1492        if (closed)
1493        {
1494          poolStatistics.incrementNumFailedCheckouts();
1495          throw new LDAPException(ResultCode.CONNECT_ERROR,
1496                                  ERR_POOL_CLOSED.get());
1497        }
1498    
1499        LDAPConnection conn = availableConnections.poll();
1500        if (conn != null)
1501        {
1502          if (conn.isConnected())
1503          {
1504            try
1505            {
1506              healthCheck.ensureConnectionValidForCheckout(conn);
1507              poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1508              return conn;
1509            }
1510            catch (LDAPException le)
1511            {
1512              debugException(le);
1513            }
1514          }
1515    
1516          handleDefunctConnection(conn);
1517          for (int i=0; i < numConnections; i++)
1518          {
1519            conn = availableConnections.poll();
1520            if (conn == null)
1521            {
1522              break;
1523            }
1524            else if (conn.isConnected())
1525            {
1526              try
1527              {
1528                healthCheck.ensureConnectionValidForCheckout(conn);
1529                poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1530                return conn;
1531              }
1532              catch (LDAPException le)
1533              {
1534                debugException(le);
1535                handleDefunctConnection(conn);
1536              }
1537            }
1538            else
1539            {
1540              handleDefunctConnection(conn);
1541            }
1542          }
1543        }
1544    
1545        if (failedReplaceCount.get() > 0)
1546        {
1547          final int newReplaceCount = failedReplaceCount.getAndDecrement();
1548          if (newReplaceCount > 0)
1549          {
1550            try
1551            {
1552              conn = createConnection();
1553              poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1554              return conn;
1555            }
1556            catch (LDAPException le)
1557            {
1558              debugException(le);
1559              failedReplaceCount.incrementAndGet();
1560              poolStatistics.incrementNumFailedCheckouts();
1561              throw le;
1562            }
1563          }
1564          else
1565          {
1566            failedReplaceCount.incrementAndGet();
1567            poolStatistics.incrementNumFailedCheckouts();
1568            throw new LDAPException(ResultCode.CONNECT_ERROR,
1569                                    ERR_POOL_NO_CONNECTIONS.get());
1570          }
1571        }
1572    
1573        if (maxWaitTime > 0)
1574        {
1575          try
1576          {
1577            conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS);
1578            if (conn != null)
1579            {
1580              try
1581              {
1582                healthCheck.ensureConnectionValidForCheckout(conn);
1583                poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting();
1584                return conn;
1585              }
1586              catch (LDAPException le)
1587              {
1588                debugException(le);
1589                handleDefunctConnection(conn);
1590              }
1591            }
1592          }
1593          catch (InterruptedException ie)
1594          {
1595            debugException(ie);
1596          }
1597        }
1598    
1599        if (createIfNecessary)
1600        {
1601          try
1602          {
1603            conn = createConnection();
1604            poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1605            return conn;
1606          }
1607          catch (LDAPException le)
1608          {
1609            debugException(le);
1610            poolStatistics.incrementNumFailedCheckouts();
1611            throw le;
1612          }
1613        }
1614        else
1615        {
1616          poolStatistics.incrementNumFailedCheckouts();
1617          throw new LDAPException(ResultCode.CONNECT_ERROR,
1618                                  ERR_POOL_NO_CONNECTIONS.get());
1619        }
1620      }
1621    
1622    
1623    
1624      /**
1625       * Attempts to retrieve a connection from the pool that is established to the
1626       * specified server.  Note that this method will only attempt to return an
1627       * existing connection that is currently available, and will not create a
1628       * connection or wait for any checked-out connections to be returned.
1629       *
1630       * @param  host  The address of the server to which the desired connection
1631       *               should be established.  This must not be {@code null}, and
1632       *               this must exactly match the address provided for the initial
1633       *               connection or the {@code ServerSet} used to create the pool.
1634       * @param  port  The port of the server to which the desired connection should
1635       *               be established.
1636       *
1637       * @return  A connection that is established to the specified server, or
1638       *          {@code null} if there are no available connections established to
1639       *          the specified server.
1640       */
1641      public LDAPConnection getConnection(final String host, final int port)
1642      {
1643        if (closed)
1644        {
1645          poolStatistics.incrementNumFailedCheckouts();
1646          return null;
1647        }
1648    
1649        final HashSet<LDAPConnection> examinedConnections =
1650             new HashSet<LDAPConnection>(numConnections);
1651        while (true)
1652        {
1653          final LDAPConnection conn = availableConnections.poll();
1654          if (conn == null)
1655          {
1656            poolStatistics.incrementNumFailedCheckouts();
1657            return null;
1658          }
1659    
1660          if (examinedConnections.contains(conn))
1661          {
1662            availableConnections.offer(conn);
1663            poolStatistics.incrementNumFailedCheckouts();
1664            return null;
1665          }
1666    
1667          if (conn.getConnectedAddress().equals(host) &&
1668              (port == conn.getConnectedPort()))
1669          {
1670            try
1671            {
1672              healthCheck.ensureConnectionValidForCheckout(conn);
1673              poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1674              return conn;
1675            }
1676            catch (final LDAPException le)
1677            {
1678              debugException(le);
1679              handleDefunctConnection(conn);
1680              continue;
1681            }
1682          }
1683    
1684          if (availableConnections.offer(conn))
1685          {
1686            examinedConnections.add(conn);
1687          }
1688        }
1689      }
1690    
1691    
1692    
1693      /**
1694       * {@inheritDoc}
1695       */
1696      @Override()
1697      public void releaseConnection(final LDAPConnection connection)
1698      {
1699        if (connection == null)
1700        {
1701          return;
1702        }
1703    
1704        connection.setConnectionPoolName(connectionPoolName);
1705        if (checkConnectionAgeOnRelease && connectionIsExpired(connection))
1706        {
1707          try
1708          {
1709            final LDAPConnection newConnection = createConnection();
1710            if (availableConnections.offer(newConnection))
1711            {
1712              connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
1713                   null, null);
1714              connection.terminate(null);
1715              poolStatistics.incrementNumConnectionsClosedExpired();
1716              lastExpiredDisconnectTime = System.currentTimeMillis();
1717            }
1718            else
1719            {
1720              newConnection.setDisconnectInfo(
1721                   DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
1722              newConnection.terminate(null);
1723              poolStatistics.incrementNumConnectionsClosedUnneeded();
1724            }
1725          }
1726          catch (final LDAPException le)
1727          {
1728            debugException(le);
1729          }
1730          return;
1731        }
1732    
1733        try
1734        {
1735          healthCheck.ensureConnectionValidForRelease(connection);
1736        }
1737        catch (LDAPException le)
1738        {
1739          releaseDefunctConnection(connection);
1740          return;
1741        }
1742    
1743        if (availableConnections.offer(connection))
1744        {
1745          poolStatistics.incrementNumReleasedValid();
1746        }
1747        else
1748        {
1749          // This means that the connection pool is full, which can happen if the
1750          // pool was empty when a request came in to retrieve a connection and
1751          // createIfNecessary was true.  In this case, we'll just close the
1752          // connection since we don't need it any more.
1753          connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1754                                       null, null);
1755          poolStatistics.incrementNumConnectionsClosedUnneeded();
1756          connection.terminate(null);
1757          return;
1758        }
1759    
1760        if (closed)
1761        {
1762          close();
1763        }
1764      }
1765    
1766    
1767    
1768      /**
1769       * Indicates that the provided connection should be removed from the pool,
1770       * and that no new connection should be created to take its place.  This may
1771       * be used to shrink the pool if such functionality is desired.
1772       *
1773       * @param  connection  The connection to be discarded.
1774       */
1775      public void discardConnection(final LDAPConnection connection)
1776      {
1777        if (connection == null)
1778        {
1779          return;
1780        }
1781    
1782        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1783             null, null);
1784        connection.terminate(null);
1785        poolStatistics.incrementNumConnectionsClosedUnneeded();
1786    
1787        if (availableConnections.remainingCapacity() > 0)
1788        {
1789          final int newReplaceCount = failedReplaceCount.incrementAndGet();
1790          if (newReplaceCount > numConnections)
1791          {
1792            failedReplaceCount.set(numConnections);
1793          }
1794        }
1795      }
1796    
1797    
1798    
1799      /**
1800       * Performs a bind on the provided connection before releasing it back to the
1801       * pool, so that it will be authenticated as the same user as
1802       * newly-established connections.  If newly-established connections are
1803       * unauthenticated, then this method will perform an anonymous simple bind to
1804       * ensure that the resulting connection is unauthenticated.
1805       *
1806       * Releases the provided connection back to this pool.
1807       *
1808       * @param  connection  The connection to be released back to the pool after
1809       *                     being re-authenticated.
1810       */
1811      public void releaseAndReAuthenticateConnection(
1812                       final LDAPConnection connection)
1813      {
1814        if (connection == null)
1815        {
1816          return;
1817        }
1818    
1819        try
1820        {
1821          if (bindRequest == null)
1822          {
1823            connection.bind("", "");
1824          }
1825          else
1826          {
1827            connection.bind(bindRequest);
1828          }
1829    
1830          releaseConnection(connection);
1831        }
1832        catch (final Exception e)
1833        {
1834          debugException(e);
1835          releaseDefunctConnection(connection);
1836        }
1837      }
1838    
1839    
1840    
1841      /**
1842       * {@inheritDoc}
1843       */
1844      @Override()
1845      public void releaseDefunctConnection(final LDAPConnection connection)
1846      {
1847        if (connection == null)
1848        {
1849          return;
1850        }
1851    
1852        connection.setConnectionPoolName(connectionPoolName);
1853        poolStatistics.incrementNumConnectionsClosedDefunct();
1854        handleDefunctConnection(connection);
1855      }
1856    
1857    
1858    
1859      /**
1860       * Performs the real work of terminating a defunct connection and replacing it
1861       * with a new connection if possible.
1862       *
1863       * @param  connection  The defunct connection to be replaced.
1864       *
1865       * @return  The new connection created to take the place of the defunct
1866       *          connection, or {@code null} if no new connection was created.
1867       *          Note that if a connection is returned, it will have already been
1868       *          made available and the caller must not rely on it being unused for
1869       *          any other purpose.
1870       */
1871      private LDAPConnection handleDefunctConnection(
1872                                  final LDAPConnection connection)
1873      {
1874        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1875                                     null);
1876        connection.terminate(null);
1877    
1878        if (closed)
1879        {
1880          return null;
1881        }
1882    
1883        if (createIfNecessary && (availableConnections.remainingCapacity() <= 0))
1884        {
1885          return null;
1886        }
1887    
1888        try
1889        {
1890          final LDAPConnection conn = createConnection();
1891          if (maxDefunctReplacementConnectionAge != null)
1892          {
1893            // Only set the maximum age if there isn't one already set for the
1894            // connection (i.e., because it was defined by the server set).
1895            if (conn.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE) == null)
1896            {
1897              conn.setAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE,
1898                   maxDefunctReplacementConnectionAge);
1899            }
1900          }
1901    
1902          if (! availableConnections.offer(conn))
1903          {
1904            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1905                                   null, null);
1906            conn.terminate(null);
1907            return null;
1908          }
1909    
1910          return conn;
1911        }
1912        catch (LDAPException le)
1913        {
1914          debugException(le);
1915          final int newReplaceCount = failedReplaceCount.incrementAndGet();
1916          if (newReplaceCount > numConnections)
1917          {
1918            failedReplaceCount.set(numConnections);
1919          }
1920          return null;
1921        }
1922      }
1923    
1924    
1925    
1926      /**
1927       * {@inheritDoc}
1928       */
1929      @Override()
1930      public LDAPConnection replaceDefunctConnection(
1931                                 final LDAPConnection connection)
1932             throws LDAPException
1933      {
1934        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1935                                     null);
1936        connection.terminate(null);
1937    
1938        if (closed)
1939        {
1940          throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
1941        }
1942    
1943        return createConnection();
1944      }
1945    
1946    
1947    
1948      /**
1949       * {@inheritDoc}
1950       */
1951      @Override()
1952      public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
1953      {
1954        return retryOperationTypes.get();
1955      }
1956    
1957    
1958    
1959      /**
1960       * {@inheritDoc}
1961       */
1962      @Override()
1963      public void setRetryFailedOperationsDueToInvalidConnections(
1964                       final Set<OperationType> operationTypes)
1965      {
1966        if ((operationTypes == null) || operationTypes.isEmpty())
1967        {
1968          retryOperationTypes.set(
1969               Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
1970        }
1971        else
1972        {
1973          final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
1974          s.addAll(operationTypes);
1975          retryOperationTypes.set(Collections.unmodifiableSet(s));
1976        }
1977      }
1978    
1979    
1980    
1981      /**
1982       * Indicates whether the provided connection should be considered expired.
1983       *
1984       * @param  connection  The connection for which to make the determination.
1985       *
1986       * @return  {@code true} if the provided connection should be considered
1987       *          expired, or {@code false} if not.
1988       */
1989      private boolean connectionIsExpired(final LDAPConnection connection)
1990      {
1991        // There may be a custom maximum connection age for the connection.  If that
1992        // is the case, then use that custom max age rather than the pool-default
1993        // max age.
1994        final long maxAge;
1995        final Object maxAgeObj =
1996             connection.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE);
1997        if ((maxAgeObj != null) && (maxAgeObj instanceof Long))
1998        {
1999          maxAge = (Long) maxAgeObj;
2000        }
2001        else
2002        {
2003          maxAge = maxConnectionAge;
2004        }
2005    
2006        // If connection expiration is not enabled, then there is nothing to do.
2007        if (maxAge <= 0L)
2008        {
2009          return false;
2010        }
2011    
2012        // If there is a minimum disconnect interval, then make sure that we have
2013        // not closed another expired connection too recently.
2014        final long currentTime = System.currentTimeMillis();
2015        if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
2016        {
2017          return false;
2018        }
2019    
2020        // Get the age of the connection and see if it is expired.
2021        final long connectionAge = currentTime - connection.getConnectTime();
2022        return (connectionAge > maxAge);
2023      }
2024    
2025    
2026    
2027      /**
2028       * {@inheritDoc}
2029       */
2030      @Override()
2031      public String getConnectionPoolName()
2032      {
2033        return connectionPoolName;
2034      }
2035    
2036    
2037    
2038      /**
2039       * {@inheritDoc}
2040       */
2041      @Override()
2042      public void setConnectionPoolName(final String connectionPoolName)
2043      {
2044        this.connectionPoolName = connectionPoolName;
2045        for (final LDAPConnection c : availableConnections)
2046        {
2047          c.setConnectionPoolName(connectionPoolName);
2048        }
2049      }
2050    
2051    
2052    
2053      /**
2054       * Indicates whether the connection pool should create a new connection if one
2055       * is requested when there are none available.
2056       *
2057       * @return  {@code true} if a new connection should be created if none are
2058       *          available when a request is received, or {@code false} if an
2059       *          exception should be thrown to indicate that no connection is
2060       *          available.
2061       */
2062      public boolean getCreateIfNecessary()
2063      {
2064        return createIfNecessary;
2065      }
2066    
2067    
2068    
2069      /**
2070       * Specifies whether the connection pool should create a new connection if one
2071       * is requested when there are none available.
2072       *
2073       * @param  createIfNecessary  Specifies whether the connection pool should
2074       *                            create a new connection if one is requested when
2075       *                            there are none available.
2076       */
2077      public void setCreateIfNecessary(final boolean createIfNecessary)
2078      {
2079        this.createIfNecessary = createIfNecessary;
2080      }
2081    
2082    
2083    
2084      /**
2085       * Retrieves the maximum length of time in milliseconds to wait for a
2086       * connection to become available when trying to obtain a connection from the
2087       * pool.
2088       *
2089       * @return  The maximum length of time in milliseconds to wait for a
2090       *          connection to become available when trying to obtain a connection
2091       *          from the pool, or zero to indicate that the pool should not block
2092       *          at all if no connections are available and that it should either
2093       *          create a new connection or throw an exception.
2094       */
2095      public long getMaxWaitTimeMillis()
2096      {
2097        return maxWaitTime;
2098      }
2099    
2100    
2101    
2102      /**
2103       * Specifies the maximum length of time in milliseconds to wait for a
2104       * connection to become available when trying to obtain a connection from the
2105       * pool.
2106       *
2107       * @param  maxWaitTime  The maximum length of time in milliseconds to wait for
2108       *                      a connection to become available when trying to obtain
2109       *                      a connection from the pool.  A value of zero should be
2110       *                      used to indicate that the pool should not block at all
2111       *                      if no connections are available and that it should
2112       *                      either create a new connection or throw an exception.
2113       */
2114      public void setMaxWaitTimeMillis(final long maxWaitTime)
2115      {
2116        if (maxWaitTime > 0L)
2117        {
2118          this.maxWaitTime = maxWaitTime;
2119        }
2120        else
2121        {
2122          this.maxWaitTime = 0L;
2123        }
2124      }
2125    
2126    
2127    
2128      /**
2129       * Retrieves the maximum length of time in milliseconds that a connection in
2130       * this pool may be established before it is closed and replaced with another
2131       * connection.
2132       *
2133       * @return  The maximum length of time in milliseconds that a connection in
2134       *          this pool may be established before it is closed and replaced with
2135       *          another connection, or {@code 0L} if no maximum age should be
2136       *          enforced.
2137       */
2138      public long getMaxConnectionAgeMillis()
2139      {
2140        return maxConnectionAge;
2141      }
2142    
2143    
2144    
2145      /**
2146       * Specifies the maximum length of time in milliseconds that a connection in
2147       * this pool may be established before it should be closed and replaced with
2148       * another connection.
2149       *
2150       * @param  maxConnectionAge  The maximum length of time in milliseconds that a
2151       *                           connection in this pool may be established before
2152       *                           it should be closed and replaced with another
2153       *                           connection.  A value of zero indicates that no
2154       *                           maximum age should be enforced.
2155       */
2156      public void setMaxConnectionAgeMillis(final long maxConnectionAge)
2157      {
2158        if (maxConnectionAge > 0L)
2159        {
2160          this.maxConnectionAge = maxConnectionAge;
2161        }
2162        else
2163        {
2164          this.maxConnectionAge = 0L;
2165        }
2166      }
2167    
2168    
2169    
2170      /**
2171       * Retrieves the maximum connection age that should be used for connections
2172       * that were created in order to replace defunct connections.  It is possible
2173       * to define a custom maximum connection age for these connections to allow
2174       * them to be closed and re-established more quickly to allow for a
2175       * potentially quicker fail-back to a normal state.  Note, that if this
2176       * capability is to be used, then the maximum age for these connections should
2177       * be long enough to allow the problematic server to become available again
2178       * under normal circumstances (e.g., it should be long enough for at least a
2179       * shutdown and restart of the server, plus some overhead for potentially
2180       * performing routine maintenance while the server is offline, or a chance for
2181       * an administrator to be made available that a server has gone down).
2182       *
2183       * @return  The maximum connection age that should be used for connections
2184       *          that were created in order to replace defunct connections, a value
2185       *          of zero to indicate that no maximum age should be enforced, or
2186       *          {@code null} if the value returned by the
2187       *          {@link #getMaxConnectionAgeMillis()} method should be used.
2188       */
2189      public Long getMaxDefunctReplacementConnectionAgeMillis()
2190      {
2191        return maxDefunctReplacementConnectionAge;
2192      }
2193    
2194    
2195    
2196      /**
2197       * Specifies the maximum connection age that should be used for connections
2198       * that were created in order to replace defunct connections.  It is possible
2199       * to define a custom maximum connection age for these connections to allow
2200       * them to be closed and re-established more quickly to allow for a
2201       * potentially quicker fail-back to a normal state.  Note, that if this
2202       * capability is to be used, then the maximum age for these connections should
2203       * be long enough to allow the problematic server to become available again
2204       * under normal circumstances (e.g., it should be long enough for at least a
2205       * shutdown and restart of the server, plus some overhead for potentially
2206       * performing routine maintenance while the server is offline, or a chance for
2207       * an administrator to be made available that a server has gone down).
2208       *
2209       * @param  maxDefunctReplacementConnectionAge  The maximum connection age that
2210       *              should be used for connections that were created in order to
2211       *              replace defunct connections.  It may be zero if no maximum age
2212       *              should be enforced for such connections, or it may be
2213       *              {@code null} if the value returned by the
2214       *              {@link #getMaxConnectionAgeMillis()} method should be used.
2215       */
2216      public void setMaxDefunctReplacementConnectionAgeMillis(
2217                       final Long maxDefunctReplacementConnectionAge)
2218      {
2219        if (maxDefunctReplacementConnectionAge == null)
2220        {
2221          this.maxDefunctReplacementConnectionAge = null;
2222        }
2223        else if (maxDefunctReplacementConnectionAge > 0L)
2224        {
2225          this.maxDefunctReplacementConnectionAge =
2226               maxDefunctReplacementConnectionAge;
2227        }
2228        else
2229        {
2230          this.maxDefunctReplacementConnectionAge = 0L;
2231        }
2232      }
2233    
2234    
2235    
2236      /**
2237       * Indicates whether to check the age of a connection against the configured
2238       * maximum connection age whenever it is released to the pool.  By default,
2239       * connection age is evaluated in the background using the health check
2240       * thread, but it is also possible to configure the pool to additionally
2241       * examine the age of a connection when it is returned to the pool.
2242       * <BR><BR>
2243       * Performing connection age evaluation only in the background will ensure
2244       * that connections are only closed and re-established in a single-threaded
2245       * manner, which helps minimize the load against the target server, but only
2246       * checks connections that are not in use when the health check thread is
2247       * active.  If the pool is configured to also evaluate the connection age when
2248       * connections are returned to the pool, then it may help ensure that the
2249       * maximum connection age is honored more strictly for all connections, but
2250       * in busy applications may lead to cases in which multiple connections are
2251       * closed and re-established simultaneously, which may increase load against
2252       * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2253       * method may be used to help mitigate the potential performance impact of
2254       * closing and re-establishing multiple connections simultaneously.
2255       *
2256       * @return  {@code true} if the connection pool should check connection age in
2257       *          both the background health check thread and when connections are
2258       *          released to the pool, or {@code false} if the connection age
2259       *          should only be checked by the background health check thread.
2260       */
2261      public boolean checkConnectionAgeOnRelease()
2262      {
2263        return checkConnectionAgeOnRelease;
2264      }
2265    
2266    
2267    
2268      /**
2269       * Specifies whether to check the age of a connection against the configured
2270       * maximum connection age whenever it is released to the pool.  By default,
2271       * connection age is evaluated in the background using the health check
2272       * thread, but it is also possible to configure the pool to additionally
2273       * examine the age of a connection when it is returned to the pool.
2274       * <BR><BR>
2275       * Performing connection age evaluation only in the background will ensure
2276       * that connections are only closed and re-established in a single-threaded
2277       * manner, which helps minimize the load against the target server, but only
2278       * checks connections that are not in use when the health check thread is
2279       * active.  If the pool is configured to also evaluate the connection age when
2280       * connections are returned to the pool, then it may help ensure that the
2281       * maximum connection age is honored more strictly for all connections, but
2282       * in busy applications may lead to cases in which multiple connections are
2283       * closed and re-established simultaneously, which may increase load against
2284       * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2285       * method may be used to help mitigate the potential performance impact of
2286       * closing and re-establishing multiple connections simultaneously.
2287       *
2288       * @param  checkConnectionAgeOnRelease  If {@code true}, this indicates that
2289       *                                      the connection pool should check
2290       *                                      connection age in both the background
2291       *                                      health check thread and when
2292       *                                      connections are released to the pool.
2293       *                                      If {@code false}, this indicates that
2294       *                                      the connection pool should check
2295       *                                      connection age only in the background
2296       *                                      health check thread.
2297       */
2298      public void setCheckConnectionAgeOnRelease(
2299                       final boolean checkConnectionAgeOnRelease)
2300      {
2301        this.checkConnectionAgeOnRelease = checkConnectionAgeOnRelease;
2302      }
2303    
2304    
2305    
2306      /**
2307       * Retrieves the minimum length of time in milliseconds that should pass
2308       * between connections closed because they have been established for longer
2309       * than the maximum connection age.
2310       *
2311       * @return  The minimum length of time in milliseconds that should pass
2312       *          between connections closed because they have been established for
2313       *          longer than the maximum connection age, or {@code 0L} if expired
2314       *          connections may be closed as quickly as they are identified.
2315       */
2316      public long getMinDisconnectIntervalMillis()
2317      {
2318        return minDisconnectInterval;
2319      }
2320    
2321    
2322    
2323      /**
2324       * Specifies the minimum length of time in milliseconds that should pass
2325       * between connections closed because they have been established for longer
2326       * than the maximum connection age.
2327       *
2328       * @param  minDisconnectInterval  The minimum length of time in milliseconds
2329       *                                that should pass between connections closed
2330       *                                because they have been established for
2331       *                                longer than the maximum connection age.  A
2332       *                                value less than or equal to zero indicates
2333       *                                that no minimum time should be enforced.
2334       */
2335      public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
2336      {
2337        if (minDisconnectInterval > 0)
2338        {
2339          this.minDisconnectInterval = minDisconnectInterval;
2340        }
2341        else
2342        {
2343          this.minDisconnectInterval = 0L;
2344        }
2345      }
2346    
2347    
2348    
2349      /**
2350       * {@inheritDoc}
2351       */
2352      @Override()
2353      public LDAPConnectionPoolHealthCheck getHealthCheck()
2354      {
2355        return healthCheck;
2356      }
2357    
2358    
2359    
2360      /**
2361       * Sets the health check implementation for this connection pool.
2362       *
2363       * @param  healthCheck  The health check implementation for this connection
2364       *                      pool.  It must not be {@code null}.
2365       */
2366      public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
2367      {
2368        ensureNotNull(healthCheck);
2369        this.healthCheck = healthCheck;
2370      }
2371    
2372    
2373    
2374      /**
2375       * {@inheritDoc}
2376       */
2377      @Override()
2378      public long getHealthCheckIntervalMillis()
2379      {
2380        return healthCheckInterval;
2381      }
2382    
2383    
2384    
2385      /**
2386       * {@inheritDoc}
2387       */
2388      @Override()
2389      public void setHealthCheckIntervalMillis(final long healthCheckInterval)
2390      {
2391        ensureTrue(healthCheckInterval > 0L,
2392             "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
2393        this.healthCheckInterval = healthCheckInterval;
2394        healthCheckThread.wakeUp();
2395      }
2396    
2397    
2398    
2399      /**
2400       * Indicates whether health check processing for connections operating in
2401       * synchronous mode should include attempting to perform a read from each
2402       * connection with a very short timeout.  This can help detect unsolicited
2403       * responses and unexpected connection closures in a more timely manner.  This
2404       * will be ignored for connections not operating in synchronous mode.
2405       *
2406       * @return  {@code true} if health check processing for connections operating
2407       *          in synchronous mode should include a read attempt with a very
2408       *          short timeout, or {@code false} if not.
2409       */
2410      public boolean trySynchronousReadDuringHealthCheck()
2411      {
2412        return trySynchronousReadDuringHealthCheck;
2413      }
2414    
2415    
2416    
2417      /**
2418       * Specifies whether health check processing for connections operating in
2419       * synchronous mode should include attempting to perform a read from each
2420       * connection with a very short timeout.
2421       *
2422       * @param  trySynchronousReadDuringHealthCheck  Indicates whether health check
2423       *                                              processing for connections
2424       *                                              operating in synchronous mode
2425       *                                              should include attempting to
2426       *                                              perform a read from each
2427       *                                              connection with a very short
2428       *                                              timeout.
2429       */
2430      public void setTrySynchronousReadDuringHealthCheck(
2431                       final boolean trySynchronousReadDuringHealthCheck)
2432      {
2433        this.trySynchronousReadDuringHealthCheck =
2434             trySynchronousReadDuringHealthCheck;
2435      }
2436    
2437    
2438    
2439      /**
2440       * {@inheritDoc}
2441       */
2442      @Override()
2443      protected void doHealthCheck()
2444      {
2445        // Create a set used to hold connections that we've already examined.  If we
2446        // encounter the same connection twice, then we know that we don't need to
2447        // do any more work.
2448        final HashSet<LDAPConnection> examinedConnections =
2449             new HashSet<LDAPConnection>(numConnections);
2450    
2451        for (int i=0; i < numConnections; i++)
2452        {
2453          LDAPConnection conn = availableConnections.poll();
2454          if (conn == null)
2455          {
2456            break;
2457          }
2458          else if (examinedConnections.contains(conn))
2459          {
2460            if (! availableConnections.offer(conn))
2461            {
2462              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2463                                     null, null);
2464              poolStatistics.incrementNumConnectionsClosedUnneeded();
2465              conn.terminate(null);
2466            }
2467            break;
2468          }
2469    
2470          if (! conn.isConnected())
2471          {
2472            conn = handleDefunctConnection(conn);
2473            if (conn != null)
2474            {
2475              examinedConnections.add(conn);
2476            }
2477          }
2478          else
2479          {
2480            if (connectionIsExpired(conn))
2481            {
2482              try
2483              {
2484                final LDAPConnection newConnection = createConnection();
2485                if (availableConnections.offer(newConnection))
2486                {
2487                  examinedConnections.add(newConnection);
2488                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
2489                       null, null);
2490                  conn.terminate(null);
2491                  poolStatistics.incrementNumConnectionsClosedExpired();
2492                  lastExpiredDisconnectTime = System.currentTimeMillis();
2493                  continue;
2494                }
2495                else
2496                {
2497                  newConnection.setDisconnectInfo(
2498                       DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
2499                  newConnection.terminate(null);
2500                  poolStatistics.incrementNumConnectionsClosedUnneeded();
2501                }
2502              }
2503              catch (final LDAPException le)
2504              {
2505                debugException(le);
2506              }
2507            }
2508    
2509    
2510            // If the connection is operating in synchronous mode, then try to read
2511            // a message on it using an extremely short timeout.  This can help
2512            // detect a connection closure or unsolicited notification in a more
2513            // timely manner than if we had to wait for the client code to try to
2514            // use the connection.
2515            if (trySynchronousReadDuringHealthCheck && conn.synchronousMode())
2516            {
2517              int previousTimeout = Integer.MIN_VALUE;
2518              Socket s = null;
2519              try
2520              {
2521                s = conn.getConnectionInternals(true).getSocket();
2522                previousTimeout = s.getSoTimeout();
2523                s.setSoTimeout(1);
2524    
2525                final LDAPResponse response = conn.readResponse(0);
2526                if (response instanceof ConnectionClosedResponse)
2527                {
2528                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
2529                       ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
2530                  poolStatistics.incrementNumConnectionsClosedDefunct();
2531                  conn = handleDefunctConnection(conn);
2532                  if (conn != null)
2533                  {
2534                    examinedConnections.add(conn);
2535                  }
2536                  continue;
2537                }
2538                else if (response instanceof ExtendedResult)
2539                {
2540                  // This means we got an unsolicited response.  It could be a
2541                  // notice of disconnection, or it could be something else, but in
2542                  // any case we'll send it to the connection's unsolicited
2543                  // notification handler (if one is defined).
2544                  final UnsolicitedNotificationHandler h = conn.
2545                       getConnectionOptions().getUnsolicitedNotificationHandler();
2546                  if (h != null)
2547                  {
2548                    h.handleUnsolicitedNotification(conn,
2549                         (ExtendedResult) response);
2550                  }
2551                }
2552                else if (response instanceof LDAPResult)
2553                {
2554                  final LDAPResult r = (LDAPResult) response;
2555                  if (r.getResultCode() == ResultCode.SERVER_DOWN)
2556                  {
2557                    conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
2558                         ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
2559                    poolStatistics.incrementNumConnectionsClosedDefunct();
2560                    conn = handleDefunctConnection(conn);
2561                    if (conn != null)
2562                    {
2563                      examinedConnections.add(conn);
2564                    }
2565                    continue;
2566                  }
2567                }
2568              }
2569              catch (final LDAPException le)
2570              {
2571                if (le.getResultCode() == ResultCode.TIMEOUT)
2572                {
2573                  debugException(Level.FINEST, le);
2574                }
2575                else
2576                {
2577                  debugException(le);
2578                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
2579                       ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
2580                            getExceptionMessage(le)), le);
2581                  poolStatistics.incrementNumConnectionsClosedDefunct();
2582                  conn = handleDefunctConnection(conn);
2583                  if (conn != null)
2584                  {
2585                    examinedConnections.add(conn);
2586                  }
2587                  continue;
2588                }
2589              }
2590              catch (final Exception e)
2591              {
2592                debugException(e);
2593                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
2594                     ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(getExceptionMessage(e)),
2595                     e);
2596                poolStatistics.incrementNumConnectionsClosedDefunct();
2597                conn = handleDefunctConnection(conn);
2598                if (conn != null)
2599                {
2600                  examinedConnections.add(conn);
2601                }
2602                continue;
2603              }
2604              finally
2605              {
2606                if (previousTimeout != Integer.MIN_VALUE)
2607                {
2608                  try
2609                  {
2610                    s.setSoTimeout(previousTimeout);
2611                  }
2612                  catch (final Exception e)
2613                  {
2614                    debugException(e);
2615                    conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
2616                         null, e);
2617                    poolStatistics.incrementNumConnectionsClosedDefunct();
2618                    conn = handleDefunctConnection(conn);
2619                    if (conn != null)
2620                    {
2621                      examinedConnections.add(conn);
2622                    }
2623                    continue;
2624                  }
2625                }
2626              }
2627            }
2628    
2629            try
2630            {
2631              healthCheck.ensureConnectionValidForContinuedUse(conn);
2632              if (availableConnections.offer(conn))
2633              {
2634                examinedConnections.add(conn);
2635              }
2636              else
2637              {
2638                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2639                                       null, null);
2640                poolStatistics.incrementNumConnectionsClosedUnneeded();
2641                conn.terminate(null);
2642              }
2643            }
2644            catch (Exception e)
2645            {
2646              debugException(e);
2647              conn = handleDefunctConnection(conn);
2648              if (conn != null)
2649              {
2650                examinedConnections.add(conn);
2651              }
2652            }
2653          }
2654        }
2655      }
2656    
2657    
2658    
2659      /**
2660       * {@inheritDoc}
2661       */
2662      @Override()
2663      public int getCurrentAvailableConnections()
2664      {
2665        return availableConnections.size();
2666      }
2667    
2668    
2669    
2670      /**
2671       * {@inheritDoc}
2672       */
2673      @Override()
2674      public int getMaximumAvailableConnections()
2675      {
2676        return numConnections;
2677      }
2678    
2679    
2680    
2681      /**
2682       * {@inheritDoc}
2683       */
2684      @Override()
2685      public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
2686      {
2687        return poolStatistics;
2688      }
2689    
2690    
2691    
2692      /**
2693       * Attempts to reduce the number of connections available for use in the pool.
2694       * Note that this will be a best-effort attempt to reach the desired number
2695       * of connections, as other threads interacting with the connection pool may
2696       * check out and/or release connections that cause the number of available
2697       * connections to fluctuate.
2698       *
2699       * @param  connectionsToRetain  The number of connections that should be
2700       *                              retained for use in the connection pool.
2701       */
2702      public void shrinkPool(final int connectionsToRetain)
2703      {
2704        while (availableConnections.size() > connectionsToRetain)
2705        {
2706          final LDAPConnection conn;
2707          try
2708          {
2709            conn = getConnection();
2710          }
2711          catch (final LDAPException le)
2712          {
2713            return;
2714          }
2715    
2716          if (availableConnections.size() >= connectionsToRetain)
2717          {
2718            discardConnection(conn);
2719          }
2720          else
2721          {
2722            releaseConnection(conn);
2723            return;
2724          }
2725        }
2726      }
2727    
2728    
2729    
2730      /**
2731       * Closes this connection pool in the event that it becomes unreferenced.
2732       *
2733       * @throws  Throwable  If an unexpected problem occurs.
2734       */
2735      @Override()
2736      protected void finalize()
2737                throws Throwable
2738      {
2739        super.finalize();
2740    
2741        close();
2742      }
2743    
2744    
2745    
2746      /**
2747       * {@inheritDoc}
2748       */
2749      @Override()
2750      public void toString(final StringBuilder buffer)
2751      {
2752        buffer.append("LDAPConnectionPool(");
2753    
2754        final String name = connectionPoolName;
2755        if (name != null)
2756        {
2757          buffer.append("name='");
2758          buffer.append(name);
2759          buffer.append("', ");
2760        }
2761    
2762        buffer.append("serverSet=");
2763        serverSet.toString(buffer);
2764        buffer.append(", maxConnections=");
2765        buffer.append(numConnections);
2766        buffer.append(')');
2767      }
2768    }