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