001    /*
002     * Copyright 2007-2012 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2012 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.Set;
031    import java.util.logging.Level;
032    import java.util.concurrent.LinkedBlockingQueue;
033    import java.util.concurrent.TimeUnit;
034    import java.util.concurrent.atomic.AtomicInteger;
035    import java.util.concurrent.atomic.AtomicReference;
036    
037    import com.unboundid.ldap.protocol.LDAPResponse;
038    import com.unboundid.util.ThreadSafety;
039    import com.unboundid.util.ThreadSafetyLevel;
040    
041    import static com.unboundid.ldap.sdk.LDAPMessages.*;
042    import static com.unboundid.util.Debug.*;
043    import static com.unboundid.util.StaticUtils.*;
044    import static com.unboundid.util.Validator.*;
045    
046    
047    
048    /**
049     * This class provides an implementation of an LDAP connection pool, which is a
050     * structure that can hold multiple connections established to a given server
051     * that can be reused for multiple operations rather than creating and
052     * destroying connections for each operation.  This connection pool
053     * implementation provides traditional methods for checking out and releasing
054     * connections, but it also provides wrapper methods that make it easy to
055     * perform operations using pooled connections without the need to explicitly
056     * check out or release the connections.
057     * <BR><BR>
058     * Note that both the {@code LDAPConnectionPool} class and the
059     * {@link LDAPConnection} class implement the {@link LDAPInterface} interface.
060     * This is a common interface that defines a number of common methods for
061     * processing LDAP requests.  This means that in many cases, an application can
062     * use an object of type {@link LDAPInterface} rather than
063     * {@link LDAPConnection}, which makes it possible to work with either a single
064     * standalone connection or with a connection pool.
065     * <BR><BR>
066     * <H2>Creating a Connection Pool</H2>
067     * An LDAP connection pool can be created from either a single
068     * {@link LDAPConnection} (for which an appropriate number of copies will be
069     * created to fill out the pool) or using a {@link ServerSet} to create
070     * connections that may span multiple servers.  For example:
071     * <BR><BR>
072     * <PRE>
073     *   // Create a new LDAP connection pool with ten connections established and
074     *   // authenticated to the same server:
075     *   LDAPConnection connection = new LDAPConnection(address, port);
076     *   BindResult bindResult = connection.bind(bindDN, password);
077     *   LDAPConnectionPool connectionPool = new LDAPConnectionPool(connection, 10);
078     *
079     *   // Create a new LDAP connection pool with 10 connections spanning multiple
080     *   // servers using a server set.
081     *   RoundRobinServerSet serverSet = new RoundRobinServerSet(addresses, ports);
082     *   SimpleBindRequest bindRequest = new SimpleBindRequest(bindDN, password);
083     *   LDAPConnectionPool connectionPool =
084     *        new LDAPConnectionPool(serverSet, bindRequest, 10);
085     * </PRE>
086     * Note that in some cases, such as when using StartTLS, it may be necessary to
087     * perform some additional processing when a new connection is created for use
088     * in the connection pool.  In this case, a {@link PostConnectProcessor} should
089     * be provided to accomplish this.  See the documentation for the
090     * {@link StartTLSPostConnectProcessor} class for an example that demonstrates
091     * its use for creating a connection pool with connections secured using
092     * StartTLS.
093     * <BR><BR>
094     * <H2>Processing Operations with a Connection Pool</H2>
095     * If a single operation is to be processed using a connection from the
096     * connection pool, then it can be used without the need to check out or release
097     * a connection or perform any validity checking on the connection.  This can
098     * be accomplished via the {@link LDAPInterface} interface that allows a
099     * connection pool to be treated like a single connection.  For example, to
100     * perform a search using a pooled connection:
101     * <PRE>
102     *   SearchResult searchResult =
103     *        connectionPool.search("dc=example,dc=com", SearchScope.SUB,
104     *                              "(uid=john.doe)");
105     * </PRE>
106     * If an application needs to process multiple operations using a single
107     * connection, then it may be beneficial to obtain a connection from the pool
108     * to use for processing those operations and then return it back to the pool
109     * when it is no longer needed.  This can be done using the
110     * {@link #getConnection} and {@link #releaseConnection} methods.  If during
111     * processing it is determined that the connection is no longer valid, then the
112     * connection should be released back to the pool using the
113     * {@link #releaseDefunctConnection} method, which will ensure that the
114     * connection is closed and a new connection will be established to take its
115     * place in the pool.
116     * <BR><BR>
117     * Note that it is also possible to process multiple operations on a single
118     * connection using the {@link #processRequests} method.  This may be useful if
119     * a fixed set of operations should be processed over the same connection and
120     * none of the subsequent requests depend upon the results of the earlier
121     * operations.
122     * <BR><BR>
123     * Connection pools should generally not be used when performing operations that
124     * may change the state of the underlying connections.  This is particularly
125     * true for bind operations and the StartTLS extended operation, but it may
126     * apply to other types of operations as well.
127     * <BR><BR>
128     * Performing a bind operation using a connection from the pool will invalidate
129     * any previous authentication on that connection, and if that connection is
130     * released back to the pool without first being re-authenticated as the
131     * original user, then subsequent operation attempts may fail or be processed in
132     * an incorrect manner.  Bind operations should only be performed in a
133     * connection pool if the pool is to be used exclusively for processing binds,
134     * if the bind request is specially crafted so that it will not change the
135     * identity of the associated connection (e.g., by including the retain identity
136     * request control in the bind request if using the Commercial Edition of the
137     * LDAP SDK with an UnboundID Directory Server), or if the code using the
138     * connection pool makes sure to re-authenticate the connection as the
139     * appropriate user whenever its identity has been changed.
140     * <BR><BR>
141     * The StartTLS extended operation should never be invoked on a connection which
142     * is part of a connection pool.  It is acceptable for the pool to maintain
143     * connections which have been configured with StartTLS security prior to being
144     * added to the pool (via the use of the {@link StartTLSPostConnectProcessor}).
145     */
146    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
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      // A counter used to keep track of the number of times that the pool failed to
159      // replace a defunct connection.  It may also be initialized to the difference
160      // between the initial and maximum number of connections that should be
161      // included in the pool.
162      private final AtomicInteger failedReplaceCount;
163    
164      // The types of operations that should be retried if they fail in a manner
165      // that may be the result of a connection that is no longer valid.
166      private final AtomicReference<Set<OperationType>> retryOperationTypes;
167    
168      // Indicates whether this connection pool has been closed.
169      private volatile boolean closed;
170    
171      // Indicates whether to create a new connection if necessary rather than
172      // waiting for a connection to become available.
173      private boolean createIfNecessary;
174    
175      // Indicates whether health check processing for connections in synchronous
176      // mode should include attempting to read with a very short timeout to attempt
177      // to detect closures and unsolicited notifications in a more timely manner.
178      private volatile boolean trySynchronousReadDuringHealthCheck;
179    
180      // The bind request to use to perform authentication whenever a new connection
181      // is established.
182      private final BindRequest bindRequest;
183    
184      // The number of connections to be held in this pool.
185      private final int numConnections;
186    
187      // The health check implementation that should be used for this connection
188      // pool.
189      private LDAPConnectionPoolHealthCheck healthCheck;
190    
191      // The thread that will be used to perform periodic background health checks
192      // for this connection pool.
193      private final LDAPConnectionPoolHealthCheckThread healthCheckThread;
194    
195      // The statistics for this connection pool.
196      private final LDAPConnectionPoolStatistics poolStatistics;
197    
198      // The set of connections that are currently available for use.
199      private final LinkedBlockingQueue<LDAPConnection> availableConnections;
200    
201      // The length of time in milliseconds between periodic health checks against
202      // the available connections in this pool.
203      private volatile long healthCheckInterval;
204    
205      // The time that the last expired connection was closed.
206      private volatile long lastExpiredDisconnectTime;
207    
208      // The maximum length of time in milliseconds that a connection should be
209      // allowed to be established before terminating and re-establishing the
210      // connection.
211      private volatile long maxConnectionAge;
212    
213      // The maximum length of time in milliseconds to wait for a connection to be
214      // available.
215      private long maxWaitTime;
216    
217      // The minimum length of time in milliseconds that must pass between
218      // disconnects of connections that have exceeded the maximum connection age.
219      private volatile long minDisconnectInterval;
220    
221      // The post-connect processor for this connection pool, if any.
222      private final PostConnectProcessor postConnectProcessor;
223    
224      // The server set to use for establishing connections for use by this pool.
225      private final ServerSet serverSet;
226    
227      // The user-friendly name assigned to this connection pool.
228      private String connectionPoolName;
229    
230    
231    
232    
233      /**
234       * Creates a new LDAP connection pool with up to the specified number of
235       * connections, created as clones of the provided connection.  Initially, only
236       * the provided connection will be included in the pool, but additional
237       * connections will be created as needed until the pool has reached its full
238       * capacity, at which point the create if necessary and max wait time settings
239       * will be used to determine how to behave if a connection is requested but
240       * none are available.
241       *
242       * @param  connection      The connection to use to provide the template for
243       *                         the other connections to be created.  This
244       *                         connection will be included in the pool.  It must
245       *                         not be {@code null}, and it must be established to
246       *                         the target server.  It does not necessarily need to
247       *                         be authenticated if all connections in the pool are
248       *                         to be unauthenticated.
249       * @param  numConnections  The total number of connections that should be
250       *                         created in the pool.  It must be greater than or
251       *                         equal to one.
252       *
253       * @throws  LDAPException  If the provided connection cannot be used to
254       *                         initialize the pool, or if a problem occurs while
255       *                         attempting to establish any of the connections.  If
256       *                         this is thrown, then all connections associated
257       *                         with the pool (including the one provided as an
258       *                         argument) will be closed.
259       */
260      public LDAPConnectionPool(final LDAPConnection connection,
261                                final int numConnections)
262             throws LDAPException
263      {
264        this(connection, 1, numConnections, null);
265      }
266    
267    
268    
269      /**
270       * Creates a new LDAP connection pool with the specified number of
271       * connections, created as clones of the provided connection.
272       *
273       * @param  connection          The connection to use to provide the template
274       *                             for the other connections to be created.  This
275       *                             connection will be included in the pool.  It
276       *                             must not be {@code null}, and it must be
277       *                             established to the target server.  It does not
278       *                             necessarily need to be authenticated if all
279       *                             connections in the pool are to be
280       *                             unauthenticated.
281       * @param  initialConnections  The number of connections to initially
282       *                             establish when the pool is created.  It must be
283       *                             greater than or equal to one.
284       * @param  maxConnections      The maximum number of connections that should
285       *                             be maintained in the pool.  It must be greater
286       *                             than or equal to the initial number of
287       *                             connections.
288       *
289       * @throws  LDAPException  If the provided connection cannot be used to
290       *                         initialize the pool, or if a problem occurs while
291       *                         attempting to establish any of the connections.  If
292       *                         this is thrown, then all connections associated
293       *                         with the pool (including the one provided as an
294       *                         argument) will be closed.
295       */
296      public LDAPConnectionPool(final LDAPConnection connection,
297                                final int initialConnections,
298                                final int maxConnections)
299             throws LDAPException
300      {
301        this(connection, initialConnections, maxConnections, null);
302      }
303    
304    
305    
306      /**
307       * Creates a new LDAP connection pool with the specified number of
308       * connections, created as clones of the provided connection.
309       *
310       * @param  connection            The connection to use to provide the template
311       *                               for the other connections to be created.
312       *                               This connection will be included in the pool.
313       *                               It must not be {@code null}, and it must be
314       *                               established to the target server.  It does
315       *                               not necessarily need to be authenticated if
316       *                               all connections in the pool are to be
317       *                               unauthenticated.
318       * @param  initialConnections    The number of connections to initially
319       *                               establish when the pool is created.  It must
320       *                               be greater than or equal to one.
321       * @param  maxConnections        The maximum number of connections that should
322       *                               be maintained in the pool.  It must be
323       *                               greater than or equal to the initial number
324       *                               of connections.
325       * @param  postConnectProcessor  A processor that should be used to perform
326       *                               any post-connect processing for connections
327       *                               in this pool.  It may be {@code null} if no
328       *                               special processing is needed.  Note that this
329       *                               processing will not be invoked on the
330       *                               provided connection that will be used as the
331       *                               first connection in the pool.
332       *
333       * @throws  LDAPException  If the provided connection cannot be used to
334       *                         initialize the pool, or if a problem occurs while
335       *                         attempting to establish any of the connections.  If
336       *                         this is thrown, then all connections associated
337       *                         with the pool (including the one provided as an
338       *                         argument) will be closed.
339       */
340      public LDAPConnectionPool(final LDAPConnection connection,
341                                final int initialConnections,
342                                final int maxConnections,
343                                final PostConnectProcessor postConnectProcessor)
344             throws LDAPException
345      {
346        ensureNotNull(connection);
347        ensureTrue(initialConnections >= 1,
348                   "LDAPConnectionPool.initialConnections must be at least 1.");
349        ensureTrue(maxConnections >= initialConnections,
350                   "LDAPConnectionPool.initialConnections must not be greater " +
351                        "than maxConnections.");
352    
353        this.postConnectProcessor = postConnectProcessor;
354    
355        trySynchronousReadDuringHealthCheck = true;
356        healthCheck               = new LDAPConnectionPoolHealthCheck();
357        healthCheckInterval       = DEFAULT_HEALTH_CHECK_INTERVAL;
358        poolStatistics            = new LDAPConnectionPoolStatistics(this);
359        connectionPoolName        = null;
360        retryOperationTypes       = new AtomicReference<Set<OperationType>>(
361             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
362    
363        if (! connection.isConnected())
364        {
365          throw new LDAPException(ResultCode.PARAM_ERROR,
366                                  ERR_POOL_CONN_NOT_ESTABLISHED.get());
367        }
368    
369    
370        serverSet = new SingleServerSet(connection.getConnectedAddress(),
371                                        connection.getConnectedPort(),
372                                        connection.getLastUsedSocketFactory(),
373                                        connection.getConnectionOptions());
374        bindRequest = connection.getLastBindRequest();
375    
376        final ArrayList<LDAPConnection> connList =
377             new ArrayList<LDAPConnection>(initialConnections);
378        connection.setConnectionName(null);
379        connection.setConnectionPool(this);
380        connList.add(connection);
381        for (int i=1; i < initialConnections; i++)
382        {
383          try
384          {
385            connList.add(createConnection());
386          }
387          catch (LDAPException le)
388          {
389            debugException(le);
390            for (final LDAPConnection c : connList)
391            {
392              try
393              {
394                c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, le);
395                c.terminate(null);
396              }
397              catch (Exception e)
398              {
399                debugException(e);
400              }
401            }
402    
403            throw le;
404          }
405        }
406    
407        numConnections = maxConnections;
408    
409        availableConnections =
410             new LinkedBlockingQueue<LDAPConnection>(numConnections);
411        availableConnections.addAll(connList);
412    
413        failedReplaceCount        =
414             new AtomicInteger(maxConnections - initialConnections);
415        createIfNecessary         = true;
416        maxConnectionAge          = 0L;
417        minDisconnectInterval     = 0L;
418        lastExpiredDisconnectTime = 0L;
419        maxWaitTime               = 5000L;
420        closed                    = false;
421    
422        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
423        healthCheckThread.start();
424      }
425    
426    
427    
428      /**
429       * Creates a new LDAP connection pool with the specified number of
430       * connections, created using the provided server set.  Initially, only
431       * one will be created and included in the pool, but additional connections
432       * will be created as needed until the pool has reached its full capacity, at
433       * which point the create if necessary and max wait time settings will be used
434       * to determine how to behave if a connection is requested but none are
435       * available.
436       *
437       * @param  serverSet       The server set to use to create the connections.
438       *                         It is acceptable for the server set to create the
439       *                         connections across multiple servers.
440       * @param  bindRequest     The bind request to use to authenticate the
441       *                         connections that are established.  It may be
442       *                         {@code null} if no authentication should be
443       *                         performed on the connections.
444       * @param  numConnections  The total number of connections that should be
445       *                         created in the pool.  It must be greater than or
446       *                         equal to one.
447       *
448       * @throws  LDAPException  If a problem occurs while attempting to establish
449       *                         any of the connections.  If this is thrown, then
450       *                         all connections associated with the pool will be
451       *                         closed.
452       */
453      public LDAPConnectionPool(final ServerSet serverSet,
454                                final BindRequest bindRequest,
455                                final int numConnections)
456             throws LDAPException
457      {
458        this(serverSet, bindRequest, 1, numConnections, null);
459      }
460    
461    
462    
463      /**
464       * Creates a new LDAP connection pool with the specified number of
465       * connections, created using the provided server set.
466       *
467       * @param  serverSet           The server set to use to create the
468       *                             connections.  It is acceptable for the server
469       *                             set to create the connections across multiple
470       *                             servers.
471       * @param  bindRequest         The bind request to use to authenticate the
472       *                             connections that are established.  It may be
473       *                             {@code null} if no authentication should be
474       *                             performed on the connections.
475       * @param  initialConnections  The number of connections to initially
476       *                             establish when the pool is created.  It must be
477       *                             greater than or equal to zero.
478       * @param  maxConnections      The maximum number of connections that should
479       *                             be maintained in the pool.  It must be greater
480       *                             than or equal to the initial number of
481       *                             connections, and must not be zero.
482       *
483       * @throws  LDAPException  If a problem occurs while attempting to establish
484       *                         any of the connections.  If this is thrown, then
485       *                         all connections associated with the pool will be
486       *                         closed.
487       */
488      public LDAPConnectionPool(final ServerSet serverSet,
489                                final BindRequest bindRequest,
490                                final int initialConnections,
491                                final int maxConnections)
492             throws LDAPException
493      {
494        this(serverSet, bindRequest, initialConnections, maxConnections, null);
495      }
496    
497    
498    
499      /**
500       * Creates a new LDAP connection pool with the specified number of
501       * connections, created using the provided server set.
502       *
503       * @param  serverSet             The server set to use to create the
504       *                               connections.  It is acceptable for the server
505       *                               set to create the connections across multiple
506       *                               servers.
507       * @param  bindRequest           The bind request to use to authenticate the
508       *                               connections that are established.  It may be
509       *                               {@code null} if no authentication should be
510       *                               performed on the connections.
511       * @param  initialConnections    The number of connections to initially
512       *                               establish when the pool is created.  It must
513       *                               be greater than or equal to zero.
514       * @param  maxConnections        The maximum number of connections that should
515       *                               be maintained in the pool.  It must be
516       *                               greater than or equal to the initial number
517       *                               of connections, and must not be zero.
518       * @param  postConnectProcessor  A processor that should be used to perform
519       *                               any post-connect processing for connections
520       *                               in this pool.  It may be {@code null} if no
521       *                               special processing is needed.
522       *
523       * @throws  LDAPException  If a problem occurs while attempting to establish
524       *                         any of the connections.  If this is thrown, then
525       *                         all connections associated with the pool will be
526       *                         closed.
527       */
528      public LDAPConnectionPool(final ServerSet serverSet,
529                                final BindRequest bindRequest,
530                                final int initialConnections,
531                                final int maxConnections,
532                                final PostConnectProcessor postConnectProcessor)
533             throws LDAPException
534      {
535        ensureNotNull(serverSet);
536        ensureTrue(initialConnections >= 0,
537                   "LDAPConnectionPool.initialConnections must be greater than " +
538                        "or equal to 0.");
539        ensureTrue(maxConnections > 0,
540                   "LDAPConnectionPool.maxConnections must be greater than 0.");
541        ensureTrue(maxConnections >= initialConnections,
542                   "LDAPConnectionPool.initialConnections must not be greater " +
543                        "than maxConnections.");
544    
545        this.serverSet            = serverSet;
546        this.bindRequest          = bindRequest;
547        this.postConnectProcessor = postConnectProcessor;
548    
549        healthCheck               = new LDAPConnectionPoolHealthCheck();
550        healthCheckInterval       = DEFAULT_HEALTH_CHECK_INTERVAL;
551        poolStatistics            = new LDAPConnectionPoolStatistics(this);
552        connectionPoolName        = null;
553        retryOperationTypes       = new AtomicReference<Set<OperationType>>(
554             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
555    
556        final ArrayList<LDAPConnection> connList =
557             new ArrayList<LDAPConnection>(initialConnections);
558        for (int i=0; i < initialConnections; i++)
559        {
560          try
561          {
562            connList.add(createConnection());
563          }
564          catch (LDAPException le)
565          {
566            debugException(le);
567            for (final LDAPConnection c : connList)
568            {
569              try
570              {
571                c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, le);
572                c.terminate(null);
573              } catch (Exception e)
574              {
575                debugException(e);
576              }
577            }
578    
579            throw le;
580          }
581        }
582    
583        numConnections = maxConnections;
584    
585        availableConnections =
586             new LinkedBlockingQueue<LDAPConnection>(numConnections);
587        availableConnections.addAll(connList);
588    
589        failedReplaceCount        =
590             new AtomicInteger(maxConnections - initialConnections);
591        createIfNecessary         = true;
592        maxConnectionAge          = 0L;
593        minDisconnectInterval     = 0L;
594        lastExpiredDisconnectTime = 0L;
595        maxWaitTime               = 5000L;
596        closed                    = false;
597    
598        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
599        healthCheckThread.start();
600      }
601    
602    
603    
604      /**
605       * Creates a new LDAP connection for use in this pool.
606       *
607       * @return  A new connection created for use in this pool.
608       *
609       * @throws  LDAPException  If a problem occurs while attempting to establish
610       *                         the connection.  If a connection had been created,
611       *                         it will be closed.
612       */
613      private LDAPConnection createConnection()
614              throws LDAPException
615      {
616        final LDAPConnection c = serverSet.getConnection(healthCheck);
617        c.setConnectionPool(this);
618    
619        // Auto-reconnect must be disabled for pooled connections, so turn it off
620        // if the associated connection options have it enabled for some reason.
621        LDAPConnectionOptions opts = c.getConnectionOptions();
622        if (opts.autoReconnect())
623        {
624          opts = opts.duplicate();
625          opts.setAutoReconnect(false);
626          c.setConnectionOptions(opts);
627        }
628    
629        if (postConnectProcessor != null)
630        {
631          try
632          {
633            postConnectProcessor.processPreAuthenticatedConnection(c);
634          }
635          catch (Exception e)
636          {
637            debugException(e);
638    
639            try
640            {
641              poolStatistics.incrementNumFailedConnectionAttempts();
642              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
643              c.terminate(null);
644            }
645            catch (Exception e2)
646            {
647              debugException(e2);
648            }
649    
650            if (e instanceof LDAPException)
651            {
652              throw ((LDAPException) e);
653            }
654            else
655            {
656              throw new LDAPException(ResultCode.CONNECT_ERROR,
657                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
658            }
659          }
660        }
661    
662        try
663        {
664          if (bindRequest != null)
665          {
666            c.bind(bindRequest.duplicate());
667          }
668        }
669        catch (Exception e)
670        {
671          debugException(e);
672          try
673          {
674            poolStatistics.incrementNumFailedConnectionAttempts();
675            c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e);
676            c.terminate(null);
677          }
678          catch (Exception e2)
679          {
680            debugException(e2);
681          }
682    
683          if (e instanceof LDAPException)
684          {
685            throw ((LDAPException) e);
686          }
687          else
688          {
689            throw new LDAPException(ResultCode.CONNECT_ERROR,
690                 ERR_POOL_CONNECT_ERROR.get(getExceptionMessage(e)), e);
691          }
692        }
693    
694        if (postConnectProcessor != null)
695        {
696          try
697          {
698            postConnectProcessor.processPostAuthenticatedConnection(c);
699          }
700          catch (Exception e)
701          {
702            debugException(e);
703            try
704            {
705              poolStatistics.incrementNumFailedConnectionAttempts();
706              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
707              c.terminate(null);
708            }
709            catch (Exception e2)
710            {
711              debugException(e2);
712            }
713    
714            if (e instanceof LDAPException)
715            {
716              throw ((LDAPException) e);
717            }
718            else
719            {
720              throw new LDAPException(ResultCode.CONNECT_ERROR,
721                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
722            }
723          }
724        }
725    
726        c.setConnectionPoolName(connectionPoolName);
727        poolStatistics.incrementNumSuccessfulConnectionAttempts();
728        return c;
729      }
730    
731    
732    
733      /**
734       * {@inheritDoc}
735       */
736      @Override()
737      public void close()
738      {
739        closed = true;
740        healthCheckThread.stopRunning();
741    
742        while (true)
743        {
744          final LDAPConnection conn = availableConnections.poll();
745          if (conn == null)
746          {
747            return;
748          }
749          else
750          {
751            poolStatistics.incrementNumConnectionsClosedUnneeded();
752            conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
753            conn.terminate(null);
754          }
755        }
756      }
757    
758    
759    
760      /**
761       * {@inheritDoc}
762       */
763      @Override()
764      public boolean isClosed()
765      {
766        return closed;
767      }
768    
769    
770    
771      /**
772       * {@inheritDoc}
773       */
774      @Override()
775      public LDAPConnection getConnection()
776             throws LDAPException
777      {
778        if (closed)
779        {
780          poolStatistics.incrementNumFailedCheckouts();
781          throw new LDAPException(ResultCode.CONNECT_ERROR,
782                                  ERR_POOL_CLOSED.get());
783        }
784    
785        LDAPConnection conn = availableConnections.poll();
786        if (conn != null)
787        {
788          if (conn.isConnected())
789          {
790            try
791            {
792              healthCheck.ensureConnectionValidForCheckout(conn);
793              poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
794              return conn;
795            }
796            catch (LDAPException le)
797            {
798              debugException(le);
799            }
800          }
801    
802          handleDefunctConnection(conn);
803          for (int i=0; i < numConnections; i++)
804          {
805            conn = availableConnections.poll();
806            if (conn == null)
807            {
808              break;
809            }
810            else if (conn.isConnected())
811            {
812              try
813              {
814                healthCheck.ensureConnectionValidForCheckout(conn);
815                poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
816                return conn;
817              }
818              catch (LDAPException le)
819              {
820                debugException(le);
821                handleDefunctConnection(conn);
822              }
823            }
824            else
825            {
826              handleDefunctConnection(conn);
827            }
828          }
829        }
830    
831        if (failedReplaceCount.get() > 0)
832        {
833          final int newReplaceCount = failedReplaceCount.getAndDecrement();
834          if (newReplaceCount > 0)
835          {
836            try
837            {
838              conn = createConnection();
839              poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
840              return conn;
841            }
842            catch (LDAPException le)
843            {
844              debugException(le);
845              failedReplaceCount.incrementAndGet();
846              poolStatistics.incrementNumFailedCheckouts();
847              throw le;
848            }
849          }
850          else
851          {
852            failedReplaceCount.incrementAndGet();
853            poolStatistics.incrementNumFailedCheckouts();
854            throw new LDAPException(ResultCode.CONNECT_ERROR,
855                                    ERR_POOL_NO_CONNECTIONS.get());
856          }
857        }
858    
859        if (maxWaitTime > 0)
860        {
861          try
862          {
863            conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS);
864            if (conn != null)
865            {
866              try
867              {
868                healthCheck.ensureConnectionValidForCheckout(conn);
869                poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting();
870                return conn;
871              }
872              catch (LDAPException le)
873              {
874                debugException(le);
875                handleDefunctConnection(conn);
876              }
877            }
878          }
879          catch (InterruptedException ie)
880          {
881            debugException(ie);
882          }
883        }
884    
885        if (createIfNecessary)
886        {
887          try
888          {
889            conn = createConnection();
890            poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
891            return conn;
892          }
893          catch (LDAPException le)
894          {
895            debugException(le);
896            poolStatistics.incrementNumFailedCheckouts();
897            throw le;
898          }
899        }
900        else
901        {
902          poolStatistics.incrementNumFailedCheckouts();
903          throw new LDAPException(ResultCode.CONNECT_ERROR,
904                                  ERR_POOL_NO_CONNECTIONS.get());
905        }
906      }
907    
908    
909    
910      /**
911       * {@inheritDoc}
912       */
913      @Override()
914      public void releaseConnection(final LDAPConnection connection)
915      {
916        if (connection == null)
917        {
918          return;
919        }
920    
921        connection.setConnectionPoolName(connectionPoolName);
922        if (connectionIsExpired(connection))
923        {
924          try
925          {
926            final LDAPConnection newConnection = createConnection();
927            if (availableConnections.offer(newConnection))
928            {
929              connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
930                   null, null);
931              connection.terminate(null);
932              poolStatistics.incrementNumConnectionsClosedExpired();
933              lastExpiredDisconnectTime = System.currentTimeMillis();
934              return;
935            }
936            else
937            {
938              newConnection.setDisconnectInfo(
939                   DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
940              newConnection.terminate(null);
941              poolStatistics.incrementNumConnectionsClosedUnneeded();
942            }
943          }
944          catch (final LDAPException le)
945          {
946            debugException(le);
947          }
948        }
949    
950        try
951        {
952          healthCheck.ensureConnectionValidForRelease(connection);
953        }
954        catch (LDAPException le)
955        {
956          releaseDefunctConnection(connection);
957          return;
958        }
959    
960        if (availableConnections.offer(connection))
961        {
962          poolStatistics.incrementNumReleasedValid();
963        }
964        else
965        {
966          // This means that the connection pool is full, which can happen if the
967          // pool was empty when a request came in to retrieve a connection and
968          // createIfNecessary was true.  In this case, we'll just close the
969          // connection since we don't need it any more.
970          connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
971                                       null, null);
972          poolStatistics.incrementNumConnectionsClosedUnneeded();
973          connection.terminate(null);
974          return;
975        }
976    
977        if (closed)
978        {
979          close();
980        }
981      }
982    
983    
984    
985      /**
986       * {@inheritDoc}
987       */
988      @Override()
989      public void releaseDefunctConnection(final LDAPConnection connection)
990      {
991        if (connection == null)
992        {
993          return;
994        }
995    
996        connection.setConnectionPoolName(connectionPoolName);
997        poolStatistics.incrementNumConnectionsClosedDefunct();
998        handleDefunctConnection(connection);
999      }
1000    
1001    
1002    
1003      /**
1004       * Performs the real work of terminating a defunct connection and replacing it
1005       * with a new connection if possible.
1006       *
1007       * @param  connection  The defunct connection to be replaced.
1008       *
1009       * @return  The new connection created to take the place of the defunct
1010       *          connection, or {@code null} if no new connection was created.
1011       *          Note that if a connection is returned, it will have already been
1012       *          made available and the caller must not rely on it being unused for
1013       *          any other purpose.
1014       */
1015      private LDAPConnection handleDefunctConnection(
1016                                  final LDAPConnection connection)
1017      {
1018        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1019                                     null);
1020        connection.terminate(null);
1021    
1022        if (closed)
1023        {
1024          return null;
1025        }
1026    
1027        if (createIfNecessary && (availableConnections.remainingCapacity() <= 0))
1028        {
1029          return null;
1030        }
1031    
1032        try
1033        {
1034          final LDAPConnection conn = createConnection();
1035          if (! availableConnections.offer(conn))
1036          {
1037            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1038                                   null, null);
1039            conn.terminate(null);
1040            return null;
1041          }
1042    
1043          return conn;
1044        }
1045        catch (LDAPException le)
1046        {
1047          debugException(le);
1048          failedReplaceCount.incrementAndGet();
1049          return null;
1050        }
1051      }
1052    
1053    
1054    
1055      /**
1056       * {@inheritDoc}
1057       */
1058      @Override()
1059      public LDAPConnection replaceDefunctConnection(
1060                                 final LDAPConnection connection)
1061             throws LDAPException
1062      {
1063        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1064                                     null);
1065        connection.terminate(null);
1066    
1067        if (closed)
1068        {
1069          throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
1070        }
1071    
1072        return createConnection();
1073      }
1074    
1075    
1076    
1077      /**
1078       * {@inheritDoc}
1079       */
1080      @Override()
1081      public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
1082      {
1083        return retryOperationTypes.get();
1084      }
1085    
1086    
1087    
1088      /**
1089       * {@inheritDoc}
1090       */
1091      @Override()
1092      public void setRetryFailedOperationsDueToInvalidConnections(
1093                       final Set<OperationType> operationTypes)
1094      {
1095        if ((operationTypes == null) || operationTypes.isEmpty())
1096        {
1097          retryOperationTypes.set(
1098               Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
1099        }
1100        else
1101        {
1102          final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
1103          s.addAll(operationTypes);
1104          retryOperationTypes.set(Collections.unmodifiableSet(s));
1105        }
1106      }
1107    
1108    
1109    
1110      /**
1111       * Indicates whether the provided connection should be considered expired.
1112       *
1113       * @param  connection  The connection for which to make the determination.
1114       *
1115       * @return  {@code true} if the provided connection should be considered
1116       *          expired, or {@code false} if not.
1117       */
1118      private boolean connectionIsExpired(final LDAPConnection connection)
1119      {
1120        // If connection expiration is not enabled, then there is nothing to do.
1121        if (maxConnectionAge <= 0L)
1122        {
1123          return false;
1124        }
1125    
1126        // If there is a minimum disconnect interval, then make sure that we have
1127        // not closed another expired connection too recently.
1128        final long currentTime = System.currentTimeMillis();
1129        if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
1130        {
1131          return false;
1132        }
1133    
1134        // Get the age of the connection and see if it is expired.
1135        final long connectionAge = currentTime - connection.getConnectTime();
1136        return (connectionAge > maxConnectionAge);
1137      }
1138    
1139    
1140    
1141      /**
1142       * {@inheritDoc}
1143       */
1144      @Override()
1145      public String getConnectionPoolName()
1146      {
1147        return connectionPoolName;
1148      }
1149    
1150    
1151    
1152      /**
1153       * {@inheritDoc}
1154       */
1155      @Override()
1156      public void setConnectionPoolName(final String connectionPoolName)
1157      {
1158        this.connectionPoolName = connectionPoolName;
1159        for (final LDAPConnection c : availableConnections)
1160        {
1161          c.setConnectionPoolName(connectionPoolName);
1162        }
1163      }
1164    
1165    
1166    
1167      /**
1168       * Indicates whether the connection pool should create a new connection if one
1169       * is requested when there are none available.
1170       *
1171       * @return  {@code true} if a new connection should be created if none are
1172       *          available when a request is received, or {@code false} if an
1173       *          exception should be thrown to indicate that no connection is
1174       *          available.
1175       */
1176      public boolean getCreateIfNecessary()
1177      {
1178        return createIfNecessary;
1179      }
1180    
1181    
1182    
1183      /**
1184       * Specifies whether the connection pool should create a new connection if one
1185       * is requested when there are none available.
1186       *
1187       * @param  createIfNecessary  Specifies whether the connection pool should
1188       *                            create a new connection if one is requested when
1189       *                            there are none available.
1190       */
1191      public void setCreateIfNecessary(final boolean createIfNecessary)
1192      {
1193        this.createIfNecessary = createIfNecessary;
1194      }
1195    
1196    
1197    
1198      /**
1199       * Retrieves the maximum length of time in milliseconds to wait for a
1200       * connection to become available when trying to obtain a connection from the
1201       * pool.
1202       *
1203       * @return  The maximum length of time in milliseconds to wait for a
1204       *          connection to become available when trying to obtain a connection
1205       *          from the pool, or zero to indicate that the pool should not block
1206       *          at all if no connections are available and that it should either
1207       *          create a new connection or throw an exception.
1208       */
1209      public long getMaxWaitTimeMillis()
1210      {
1211        return maxWaitTime;
1212      }
1213    
1214    
1215    
1216      /**
1217       * Specifies the maximum length of time in milliseconds to wait for a
1218       * connection to become available when trying to obtain a connection from the
1219       * pool.
1220       *
1221       * @param  maxWaitTime  The maximum length of time in milliseconds to wait for
1222       *                      a connection to become available when trying to obtain
1223       *                      a connection from the pool.  A value of zero should be
1224       *                      used to indicate that the pool should not block at all
1225       *                      if no connections are available and that it should
1226       *                      either create a new connection or throw an exception.
1227       */
1228      public void setMaxWaitTimeMillis(final long maxWaitTime)
1229      {
1230        if (maxWaitTime > 0L)
1231        {
1232          this.maxWaitTime = maxWaitTime;
1233        }
1234        else
1235        {
1236          this.maxWaitTime = 0L;
1237        }
1238      }
1239    
1240    
1241    
1242      /**
1243       * Retrieves the maximum length of time in milliseconds that a connection in
1244       * this pool may be established before it is closed and replaced with another
1245       * connection.
1246       *
1247       * @return  The maximum length of time in milliseconds that a connection in
1248       *          this pool may be established before it is closed and replaced with
1249       *          another connection, or {@code 0L} if no maximum age should be
1250       *          enforced.
1251       */
1252      public long getMaxConnectionAgeMillis()
1253      {
1254        return maxConnectionAge;
1255      }
1256    
1257    
1258    
1259      /**
1260       * Specifies the maximum length of time in milliseconds that a connection in
1261       * this pool may be established before it should be closed and replaced with
1262       * another connection.
1263       *
1264       * @param  maxConnectionAge  The maximum length of time in milliseconds that a
1265       *                           connection in this pool may be established before
1266       *                           it should be closed and replaced with another
1267       *                           connection.  A value of zero indicates that no
1268       *                           maximum age should be enforced.
1269       */
1270      public void setMaxConnectionAgeMillis(final long maxConnectionAge)
1271      {
1272        if (maxConnectionAge > 0L)
1273        {
1274          this.maxConnectionAge = maxConnectionAge;
1275        }
1276        else
1277        {
1278          this.maxConnectionAge = 0L;
1279        }
1280      }
1281    
1282    
1283    
1284      /**
1285       * Retrieves the minimum length of time in milliseconds that should pass
1286       * between connections closed because they have been established for longer
1287       * than the maximum connection age.
1288       *
1289       * @return  The minimum length of time in milliseconds that should pass
1290       *          between connections closed because they have been established for
1291       *          longer than the maximum connection age, or {@code 0L} if expired
1292       *          connections may be closed as quickly as they are identified.
1293       */
1294      public long getMinDisconnectIntervalMillis()
1295      {
1296        return minDisconnectInterval;
1297      }
1298    
1299    
1300    
1301      /**
1302       * Specifies the minimum length of time in milliseconds that should pass
1303       * between connections closed because they have been established for longer
1304       * than the maximum connection age.
1305       *
1306       * @param  minDisconnectInterval  The minimum length of time in milliseconds
1307       *                                that should pass between connections closed
1308       *                                because they have been established for
1309       *                                longer than the maximum connection age.  A
1310       *                                value less than or equal to zero indicates
1311       *                                that no minimum time should be enforced.
1312       */
1313      public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
1314      {
1315        if (minDisconnectInterval > 0)
1316        {
1317          this.minDisconnectInterval = minDisconnectInterval;
1318        }
1319        else
1320        {
1321          this.minDisconnectInterval = 0L;
1322        }
1323      }
1324    
1325    
1326    
1327      /**
1328       * {@inheritDoc}
1329       */
1330      @Override()
1331      public LDAPConnectionPoolHealthCheck getHealthCheck()
1332      {
1333        return healthCheck;
1334      }
1335    
1336    
1337    
1338      /**
1339       * Sets the health check implementation for this connection pool.
1340       *
1341       * @param  healthCheck  The health check implementation for this connection
1342       *                      pool.  It must not be {@code null}.
1343       */
1344      public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
1345      {
1346        ensureNotNull(healthCheck);
1347        this.healthCheck = healthCheck;
1348      }
1349    
1350    
1351    
1352      /**
1353       * {@inheritDoc}
1354       */
1355      @Override()
1356      public long getHealthCheckIntervalMillis()
1357      {
1358        return healthCheckInterval;
1359      }
1360    
1361    
1362    
1363      /**
1364       * {@inheritDoc}
1365       */
1366      @Override()
1367      public void setHealthCheckIntervalMillis(final long healthCheckInterval)
1368      {
1369        ensureTrue(healthCheckInterval > 0L,
1370             "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
1371        this.healthCheckInterval = healthCheckInterval;
1372        healthCheckThread.wakeUp();
1373      }
1374    
1375    
1376    
1377      /**
1378       * Indicates whether health check processing for connections operating in
1379       * synchronous mode should include attempting to perform a read from each
1380       * connection with a very short timeout.  This can help detect unsolicited
1381       * responses and unexpected connection closures in a more timely manner.  This
1382       * will be ignored for connections not operating in synchronous mode.
1383       *
1384       * @return  {@code true} if health check processing for connections operating
1385       *          in synchronous mode should include a read attempt with a very
1386       *          short timeout, or {@code false} if not.
1387       */
1388      public boolean trySynchronousReadDuringHealthCheck()
1389      {
1390        return trySynchronousReadDuringHealthCheck;
1391      }
1392    
1393    
1394    
1395      /**
1396       * Specifies whether health check processing for connections operating in
1397       * synchronous mode should include attempting to perform a read from each
1398       * connection with a very short timeout.
1399       *
1400       * @param  trySynchronousReadDuringHealthCheck  Indicates whether health check
1401       *                                              processing for connections
1402       *                                              operating in synchronous mode
1403       *                                              should include attempting to
1404       *                                              perform a read from each
1405       *                                              connection with a very short
1406       *                                              timeout.
1407       */
1408      public void setTrySynchronousReadDuringHealthCheck(
1409                       final boolean trySynchronousReadDuringHealthCheck)
1410      {
1411        this.trySynchronousReadDuringHealthCheck =
1412             trySynchronousReadDuringHealthCheck;
1413      }
1414    
1415    
1416    
1417      /**
1418       * {@inheritDoc}
1419       */
1420      @Override()
1421      protected void doHealthCheck()
1422      {
1423        // Create a set used to hold connections that we've already examined.  If we
1424        // encounter the same connection twice, then we know that we don't need to
1425        // do any more work.
1426        final HashSet<LDAPConnection> examinedConnections =
1427             new HashSet<LDAPConnection>(numConnections);
1428    
1429        for (int i=0; i < numConnections; i++)
1430        {
1431          LDAPConnection conn = availableConnections.poll();
1432          if (conn == null)
1433          {
1434            break;
1435          }
1436          else if (examinedConnections.contains(conn))
1437          {
1438            if (! availableConnections.offer(conn))
1439            {
1440              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1441                                     null, null);
1442              poolStatistics.incrementNumConnectionsClosedUnneeded();
1443              conn.terminate(null);
1444            }
1445            break;
1446          }
1447    
1448          if (! conn.isConnected())
1449          {
1450            conn = handleDefunctConnection(conn);
1451            if (conn != null)
1452            {
1453              examinedConnections.add(conn);
1454            }
1455          }
1456          else
1457          {
1458            if (connectionIsExpired(conn))
1459            {
1460              try
1461              {
1462                final LDAPConnection newConnection = createConnection();
1463                if (availableConnections.offer(newConnection))
1464                {
1465                  examinedConnections.add(newConnection);
1466                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
1467                       null, null);
1468                  conn.terminate(null);
1469                  poolStatistics.incrementNumConnectionsClosedExpired();
1470                  lastExpiredDisconnectTime = System.currentTimeMillis();
1471                  continue;
1472                }
1473                else
1474                {
1475                  newConnection.setDisconnectInfo(
1476                       DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
1477                  newConnection.terminate(null);
1478                  poolStatistics.incrementNumConnectionsClosedUnneeded();
1479                }
1480              }
1481              catch (final LDAPException le)
1482              {
1483                debugException(le);
1484              }
1485            }
1486    
1487    
1488            // If the connection is operating in synchronous mode, then try to read
1489            // a message on it using an extremely short timeout.  This can help
1490            // detect a connection closure or unsolicited notification in a more
1491            // timely manner than if we had to wait for the client code to try to
1492            // use the connection.
1493            if (trySynchronousReadDuringHealthCheck && conn.synchronousMode())
1494            {
1495              int previousTimeout = Integer.MIN_VALUE;
1496              final Socket s = conn.getConnectionInternals().getSocket();
1497              try
1498              {
1499                previousTimeout = s.getSoTimeout();
1500                s.setSoTimeout(1);
1501    
1502                final LDAPResponse response = conn.readResponse(0);
1503                if (response instanceof ConnectionClosedResponse)
1504                {
1505                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
1506                       ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
1507                  poolStatistics.incrementNumConnectionsClosedDefunct();
1508                  conn = handleDefunctConnection(conn);
1509                  if (conn != null)
1510                  {
1511                    examinedConnections.add(conn);
1512                  }
1513                  continue;
1514                }
1515                else if (response instanceof ExtendedResult)
1516                {
1517                  // This means we got an unsolicited response.  It could be a
1518                  // notice of disconnection, or it could be something else, but in
1519                  // any case we'll send it to the connection's unsolicited
1520                  // notification handler (if one is defined).
1521                  final UnsolicitedNotificationHandler h = conn.
1522                       getConnectionOptions().getUnsolicitedNotificationHandler();
1523                  if (h != null)
1524                  {
1525                    h.handleUnsolicitedNotification(conn,
1526                         (ExtendedResult) response);
1527                  }
1528                }
1529                else if (response instanceof LDAPResult)
1530                {
1531                  final LDAPResult r = (LDAPResult) response;
1532                  if (r.getResultCode() == ResultCode.SERVER_DOWN)
1533                  {
1534                    conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
1535                         ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
1536                    poolStatistics.incrementNumConnectionsClosedDefunct();
1537                    conn = handleDefunctConnection(conn);
1538                    if (conn != null)
1539                    {
1540                      examinedConnections.add(conn);
1541                    }
1542                    continue;
1543                  }
1544                }
1545              }
1546              catch (final LDAPException le)
1547              {
1548                if (le.getResultCode() == ResultCode.TIMEOUT)
1549                {
1550                  debugException(Level.FINEST, le);
1551                }
1552                else
1553                {
1554                  debugException(le);
1555                  conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
1556                       ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
1557                            getExceptionMessage(le)), le);
1558                  poolStatistics.incrementNumConnectionsClosedDefunct();
1559                  conn = handleDefunctConnection(conn);
1560                  if (conn != null)
1561                  {
1562                    examinedConnections.add(conn);
1563                  }
1564                  continue;
1565                }
1566              }
1567              catch (final Exception e)
1568              {
1569                debugException(e);
1570                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
1571                     ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(getExceptionMessage(e)),
1572                     e);
1573                poolStatistics.incrementNumConnectionsClosedDefunct();
1574                conn = handleDefunctConnection(conn);
1575                if (conn != null)
1576                {
1577                  examinedConnections.add(conn);
1578                }
1579                continue;
1580              }
1581              finally
1582              {
1583                if (previousTimeout != Integer.MIN_VALUE)
1584                {
1585                  try
1586                  {
1587                    s.setSoTimeout(previousTimeout);
1588                  }
1589                  catch (final Exception e)
1590                  {
1591                    debugException(e);
1592                    conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
1593                         null, e);
1594                    poolStatistics.incrementNumConnectionsClosedDefunct();
1595                    conn = handleDefunctConnection(conn);
1596                    if (conn != null)
1597                    {
1598                      examinedConnections.add(conn);
1599                    }
1600                    continue;
1601                  }
1602                }
1603              }
1604            }
1605    
1606            try
1607            {
1608              healthCheck.ensureConnectionValidForContinuedUse(conn);
1609              if (availableConnections.offer(conn))
1610              {
1611                examinedConnections.add(conn);
1612              }
1613              else
1614              {
1615                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
1616                                       null, null);
1617                poolStatistics.incrementNumConnectionsClosedUnneeded();
1618                conn.terminate(null);
1619              }
1620            }
1621            catch (Exception e)
1622            {
1623              debugException(e);
1624              conn = handleDefunctConnection(conn);
1625              if (conn != null)
1626              {
1627                examinedConnections.add(conn);
1628              }
1629            }
1630          }
1631        }
1632      }
1633    
1634    
1635    
1636      /**
1637       * {@inheritDoc}
1638       */
1639      @Override()
1640      public int getCurrentAvailableConnections()
1641      {
1642        return availableConnections.size();
1643      }
1644    
1645    
1646    
1647      /**
1648       * {@inheritDoc}
1649       */
1650      @Override()
1651      public int getMaximumAvailableConnections()
1652      {
1653        return numConnections;
1654      }
1655    
1656    
1657    
1658      /**
1659       * {@inheritDoc}
1660       */
1661      @Override()
1662      public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
1663      {
1664        return poolStatistics;
1665      }
1666    
1667    
1668    
1669      /**
1670       * Closes this connection pool in the event that it becomes unreferenced.
1671       *
1672       * @throws  Throwable  If an unexpected problem occurs.
1673       */
1674      @Override()
1675      protected void finalize()
1676                throws Throwable
1677      {
1678        super.finalize();
1679    
1680        close();
1681      }
1682    
1683    
1684    
1685      /**
1686       * {@inheritDoc}
1687       */
1688      @Override()
1689      public void toString(final StringBuilder buffer)
1690      {
1691        buffer.append("LDAPConnectionPool(");
1692    
1693        final String name = connectionPoolName;
1694        if (name != null)
1695        {
1696          buffer.append("name='");
1697          buffer.append(name);
1698          buffer.append("', ");
1699        }
1700    
1701        buffer.append("serverSet=");
1702        serverSet.toString(buffer);
1703        buffer.append(", maxConnections=");
1704        buffer.append(numConnections);
1705        buffer.append(')');
1706      }
1707    }