001/*
002 * Copyright 2007-2023 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2023 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-2023 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      poolStatistics.incrementNumConnectionsClosedDefunct();
1816      Debug.debugConnectionPool(Level.WARNING, this, conn,
1817           "Closing a defunct connection encountered during checkout",
1818           connException);
1819      handleDefunctConnection(conn);
1820      for (int i=0; i < numConnections; i++)
1821      {
1822        conn = availableConnections.poll();
1823        if (conn == null)
1824        {
1825          break;
1826        }
1827        else if (conn.isConnected())
1828        {
1829          try
1830          {
1831            healthCheck.ensureConnectionValidForCheckout(conn);
1832            poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1833            Debug.debugConnectionPool(Level.INFO, this, conn,
1834                 "Checked out an immediately available pooled connection",
1835                 null);
1836            return conn;
1837          }
1838          catch (final LDAPException le)
1839          {
1840            Debug.debugException(le);
1841            poolStatistics.incrementNumConnectionsClosedDefunct();
1842            Debug.debugConnectionPool(Level.WARNING, this, conn,
1843                 "Closing a defunct connection encountered during checkout",
1844                 le);
1845            handleDefunctConnection(conn);
1846          }
1847        }
1848        else
1849        {
1850          poolStatistics.incrementNumConnectionsClosedDefunct();
1851          Debug.debugConnectionPool(Level.WARNING, this, conn,
1852               "Closing a defunct connection encountered during checkout",
1853               null);
1854          handleDefunctConnection(conn);
1855        }
1856      }
1857    }
1858
1859    if (failedReplaceCount.get() > 0)
1860    {
1861      final int newReplaceCount = failedReplaceCount.getAndDecrement();
1862      if (newReplaceCount > 0)
1863      {
1864        try
1865        {
1866          conn = createConnection();
1867          poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1868          Debug.debugConnectionPool(Level.INFO, this, conn,
1869               "Checked out a newly created connection", null);
1870          return conn;
1871        }
1872        catch (final LDAPException le)
1873        {
1874          Debug.debugException(le);
1875          failedReplaceCount.incrementAndGet();
1876          poolStatistics.incrementNumFailedCheckouts();
1877          Debug.debugConnectionPool(Level.SEVERE, this, conn,
1878               "Unable to create a new connection for checkout", le);
1879          throw le;
1880        }
1881      }
1882      else
1883      {
1884        failedReplaceCount.incrementAndGet();
1885      }
1886    }
1887
1888    if (maxWaitTime > 0)
1889    {
1890      try
1891      {
1892        final long startWaitTime = System.currentTimeMillis();
1893        conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS);
1894        final long elapsedWaitTime = System.currentTimeMillis() - startWaitTime;
1895        if (conn != null)
1896        {
1897          try
1898          {
1899            healthCheck.ensureConnectionValidForCheckout(conn);
1900            poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting();
1901            Debug.debugConnectionPool(Level.INFO, this, conn,
1902                 "Checked out an existing connection after waiting " +
1903                      elapsedWaitTime + "ms for it to become available",
1904                 null);
1905            return conn;
1906          }
1907          catch (final LDAPException le)
1908          {
1909            Debug.debugException(le);
1910            poolStatistics.incrementNumConnectionsClosedDefunct();
1911            Debug.debugConnectionPool(Level.WARNING, this, conn,
1912                 "Got a connection for checkout after waiting " +
1913                      elapsedWaitTime + "ms for it to become available, but " +
1914                      "the connection failed the checkout health check",
1915                 le);
1916            handleDefunctConnection(conn);
1917          }
1918        }
1919      }
1920      catch (final InterruptedException ie)
1921      {
1922        Debug.debugException(ie);
1923        Thread.currentThread().interrupt();
1924        throw new LDAPException(ResultCode.LOCAL_ERROR,
1925             ERR_POOL_CHECKOUT_INTERRUPTED.get(), ie);
1926      }
1927    }
1928
1929    if (createIfNecessary)
1930    {
1931      try
1932      {
1933        conn = createConnection();
1934        poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1935        Debug.debugConnectionPool(Level.INFO, this, conn,
1936             "Checked out a newly created connection", null);
1937        return conn;
1938      }
1939      catch (final LDAPException le)
1940      {
1941        Debug.debugException(le);
1942        poolStatistics.incrementNumFailedCheckouts();
1943        Debug.debugConnectionPool(Level.SEVERE, this, null,
1944             "Unable to create a new connection for checkout", le);
1945        throw le;
1946      }
1947    }
1948    else
1949    {
1950      poolStatistics.incrementNumFailedCheckouts();
1951      Debug.debugConnectionPool(Level.SEVERE, this, null,
1952           "Unable to check out a connection because none are available",
1953           null);
1954      throw new LDAPException(ResultCode.CONNECT_ERROR,
1955                              ERR_POOL_NO_CONNECTIONS.get());
1956    }
1957  }
1958
1959
1960
1961  /**
1962   * Attempts to retrieve a connection from the pool that is established to the
1963   * specified server.  Note that this method will only attempt to return an
1964   * existing connection that is currently available, and will not create a
1965   * connection or wait for any checked-out connections to be returned.
1966   *
1967   * @param  host  The address of the server to which the desired connection
1968   *               should be established.  This must not be {@code null}, and
1969   *               this must exactly match the address provided for the initial
1970   *               connection or the {@code ServerSet} used to create the pool.
1971   * @param  port  The port of the server to which the desired connection should
1972   *               be established.
1973   *
1974   * @return  A connection that is established to the specified server, or
1975   *          {@code null} if there are no available connections established to
1976   *          the specified server.
1977   */
1978  @Nullable()
1979  public LDAPConnection getConnection(@NotNull final String host,
1980                                               final int port)
1981  {
1982    if (closed)
1983    {
1984      poolStatistics.incrementNumFailedCheckouts();
1985      Debug.debugConnectionPool(Level.WARNING, this, null,
1986           "Failed to get a connection to a closed connection pool", null);
1987      return null;
1988    }
1989
1990    final HashSet<LDAPConnection> examinedConnections =
1991         new HashSet<>(StaticUtils.computeMapCapacity(numConnections));
1992    while (true)
1993    {
1994      final LDAPConnection conn = availableConnections.poll();
1995      if (conn == null)
1996      {
1997        poolStatistics.incrementNumFailedCheckouts();
1998        Debug.debugConnectionPool(Level.SEVERE, this, null,
1999             "Failed to get an existing connection to " + host + ':' + port +
2000                  " because no connections are immediately available",
2001             null);
2002        return null;
2003      }
2004
2005      if (examinedConnections.contains(conn))
2006      {
2007        if (! availableConnections.offer(conn))
2008        {
2009          discardConnection(conn);
2010        }
2011
2012        poolStatistics.incrementNumFailedCheckouts();
2013        Debug.debugConnectionPool(Level.WARNING, this, null,
2014             "Failed to get an existing connection to " + host + ':' + port +
2015                  " because none of the available connections are " +
2016                  "established to that server",
2017             null);
2018        return null;
2019      }
2020
2021      if (conn.getConnectedAddress().equals(host) &&
2022          (port == conn.getConnectedPort()))
2023      {
2024        try
2025        {
2026          healthCheck.ensureConnectionValidForCheckout(conn);
2027          poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
2028          Debug.debugConnectionPool(Level.INFO, this, conn,
2029               "Successfully checked out an existing connection to requested " +
2030                    "server " + host + ':' + port,
2031               null);
2032          return conn;
2033        }
2034        catch (final LDAPException le)
2035        {
2036          Debug.debugException(le);
2037          poolStatistics.incrementNumConnectionsClosedDefunct();
2038          Debug.debugConnectionPool(Level.WARNING, this, conn,
2039               "Closing an existing connection to requested server " + host +
2040                    ':' + port + " because it failed the checkout health " +
2041                    "check",
2042               le);
2043          handleDefunctConnection(conn);
2044          continue;
2045        }
2046      }
2047
2048      if (availableConnections.offer(conn))
2049      {
2050        examinedConnections.add(conn);
2051      }
2052      else
2053      {
2054        discardConnection(conn);
2055      }
2056    }
2057  }
2058
2059
2060
2061  /**
2062   * {@inheritDoc}
2063   */
2064  @Override()
2065  public void releaseConnection(@NotNull final LDAPConnection connection)
2066  {
2067    if (connection == null)
2068    {
2069      return;
2070    }
2071
2072    connection.setConnectionPoolName(connectionPoolName);
2073    if (checkConnectionAgeOnRelease && connectionIsExpired(connection))
2074    {
2075      try
2076      {
2077        final LDAPConnection newConnection = createConnection();
2078        if (availableConnections.offer(newConnection))
2079        {
2080          connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
2081               null, null);
2082          connection.terminate(null);
2083          poolStatistics.incrementNumConnectionsClosedExpired();
2084          Debug.debugConnectionPool(Level.WARNING, this, connection,
2085               "Closing a released connection because it is expired", null);
2086          lastExpiredDisconnectTime = System.currentTimeMillis();
2087        }
2088        else
2089        {
2090          newConnection.setDisconnectInfo(
2091               DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
2092          newConnection.terminate(null);
2093          poolStatistics.incrementNumConnectionsClosedUnneeded();
2094          Debug.debugConnectionPool(Level.WARNING, this, connection,
2095               "Closing a released connection because the pool is already full",
2096               null);
2097        }
2098      }
2099      catch (final LDAPException le)
2100      {
2101        Debug.debugException(le);
2102      }
2103      return;
2104    }
2105
2106    try
2107    {
2108      healthCheck.ensureConnectionValidForRelease(connection);
2109    }
2110    catch (final LDAPException le)
2111    {
2112      releaseDefunctConnection(connection);
2113      return;
2114    }
2115
2116    if (availableConnections.offer(connection))
2117    {
2118      poolStatistics.incrementNumReleasedValid();
2119      Debug.debugConnectionPool(Level.INFO, this, connection,
2120           "Released a connection back to the pool", null);
2121    }
2122    else
2123    {
2124      // This means that the connection pool is full, which can happen if the
2125      // pool was empty when a request came in to retrieve a connection and
2126      // createIfNecessary was true.  In this case, we'll just close the
2127      // connection since we don't need it any more.
2128      connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2129                                   null, null);
2130      poolStatistics.incrementNumConnectionsClosedUnneeded();
2131      Debug.debugConnectionPool(Level.WARNING, this, connection,
2132           "Closing a released connection because the pool is already full",
2133           null);
2134      connection.terminate(null);
2135      return;
2136    }
2137
2138    if (closed)
2139    {
2140      close();
2141    }
2142  }
2143
2144
2145
2146  /**
2147   * Indicates that the provided connection should be removed from the pool,
2148   * and that no new connection should be created to take its place.  This may
2149   * be used to shrink the pool if such functionality is desired.
2150   *
2151   * @param  connection  The connection to be discarded.
2152   */
2153  public void discardConnection(@NotNull final LDAPConnection connection)
2154  {
2155    if (connection == null)
2156    {
2157      return;
2158    }
2159
2160    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2161         null, null);
2162    connection.terminate(null);
2163    poolStatistics.incrementNumConnectionsClosedUnneeded();
2164    Debug.debugConnectionPool(Level.INFO, this, connection,
2165         "Discareded a connection that is no longer needed", null);
2166
2167    if (availableConnections.remainingCapacity() > 0)
2168    {
2169      final int newReplaceCount = failedReplaceCount.incrementAndGet();
2170      if (newReplaceCount > numConnections)
2171      {
2172        failedReplaceCount.set(numConnections);
2173      }
2174    }
2175  }
2176
2177
2178
2179  /**
2180   * Performs a bind on the provided connection before releasing it back to the
2181   * pool, so that it will be authenticated as the same user as
2182   * newly-established connections.  If newly-established connections are
2183   * unauthenticated, then this method will perform an anonymous simple bind to
2184   * ensure that the resulting connection is unauthenticated.
2185   *
2186   * Releases the provided connection back to this pool.
2187   *
2188   * @param  connection  The connection to be released back to the pool after
2189   *                     being re-authenticated.
2190   */
2191  public void releaseAndReAuthenticateConnection(
2192                   @NotNull final LDAPConnection connection)
2193  {
2194    if (connection == null)
2195    {
2196      return;
2197    }
2198
2199    try
2200    {
2201      BindResult bindResult;
2202      try
2203      {
2204        if (bindRequest == null)
2205        {
2206          bindResult = connection.bind("", "");
2207        }
2208        else
2209        {
2210          bindResult = connection.bind(bindRequest.duplicate());
2211        }
2212      }
2213      catch (final LDAPBindException lbe)
2214      {
2215        Debug.debugException(lbe);
2216        bindResult = lbe.getBindResult();
2217      }
2218
2219      try
2220      {
2221        healthCheck.ensureConnectionValidAfterAuthentication(connection,
2222             bindResult);
2223        if (bindResult.getResultCode() != ResultCode.SUCCESS)
2224        {
2225          throw new LDAPBindException(bindResult);
2226        }
2227      }
2228      catch (final LDAPException le)
2229      {
2230        Debug.debugException(le);
2231
2232        try
2233        {
2234          connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
2235          connection.setClosed();
2236          releaseDefunctConnection(connection);
2237        }
2238        catch (final Exception e)
2239        {
2240          Debug.debugException(e);
2241        }
2242
2243        throw le;
2244      }
2245
2246      releaseConnection(connection);
2247    }
2248    catch (final Exception e)
2249    {
2250      Debug.debugException(e);
2251      releaseDefunctConnection(connection);
2252    }
2253  }
2254
2255
2256
2257  /**
2258   * {@inheritDoc}
2259   */
2260  @Override()
2261  public void releaseDefunctConnection(@NotNull final LDAPConnection connection)
2262  {
2263    if (connection == null)
2264    {
2265      return;
2266    }
2267
2268    connection.setConnectionPoolName(connectionPoolName);
2269    poolStatistics.incrementNumConnectionsClosedDefunct();
2270    Debug.debugConnectionPool(Level.WARNING, this, connection,
2271         "Releasing a defunct connection", null);
2272    handleDefunctConnection(connection);
2273  }
2274
2275
2276
2277  /**
2278   * Performs the real work of terminating a defunct connection and replacing it
2279   * with a new connection if possible.
2280   *
2281   * @param  connection  The defunct connection to be replaced.
2282   *
2283   * @return  The new connection created to take the place of the defunct
2284   *          connection, or {@code null} if no new connection was created.
2285   *          Note that if a connection is returned, it will have already been
2286   *          made available and the caller must not rely on it being unused for
2287   *          any other purpose.
2288   */
2289  @NotNull()
2290  private LDAPConnection handleDefunctConnection(
2291                              @NotNull final LDAPConnection connection)
2292  {
2293    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
2294                                 null);
2295    connection.setClosed();
2296
2297    if (closed)
2298    {
2299      return null;
2300    }
2301
2302    if (createIfNecessary && (availableConnections.remainingCapacity() <= 0))
2303    {
2304      return null;
2305    }
2306
2307    try
2308    {
2309      final LDAPConnection conn = createConnection();
2310      if (maxDefunctReplacementConnectionAge != null)
2311      {
2312        // Only set the maximum age if there isn't one already set for the
2313        // connection (i.e., because it was defined by the server set).
2314        if (conn.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE) == null)
2315        {
2316          conn.setAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE,
2317               maxDefunctReplacementConnectionAge);
2318        }
2319      }
2320
2321      if (! availableConnections.offer(conn))
2322      {
2323        conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2324                               null, null);
2325        conn.terminate(null);
2326        return null;
2327      }
2328
2329      return conn;
2330    }
2331    catch (final LDAPException le)
2332    {
2333      Debug.debugException(le);
2334      final int newReplaceCount = failedReplaceCount.incrementAndGet();
2335      if (newReplaceCount > numConnections)
2336      {
2337        failedReplaceCount.set(numConnections);
2338      }
2339      return null;
2340    }
2341  }
2342
2343
2344
2345  /**
2346   * {@inheritDoc}
2347   */
2348  @Override()
2349  @NotNull()
2350  public LDAPConnection replaceDefunctConnection(
2351                             @NotNull final LDAPConnection connection)
2352         throws LDAPException
2353  {
2354    poolStatistics.incrementNumConnectionsClosedDefunct();
2355    Debug.debugConnectionPool(Level.WARNING, this, connection,
2356         "Releasing a defunct connection that is to be replaced", null);
2357    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
2358                                 null);
2359    connection.setClosed();
2360
2361    if (closed)
2362    {
2363      throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
2364    }
2365
2366    try
2367    {
2368      return createConnection();
2369    }
2370    catch (final LDAPException le)
2371    {
2372      Debug.debugException(le);
2373      failedReplaceCount.incrementAndGet();
2374      throw le;
2375    }
2376  }
2377
2378
2379
2380  /**
2381   * {@inheritDoc}
2382   */
2383  @Override()
2384  @NotNull()
2385  public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
2386  {
2387    return retryOperationTypes.get();
2388  }
2389
2390
2391
2392  /**
2393   * {@inheritDoc}
2394   */
2395  @Override()
2396  public void setRetryFailedOperationsDueToInvalidConnections(
2397                   @Nullable final Set<OperationType> operationTypes)
2398  {
2399    if ((operationTypes == null) || operationTypes.isEmpty())
2400    {
2401      retryOperationTypes.set(
2402           Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
2403    }
2404    else
2405    {
2406      final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
2407      s.addAll(operationTypes);
2408      retryOperationTypes.set(Collections.unmodifiableSet(s));
2409    }
2410  }
2411
2412
2413
2414  /**
2415   * Indicates whether the provided connection should be considered expired.
2416   *
2417   * @param  connection  The connection for which to make the determination.
2418   *
2419   * @return  {@code true} if the provided connection should be considered
2420   *          expired, or {@code false} if not.
2421   */
2422  private boolean connectionIsExpired(@NotNull final LDAPConnection connection)
2423  {
2424    // There may be a custom maximum connection age for the connection.  If that
2425    // is the case, then use that custom max age rather than the pool-default
2426    // max age.
2427    final long maxAge;
2428    final Object maxAgeObj =
2429         connection.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE);
2430    if ((maxAgeObj != null) && (maxAgeObj instanceof Long))
2431    {
2432      maxAge = (Long) maxAgeObj;
2433    }
2434    else
2435    {
2436      maxAge = maxConnectionAge;
2437    }
2438
2439    // If connection expiration is not enabled, then there is nothing to do.
2440    if (maxAge <= 0L)
2441    {
2442      return false;
2443    }
2444
2445    // If there is a minimum disconnect interval, then make sure that we have
2446    // not closed another expired connection too recently.
2447    final long currentTime = System.currentTimeMillis();
2448    if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
2449    {
2450      return false;
2451    }
2452
2453    // Get the age of the connection and see if it is expired.
2454    final long connectionAge = currentTime - connection.getConnectTime();
2455    return (connectionAge > maxAge);
2456  }
2457
2458
2459
2460  /**
2461   * Specifies the bind request that will be used to authenticate subsequent new
2462   * connections that are established by this connection pool.  The
2463   * authentication state for existing connections will not be altered unless
2464   * one of the {@code bindAndRevertAuthentication} or
2465   * {@code releaseAndReAuthenticateConnection} methods are invoked on those
2466   * connections.
2467   *
2468   * @param  bindRequest  The bind request that will be used to authenticate new
2469   *                      connections that are established by this pool, or
2470   *                      that will be applied to existing connections via the
2471   *                      {@code bindAndRevertAuthentication} or
2472   *                      {@code releaseAndReAuthenticateConnection} method.  It
2473   *                      may be {@code null} if new connections should be
2474   *                      unauthenticated.
2475   */
2476  public void setBindRequest(@Nullable final BindRequest bindRequest)
2477  {
2478    this.bindRequest = bindRequest;
2479  }
2480
2481
2482
2483  /**
2484   * Retrieves the server set that should be used to establish new connections
2485   * for use in this connection pool.
2486   *
2487   * @return  The server set that should be used to establish new connections
2488   *          for use in this connection pool.
2489   */
2490  @NotNull()
2491  public ServerSet getServerSet()
2492  {
2493    return serverSet;
2494  }
2495
2496
2497
2498  /**
2499   * Specifies the server set that should be used to establish new connections
2500   * for use in this connection pool.  Existing connections will not be
2501   * affected.
2502   *
2503   * @param  serverSet  The server set that should be used to establish new
2504   *                    connections for use in this connection pool.  It must
2505   *                    not be {@code null}.
2506   */
2507  public void setServerSet(@Nullable final ServerSet serverSet)
2508  {
2509    Validator.ensureNotNull(serverSet);
2510    this.serverSet = serverSet;
2511  }
2512
2513
2514
2515  /**
2516   * {@inheritDoc}
2517   */
2518  @Override()
2519  @Nullable()
2520  public String getConnectionPoolName()
2521  {
2522    return connectionPoolName;
2523  }
2524
2525
2526
2527  /**
2528   * {@inheritDoc}
2529   */
2530  @Override()
2531  public void setConnectionPoolName(@Nullable final String connectionPoolName)
2532  {
2533    this.connectionPoolName = connectionPoolName;
2534    for (final LDAPConnection c : availableConnections)
2535    {
2536      c.setConnectionPoolName(connectionPoolName);
2537    }
2538  }
2539
2540
2541
2542  /**
2543   * Indicates whether the connection pool should create a new connection if one
2544   * is requested when there are none available.
2545   *
2546   * @return  {@code true} if a new connection should be created if none are
2547   *          available when a request is received, or {@code false} if an
2548   *          exception should be thrown to indicate that no connection is
2549   *          available.
2550   */
2551  public boolean getCreateIfNecessary()
2552  {
2553    return createIfNecessary;
2554  }
2555
2556
2557
2558  /**
2559   * Specifies whether the connection pool should create a new connection if one
2560   * is requested when there are none available.
2561   *
2562   * @param  createIfNecessary  Specifies whether the connection pool should
2563   *                            create a new connection if one is requested when
2564   *                            there are none available.
2565   */
2566  public void setCreateIfNecessary(final boolean createIfNecessary)
2567  {
2568    this.createIfNecessary = createIfNecessary;
2569  }
2570
2571
2572
2573  /**
2574   * Retrieves the maximum length of time in milliseconds to wait for a
2575   * connection to become available when trying to obtain a connection from the
2576   * pool.
2577   *
2578   * @return  The maximum length of time in milliseconds to wait for a
2579   *          connection to become available when trying to obtain a connection
2580   *          from the pool, or zero to indicate that the pool should not block
2581   *          at all if no connections are available and that it should either
2582   *          create a new connection or throw an exception.
2583   */
2584  public long getMaxWaitTimeMillis()
2585  {
2586    return maxWaitTime;
2587  }
2588
2589
2590
2591  /**
2592   * Specifies the maximum length of time in milliseconds to wait for a
2593   * connection to become available when trying to obtain a connection from the
2594   * pool.
2595   *
2596   * @param  maxWaitTime  The maximum length of time in milliseconds to wait for
2597   *                      a connection to become available when trying to obtain
2598   *                      a connection from the pool.  A value of zero should be
2599   *                      used to indicate that the pool should not block at all
2600   *                      if no connections are available and that it should
2601   *                      either create a new connection or throw an exception.
2602   */
2603  public void setMaxWaitTimeMillis(final long maxWaitTime)
2604  {
2605    if (maxWaitTime > 0L)
2606    {
2607      this.maxWaitTime = maxWaitTime;
2608    }
2609    else
2610    {
2611      this.maxWaitTime = 0L;
2612    }
2613  }
2614
2615
2616
2617  /**
2618   * Retrieves the maximum length of time in milliseconds that a connection in
2619   * this pool may be established before it is closed and replaced with another
2620   * connection.
2621   *
2622   * @return  The maximum length of time in milliseconds that a connection in
2623   *          this pool may be established before it is closed and replaced with
2624   *          another connection, or {@code 0L} if no maximum age should be
2625   *          enforced.
2626   */
2627  public long getMaxConnectionAgeMillis()
2628  {
2629    return maxConnectionAge;
2630  }
2631
2632
2633
2634  /**
2635   * Specifies the maximum length of time in milliseconds that a connection in
2636   * this pool may be established before it should be closed and replaced with
2637   * another connection.
2638   *
2639   * @param  maxConnectionAge  The maximum length of time in milliseconds that a
2640   *                           connection in this pool may be established before
2641   *                           it should be closed and replaced with another
2642   *                           connection.  A value of zero indicates that no
2643   *                           maximum age should be enforced.
2644   */
2645  public void setMaxConnectionAgeMillis(final long maxConnectionAge)
2646  {
2647    if (maxConnectionAge > 0L)
2648    {
2649      this.maxConnectionAge = maxConnectionAge;
2650    }
2651    else
2652    {
2653      this.maxConnectionAge = 0L;
2654    }
2655  }
2656
2657
2658
2659  /**
2660   * Retrieves the maximum connection age that should be used for connections
2661   * that were created in order to replace defunct connections.  It is possible
2662   * to define a custom maximum connection age for these connections to allow
2663   * them to be closed and re-established more quickly to allow for a
2664   * potentially quicker fail-back to a normal state.  Note, that if this
2665   * capability is to be used, then the maximum age for these connections should
2666   * be long enough to allow the problematic server to become available again
2667   * under normal circumstances (e.g., it should be long enough for at least a
2668   * shutdown and restart of the server, plus some overhead for potentially
2669   * performing routine maintenance while the server is offline, or a chance for
2670   * an administrator to be made available that a server has gone down).
2671   *
2672   * @return  The maximum connection age that should be used for connections
2673   *          that were created in order to replace defunct connections, a value
2674   *          of zero to indicate that no maximum age should be enforced, or
2675   *          {@code null} if the value returned by the
2676   *          {@link #getMaxConnectionAgeMillis()} method should be used.
2677   */
2678  @Nullable()
2679  public Long getMaxDefunctReplacementConnectionAgeMillis()
2680  {
2681    return maxDefunctReplacementConnectionAge;
2682  }
2683
2684
2685
2686  /**
2687   * Specifies the maximum connection age that should be used for connections
2688   * that were created in order to replace defunct connections.  It is possible
2689   * to define a custom maximum connection age for these connections to allow
2690   * them to be closed and re-established more quickly to allow for a
2691   * potentially quicker fail-back to a normal state.  Note, that if this
2692   * capability is to be used, then the maximum age for these connections should
2693   * be long enough to allow the problematic server to become available again
2694   * under normal circumstances (e.g., it should be long enough for at least a
2695   * shutdown and restart of the server, plus some overhead for potentially
2696   * performing routine maintenance while the server is offline, or a chance for
2697   * an administrator to be made available that a server has gone down).
2698   *
2699   * @param  maxDefunctReplacementConnectionAge  The maximum connection age that
2700   *              should be used for connections that were created in order to
2701   *              replace defunct connections.  It may be zero if no maximum age
2702   *              should be enforced for such connections, or it may be
2703   *              {@code null} if the value returned by the
2704   *              {@link #getMaxConnectionAgeMillis()} method should be used.
2705   */
2706  public void setMaxDefunctReplacementConnectionAgeMillis(
2707                   @Nullable final Long maxDefunctReplacementConnectionAge)
2708  {
2709    if (maxDefunctReplacementConnectionAge == null)
2710    {
2711      this.maxDefunctReplacementConnectionAge = null;
2712    }
2713    else if (maxDefunctReplacementConnectionAge > 0L)
2714    {
2715      this.maxDefunctReplacementConnectionAge =
2716           maxDefunctReplacementConnectionAge;
2717    }
2718    else
2719    {
2720      this.maxDefunctReplacementConnectionAge = 0L;
2721    }
2722  }
2723
2724
2725
2726  /**
2727   * Indicates whether to check the age of a connection against the configured
2728   * maximum connection age whenever it is released to the pool.  By default,
2729   * connection age is evaluated in the background using the health check
2730   * thread, but it is also possible to configure the pool to additionally
2731   * examine the age of a connection when it is returned to the pool.
2732   * <BR><BR>
2733   * Performing connection age evaluation only in the background will ensure
2734   * that connections are only closed and re-established in a single-threaded
2735   * manner, which helps minimize the load against the target server, but only
2736   * checks connections that are not in use when the health check thread is
2737   * active.  If the pool is configured to also evaluate the connection age when
2738   * connections are returned to the pool, then it may help ensure that the
2739   * maximum connection age is honored more strictly for all connections, but
2740   * in busy applications may lead to cases in which multiple connections are
2741   * closed and re-established simultaneously, which may increase load against
2742   * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2743   * method may be used to help mitigate the potential performance impact of
2744   * closing and re-establishing multiple connections simultaneously.
2745   *
2746   * @return  {@code true} if the connection pool should check connection age in
2747   *          both the background health check thread and when connections are
2748   *          released to the pool, or {@code false} if the connection age
2749   *          should only be checked by the background health check thread.
2750   */
2751  public boolean checkConnectionAgeOnRelease()
2752  {
2753    return checkConnectionAgeOnRelease;
2754  }
2755
2756
2757
2758  /**
2759   * Specifies whether to check the age of a connection against the configured
2760   * maximum connection age whenever it is released to the pool.  By default,
2761   * connection age is evaluated in the background using the health check
2762   * thread, but it is also possible to configure the pool to additionally
2763   * examine the age of a connection when it is returned to the pool.
2764   * <BR><BR>
2765   * Performing connection age evaluation only in the background will ensure
2766   * that connections are only closed and re-established in a single-threaded
2767   * manner, which helps minimize the load against the target server, but only
2768   * checks connections that are not in use when the health check thread is
2769   * active.  If the pool is configured to also evaluate the connection age when
2770   * connections are returned to the pool, then it may help ensure that the
2771   * maximum connection age is honored more strictly for all connections, but
2772   * in busy applications may lead to cases in which multiple connections are
2773   * closed and re-established simultaneously, which may increase load against
2774   * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2775   * method may be used to help mitigate the potential performance impact of
2776   * closing and re-establishing multiple connections simultaneously.
2777   *
2778   * @param  checkConnectionAgeOnRelease  If {@code true}, this indicates that
2779   *                                      the connection pool should check
2780   *                                      connection age in both the background
2781   *                                      health check thread and when
2782   *                                      connections are released to the pool.
2783   *                                      If {@code false}, this indicates that
2784   *                                      the connection pool should check
2785   *                                      connection age only in the background
2786   *                                      health check thread.
2787   */
2788  public void setCheckConnectionAgeOnRelease(
2789                   final boolean checkConnectionAgeOnRelease)
2790  {
2791    this.checkConnectionAgeOnRelease = checkConnectionAgeOnRelease;
2792  }
2793
2794
2795
2796  /**
2797   * Retrieves the minimum length of time in milliseconds that should pass
2798   * between connections closed because they have been established for longer
2799   * than the maximum connection age.
2800   *
2801   * @return  The minimum length of time in milliseconds that should pass
2802   *          between connections closed because they have been established for
2803   *          longer than the maximum connection age, or {@code 0L} if expired
2804   *          connections may be closed as quickly as they are identified.
2805   */
2806  public long getMinDisconnectIntervalMillis()
2807  {
2808    return minDisconnectInterval;
2809  }
2810
2811
2812
2813  /**
2814   * Specifies the minimum length of time in milliseconds that should pass
2815   * between connections closed because they have been established for longer
2816   * than the maximum connection age.
2817   *
2818   * @param  minDisconnectInterval  The minimum length of time in milliseconds
2819   *                                that should pass between connections closed
2820   *                                because they have been established for
2821   *                                longer than the maximum connection age.  A
2822   *                                value less than or equal to zero indicates
2823   *                                that no minimum time should be enforced.
2824   */
2825  public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
2826  {
2827    if (minDisconnectInterval > 0)
2828    {
2829      this.minDisconnectInterval = minDisconnectInterval;
2830    }
2831    else
2832    {
2833      this.minDisconnectInterval = 0L;
2834    }
2835  }
2836
2837
2838
2839  /**
2840   * {@inheritDoc}
2841   */
2842  @Override()
2843  @NotNull()
2844  public LDAPConnectionPoolHealthCheck getHealthCheck()
2845  {
2846    return healthCheck;
2847  }
2848
2849
2850
2851  /**
2852   * Sets the health check implementation for this connection pool.
2853   *
2854   * @param  healthCheck  The health check implementation for this connection
2855   *                      pool.  It must not be {@code null}.
2856   */
2857  public void setHealthCheck(
2858                   @NotNull final LDAPConnectionPoolHealthCheck healthCheck)
2859  {
2860    Validator.ensureNotNull(healthCheck);
2861    this.healthCheck = healthCheck;
2862  }
2863
2864
2865
2866  /**
2867   * {@inheritDoc}
2868   */
2869  @Override()
2870  public long getHealthCheckIntervalMillis()
2871  {
2872    return healthCheckInterval;
2873  }
2874
2875
2876
2877  /**
2878   * {@inheritDoc}
2879   */
2880  @Override()
2881  public void setHealthCheckIntervalMillis(final long healthCheckInterval)
2882  {
2883    Validator.ensureTrue(healthCheckInterval > 0L,
2884         "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
2885    this.healthCheckInterval = healthCheckInterval;
2886    healthCheckThread.wakeUp();
2887  }
2888
2889
2890
2891  /**
2892   * Indicates whether health check processing for connections operating in
2893   * synchronous mode should include attempting to perform a read from each
2894   * connection with a very short timeout.  This can help detect unsolicited
2895   * responses and unexpected connection closures in a more timely manner.  This
2896   * will be ignored for connections not operating in synchronous mode.
2897   *
2898   * @return  {@code true} if health check processing for connections operating
2899   *          in synchronous mode should include a read attempt with a very
2900   *          short timeout, or {@code false} if not.
2901   */
2902  public boolean trySynchronousReadDuringHealthCheck()
2903  {
2904    return trySynchronousReadDuringHealthCheck;
2905  }
2906
2907
2908
2909  /**
2910   * Specifies whether health check processing for connections operating in
2911   * synchronous mode should include attempting to perform a read from each
2912   * connection with a very short timeout.
2913   *
2914   * @param  trySynchronousReadDuringHealthCheck  Indicates whether health check
2915   *                                              processing for connections
2916   *                                              operating in synchronous mode
2917   *                                              should include attempting to
2918   *                                              perform a read from each
2919   *                                              connection with a very short
2920   *                                              timeout.
2921   */
2922  public void setTrySynchronousReadDuringHealthCheck(
2923                   final boolean trySynchronousReadDuringHealthCheck)
2924  {
2925    this.trySynchronousReadDuringHealthCheck =
2926         trySynchronousReadDuringHealthCheck;
2927  }
2928
2929
2930
2931  /**
2932   * {@inheritDoc}
2933   */
2934  @Override()
2935  protected void doHealthCheck()
2936  {
2937    invokeHealthCheck(null, true);
2938  }
2939
2940
2941
2942  /**
2943   * Invokes a synchronous one-time health-check against the connections in this
2944   * pool that are not currently in use.  This will be independent of any
2945   * background health checking that may be automatically performed by the pool.
2946   *
2947   * @param  healthCheck         The health check to use.  If this is
2948   *                             {@code null}, then the pool's
2949   *                             currently-configured health check (if any) will
2950   *                             be used.  If this is {@code null} and there is
2951   *                             no health check configured for the pool, then
2952   *                             only a basic set of checks.
2953   * @param  checkForExpiration  Indicates whether to check to see if any
2954   *                             connections have been established for longer
2955   *                             than the maximum connection age.  If this is
2956   *                             {@code true} then any expired connections will
2957   *                             be closed and replaced with newly-established
2958   *                             connections.
2959   *
2960   * @return  An object with information about the result of the health check
2961   *          processing.
2962   */
2963  @NotNull()
2964  public LDAPConnectionPoolHealthCheckResult invokeHealthCheck(
2965              @Nullable final LDAPConnectionPoolHealthCheck healthCheck,
2966              final boolean checkForExpiration)
2967  {
2968    return invokeHealthCheck(healthCheck, checkForExpiration,
2969         checkForExpiration);
2970  }
2971
2972
2973
2974  /**
2975   * Invokes a synchronous one-time health-check against the connections in this
2976   * pool that are not currently in use.  This will be independent of any
2977   * background health checking that may be automatically performed by the pool.
2978   *
2979   * @param  healthCheck             The health check to use.  If this is
2980   *                                 {@code null}, then the pool's
2981   *                                 currently-configured health check (if any)
2982   *                                 will be used.  If this is {@code null} and
2983   *                                 there is no health check configured for the
2984   *                                 pool, then only a basic set of checks.
2985   * @param  checkForExpiration      Indicates whether to check to see if any
2986   *                                 connections have been established for
2987   *                                 longer than the maximum connection age.  If
2988   *                                 this is {@code true} then any expired
2989   *                                 connections will be closed and replaced
2990   *                                 with newly-established connections.
2991   * @param  checkMinConnectionGoal  Indicates whether to check to see if the
2992   *                                 currently-available number of connections
2993   *                                 is less than the minimum available
2994   *                                 connection goal.  If this is {@code true}
2995   *                                 the minimum available connection goal is
2996   *                                 greater than zero, and the number of
2997   *                                 currently-available connections is less
2998   *                                 than the goal, then this method will
2999   *                                 attempt to create enough new connections to
3000   *                                 reach the goal.
3001   *
3002   * @return  An object with information about the result of the health check
3003   *          processing.
3004   */
3005  @NotNull()
3006  public LDAPConnectionPoolHealthCheckResult invokeHealthCheck(
3007              @Nullable final LDAPConnectionPoolHealthCheck healthCheck,
3008              final boolean checkForExpiration,
3009              final boolean checkMinConnectionGoal)
3010  {
3011    // Determine which health check to use.
3012    final LDAPConnectionPoolHealthCheck hc;
3013    if (healthCheck == null)
3014    {
3015      hc = this.healthCheck;
3016    }
3017    else
3018    {
3019      hc = healthCheck;
3020    }
3021
3022
3023    // Create a set used to hold connections that we've already examined.  If we
3024    // encounter the same connection twice, then we know that we don't need to
3025    // do any more work.
3026    final HashSet<LDAPConnection> examinedConnections =
3027         new HashSet<>(StaticUtils.computeMapCapacity(numConnections));
3028    int numExamined = 0;
3029    int numDefunct = 0;
3030    int numExpired = 0;
3031
3032    for (int i=0; i < numConnections; i++)
3033    {
3034      LDAPConnection conn = availableConnections.poll();
3035      if (conn == null)
3036      {
3037        break;
3038      }
3039      else if (examinedConnections.contains(conn))
3040      {
3041        if (! availableConnections.offer(conn))
3042        {
3043          conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3044                                 null, null);
3045          poolStatistics.incrementNumConnectionsClosedUnneeded();
3046          Debug.debugConnectionPool(Level.INFO, this, conn,
3047               "Closing a connection that had just been health checked " +
3048                    "because the pool is now full", null);
3049          conn.terminate(null);
3050        }
3051        break;
3052      }
3053
3054      numExamined++;
3055      if (! conn.isConnected())
3056      {
3057        numDefunct++;
3058        poolStatistics.incrementNumConnectionsClosedDefunct();
3059        Debug.debugConnectionPool(Level.WARNING, this, conn,
3060             "Closing a connection that was identified as not established " +
3061                  "during health check processing",
3062             null);
3063        conn = handleDefunctConnection(conn);
3064        if (conn != null)
3065        {
3066          examinedConnections.add(conn);
3067        }
3068      }
3069      else
3070      {
3071        if (checkForExpiration && connectionIsExpired(conn))
3072        {
3073          numExpired++;
3074
3075          try
3076          {
3077            final LDAPConnection newConnection = createConnection();
3078            if (availableConnections.offer(newConnection))
3079            {
3080              examinedConnections.add(newConnection);
3081              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
3082                   null, null);
3083              conn.terminate(null);
3084              poolStatistics.incrementNumConnectionsClosedExpired();
3085              Debug.debugConnectionPool(Level.INFO, this, conn,
3086                   "Closing a connection that was identified as expired " +
3087                        "during health check processing",
3088                   null);
3089              lastExpiredDisconnectTime = System.currentTimeMillis();
3090              continue;
3091            }
3092            else
3093            {
3094              newConnection.setDisconnectInfo(
3095                   DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
3096              newConnection.terminate(null);
3097              poolStatistics.incrementNumConnectionsClosedUnneeded();
3098              Debug.debugConnectionPool(Level.INFO, this, newConnection,
3099                   "Closing a newly created connection created to replace " +
3100                        "an expired connection because the pool is already " +
3101                        "full",
3102                   null);
3103            }
3104          }
3105          catch (final LDAPException le)
3106          {
3107            Debug.debugException(le);
3108          }
3109        }
3110
3111
3112        // If the connection is operating in synchronous mode, then try to read
3113        // a message on it using an extremely short timeout.  This can help
3114        // detect a connection closure or unsolicited notification in a more
3115        // timely manner than if we had to wait for the client code to try to
3116        // use the connection.
3117        if (trySynchronousReadDuringHealthCheck && conn.synchronousMode())
3118        {
3119          int previousTimeout = Integer.MIN_VALUE;
3120          Socket s = null;
3121          try
3122          {
3123            s = conn.getConnectionInternals(true).getSocket();
3124            previousTimeout = s.getSoTimeout();
3125            InternalSDKHelper.setSoTimeout(conn, 1);
3126
3127            final LDAPResponse response = conn.readResponse(0);
3128            if (response instanceof ConnectionClosedResponse)
3129            {
3130              numDefunct++;
3131              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3132                   ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
3133              poolStatistics.incrementNumConnectionsClosedDefunct();
3134              Debug.debugConnectionPool(Level.WARNING, this, conn,
3135                   "Closing existing connection discovered to be " +
3136                        "disconnected during health check processing",
3137                   null);
3138              conn = handleDefunctConnection(conn);
3139              if (conn != null)
3140              {
3141                examinedConnections.add(conn);
3142              }
3143              continue;
3144            }
3145            else if (response instanceof ExtendedResult)
3146            {
3147              // This means we got an unsolicited response.  It could be a
3148              // notice of disconnection, or it could be something else, but in
3149              // any case we'll send it to the connection's unsolicited
3150              // notification handler (if one is defined).
3151              final UnsolicitedNotificationHandler h = conn.
3152                   getConnectionOptions().getUnsolicitedNotificationHandler();
3153              if (h != null)
3154              {
3155                h.handleUnsolicitedNotification(conn,
3156                     (ExtendedResult) response);
3157              }
3158            }
3159            else if (response instanceof LDAPResult)
3160            {
3161              final LDAPResult r = (LDAPResult) response;
3162              if (r.getResultCode() == ResultCode.SERVER_DOWN)
3163              {
3164                numDefunct++;
3165                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3166                     ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
3167                poolStatistics.incrementNumConnectionsClosedDefunct();
3168                Debug.debugConnectionPool(Level.WARNING, this, conn,
3169                     "Closing existing connection discovered to be invalid " +
3170                          "with result " + r + " during health check " +
3171                          "processing",
3172                     null);
3173                conn = handleDefunctConnection(conn);
3174                if (conn != null)
3175                {
3176                  examinedConnections.add(conn);
3177                }
3178                continue;
3179              }
3180            }
3181          }
3182          catch (final LDAPException le)
3183          {
3184            if (le.getResultCode() == ResultCode.TIMEOUT)
3185            {
3186              Debug.debugException(Level.FINEST, le);
3187            }
3188            else
3189            {
3190              Debug.debugException(le);
3191              numDefunct++;
3192              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3193                   ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
3194                        StaticUtils.getExceptionMessage(le)), le);
3195              poolStatistics.incrementNumConnectionsClosedDefunct();
3196              Debug.debugConnectionPool(Level.WARNING, this, conn,
3197                   "Closing existing connection discovered to be invalid " +
3198                        "during health check processing",
3199                   le);
3200              conn = handleDefunctConnection(conn);
3201              if (conn != null)
3202              {
3203                examinedConnections.add(conn);
3204              }
3205              continue;
3206            }
3207          }
3208          catch (final Exception e)
3209          {
3210            Debug.debugException(e);
3211            numDefunct++;
3212            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3213                 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
3214                      StaticUtils.getExceptionMessage(e)),
3215                 e);
3216            poolStatistics.incrementNumConnectionsClosedDefunct();
3217            Debug.debugConnectionPool(Level.SEVERE, this, conn,
3218                 "Closing existing connection discovered to be invalid " +
3219                      "with an unexpected exception type during health check " +
3220                      "processing",
3221                 e);
3222            conn = handleDefunctConnection(conn);
3223            if (conn != null)
3224            {
3225              examinedConnections.add(conn);
3226            }
3227            continue;
3228          }
3229          finally
3230          {
3231            if (previousTimeout != Integer.MIN_VALUE)
3232            {
3233              try
3234              {
3235                if (s != null)
3236                {
3237                  InternalSDKHelper.setSoTimeout(conn, previousTimeout);
3238                }
3239              }
3240              catch (final Exception e)
3241              {
3242                Debug.debugException(e);
3243                numDefunct++;
3244                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3245                     null, e);
3246                poolStatistics.incrementNumConnectionsClosedDefunct();
3247                Debug.debugConnectionPool(Level.SEVERE, this, conn,
3248                     "Closing existing connection during health check " +
3249                          "processing because an error occurred while " +
3250                          "attempting to set the SO_TIMEOUT",
3251                     e);
3252                conn = handleDefunctConnection(conn);
3253                if (conn != null)
3254                {
3255                  examinedConnections.add(conn);
3256                }
3257                continue;
3258              }
3259            }
3260          }
3261        }
3262
3263        try
3264        {
3265          hc.ensureConnectionValidForContinuedUse(conn);
3266          if (availableConnections.offer(conn))
3267          {
3268            examinedConnections.add(conn);
3269          }
3270          else
3271          {
3272            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3273                                   null, null);
3274            poolStatistics.incrementNumConnectionsClosedUnneeded();
3275            Debug.debugConnectionPool(Level.INFO, this, conn,
3276                 "Closing existing connection that passed health check " +
3277                      "processing because the pool is already full",
3278                 null);
3279            conn.terminate(null);
3280          }
3281        }
3282        catch (final Exception e)
3283        {
3284          Debug.debugException(e);
3285          numDefunct++;
3286          poolStatistics.incrementNumConnectionsClosedDefunct();
3287          Debug.debugConnectionPool(Level.WARNING, this, conn,
3288               "Closing existing connection that failed health check " +
3289                    "processing",
3290               e);
3291          conn = handleDefunctConnection(conn);
3292          if (conn != null)
3293          {
3294            examinedConnections.add(conn);
3295          }
3296        }
3297      }
3298    }
3299
3300    if (checkMinConnectionGoal)
3301    {
3302      try
3303      {
3304        final int neededConnections =
3305             minConnectionGoal - availableConnections.size();
3306        for (int i=0; i < neededConnections; i++)
3307        {
3308          final LDAPConnection conn = createConnection(hc);
3309          if (! availableConnections.offer(conn))
3310          {
3311            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3312                                   null, null);
3313            poolStatistics.incrementNumConnectionsClosedUnneeded();
3314            Debug.debugConnectionPool(Level.INFO, this, conn,
3315                 "Closing a new connection that was created during health " +
3316                      "check processing in achieve the minimum connection " +
3317                      "goal, but the pool had already become full after the " +
3318                      "connection was created",
3319                 null);
3320            conn.terminate(null);
3321            break;
3322          }
3323        }
3324      }
3325      catch (final Exception e)
3326      {
3327        Debug.debugException(e);
3328      }
3329    }
3330
3331    return new LDAPConnectionPoolHealthCheckResult(numExamined, numExpired,
3332         numDefunct);
3333  }
3334
3335
3336
3337  /**
3338   * {@inheritDoc}
3339   */
3340  @Override()
3341  public int getCurrentAvailableConnections()
3342  {
3343    return availableConnections.size();
3344  }
3345
3346
3347
3348  /**
3349   * {@inheritDoc}
3350   */
3351  @Override()
3352  public int getMaximumAvailableConnections()
3353  {
3354    return numConnections;
3355  }
3356
3357
3358
3359  /**
3360   * Retrieves the goal for the minimum number of available connections that the
3361   * pool should try to maintain for immediate use.  If this goal is greater
3362   * than zero, then the health checking process will attempt to create enough
3363   * new connections to achieve this goal.
3364   *
3365   * @return  The goal for the minimum number of available connections that the
3366   *          pool should try to maintain for immediate use, or zero if it will
3367   *          not try to maintain a minimum number of available connections.
3368   */
3369  public int getMinimumAvailableConnectionGoal()
3370  {
3371    return minConnectionGoal;
3372  }
3373
3374
3375
3376  /**
3377   * Specifies the goal for the minimum number of available connections that the
3378   * pool should try to maintain for immediate use.  If this goal is greater
3379   * than zero, then the health checking process will attempt to create enough
3380   * new connections to achieve this goal.
3381   *
3382   * @param  goal  The goal for the minimum number of available connections that
3383   *               the pool should try to maintain for immediate use.  A value
3384   *               less than or equal to zero indicates that the pool should not
3385   *               try to maintain a minimum number of available connections.
3386   */
3387  public void setMinimumAvailableConnectionGoal(final int goal)
3388  {
3389    if (goal > numConnections)
3390    {
3391      minConnectionGoal = numConnections;
3392    }
3393    else if (goal > 0)
3394    {
3395      minConnectionGoal = goal;
3396    }
3397    else
3398    {
3399      minConnectionGoal = 0;
3400    }
3401  }
3402
3403
3404
3405  /**
3406   * {@inheritDoc}
3407   */
3408  @Override()
3409  @NotNull()
3410  public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
3411  {
3412    return poolStatistics;
3413  }
3414
3415
3416
3417  /**
3418   * Attempts to reduce the number of connections available for use in the pool.
3419   * Note that this will be a best-effort attempt to reach the desired number
3420   * of connections, as other threads interacting with the connection pool may
3421   * check out and/or release connections that cause the number of available
3422   * connections to fluctuate.
3423   *
3424   * @param  connectionsToRetain  The number of connections that should be
3425   *                              retained for use in the connection pool.
3426   */
3427  public void shrinkPool(final int connectionsToRetain)
3428  {
3429    while (availableConnections.size() > connectionsToRetain)
3430    {
3431      final LDAPConnection conn;
3432      try
3433      {
3434        conn = getConnection();
3435      }
3436      catch (final LDAPException le)
3437      {
3438        return;
3439      }
3440
3441      if (availableConnections.size() >= connectionsToRetain)
3442      {
3443        discardConnection(conn);
3444      }
3445      else
3446      {
3447        releaseConnection(conn);
3448        return;
3449      }
3450    }
3451  }
3452
3453
3454
3455  /**
3456   * Closes this connection pool in the event that it becomes unreferenced.
3457   *
3458   * @throws  Throwable  If an unexpected problem occurs.
3459   */
3460  @Override()
3461  protected void finalize()
3462            throws Throwable
3463  {
3464    super.finalize();
3465
3466    close();
3467  }
3468
3469
3470
3471  /**
3472   * {@inheritDoc}
3473   */
3474  @Override()
3475  public void toString(@NotNull final StringBuilder buffer)
3476  {
3477    buffer.append("LDAPConnectionPool(");
3478
3479    final String name = connectionPoolName;
3480    if (name != null)
3481    {
3482      buffer.append("name='");
3483      buffer.append(name);
3484      buffer.append("', ");
3485    }
3486
3487    buffer.append("serverSet=");
3488    serverSet.toString(buffer);
3489    buffer.append(", maxConnections=");
3490    buffer.append(numConnections);
3491    buffer.append(')');
3492  }
3493}