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