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