001/*
002 * Copyright 2007-2026 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2026 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-2026 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    // Invoke pre-authentication post-connect processing.
1311    if (postConnectProcessor != null)
1312    {
1313      try
1314      {
1315        postConnectProcessor.processPreAuthenticatedConnection(c);
1316      }
1317      catch (final Exception e)
1318      {
1319        Debug.debugException(e);
1320
1321        try
1322        {
1323          poolStatistics.incrementNumFailedConnectionAttempts();
1324          Debug.debugConnectionPool(Level.SEVERE, this, c,
1325               "Exception in pre-authentication post-connect processing", e);
1326          c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
1327          c.setClosed();
1328        }
1329        catch (final Exception e2)
1330        {
1331          Debug.debugException(e2);
1332        }
1333
1334        if (e instanceof LDAPException)
1335        {
1336          throw ((LDAPException) e);
1337        }
1338        else
1339        {
1340          throw new LDAPException(ResultCode.CONNECT_ERROR,
1341               ERR_POOL_POST_CONNECT_ERROR.get(
1342                    StaticUtils.getExceptionMessage(e)),
1343               e);
1344        }
1345      }
1346    }
1347
1348
1349    // Authenticate the connection if appropriate.
1350    if ((bindRequest != null) && (! serverSet.includesAuthentication()))
1351    {
1352      BindResult bindResult;
1353      try
1354      {
1355        bindResult = c.bind(bindRequest.duplicate());
1356      }
1357      catch (final LDAPBindException lbe)
1358      {
1359        Debug.debugException(lbe);
1360        bindResult = lbe.getBindResult();
1361      }
1362      catch (final LDAPException le)
1363      {
1364        Debug.debugException(le);
1365        bindResult = new BindResult(le);
1366      }
1367
1368      try
1369      {
1370        if (healthCheck != null)
1371        {
1372          healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult);
1373        }
1374
1375        if (bindResult.getResultCode() != ResultCode.SUCCESS)
1376        {
1377          throw new LDAPBindException(bindResult);
1378        }
1379      }
1380      catch (final LDAPException le)
1381      {
1382        Debug.debugException(le);
1383
1384        try
1385        {
1386          poolStatistics.incrementNumFailedConnectionAttempts();
1387          if (bindResult.getResultCode() != ResultCode.SUCCESS)
1388          {
1389            Debug.debugConnectionPool(Level.SEVERE, this, c,
1390                 "Failed to authenticate a new pooled connection", le);
1391          }
1392          else
1393          {
1394            Debug.debugConnectionPool(Level.SEVERE, this, c,
1395                 "A new pooled connection failed its post-authentication " +
1396                      "health check",
1397                 le);
1398          }
1399          c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
1400          c.setClosed();
1401        }
1402        catch (final Exception e)
1403        {
1404          Debug.debugException(e);
1405        }
1406
1407        throw le;
1408      }
1409    }
1410
1411
1412    // Invoke post-authentication post-connect processing.
1413    if (postConnectProcessor != null)
1414    {
1415      try
1416      {
1417        postConnectProcessor.processPostAuthenticatedConnection(c);
1418      }
1419      catch (final Exception e)
1420      {
1421        Debug.debugException(e);
1422        try
1423        {
1424          poolStatistics.incrementNumFailedConnectionAttempts();
1425          Debug.debugConnectionPool(Level.SEVERE, this, c,
1426               "Exception in post-authentication post-connect processing", e);
1427          c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
1428          c.setClosed();
1429        }
1430        catch (final Exception e2)
1431        {
1432          Debug.debugException(e2);
1433        }
1434
1435        if (e instanceof LDAPException)
1436        {
1437          throw ((LDAPException) e);
1438        }
1439        else
1440        {
1441          throw new LDAPException(ResultCode.CONNECT_ERROR,
1442               ERR_POOL_POST_CONNECT_ERROR.get(
1443                    StaticUtils.getExceptionMessage(e)),
1444               e);
1445        }
1446      }
1447    }
1448
1449
1450    // Get the pooled schema if appropriate.
1451    final LDAPConnectionOptions opts = c.getConnectionOptions();
1452    if (opts.usePooledSchema())
1453    {
1454      final long currentTime = System.currentTimeMillis();
1455      if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst()))
1456      {
1457        try
1458        {
1459          final Schema schema = c.getSchema();
1460          if (schema != null)
1461          {
1462            c.setCachedSchema(schema);
1463
1464            final long timeout = opts.getPooledSchemaTimeoutMillis();
1465            if ((timeout <= 0L) || (currentTime + timeout <= 0L))
1466            {
1467              pooledSchema = new ObjectPair<>(Long.MAX_VALUE, schema);
1468            }
1469            else
1470            {
1471              pooledSchema = new ObjectPair<>((currentTime+timeout), schema);
1472            }
1473          }
1474        }
1475        catch (final Exception e)
1476        {
1477          Debug.debugException(e);
1478
1479          // There was a problem retrieving the schema from the server, but if
1480          // we have an earlier copy then we can assume it's still valid.
1481          if (pooledSchema != null)
1482          {
1483            c.setCachedSchema(pooledSchema.getSecond());
1484          }
1485        }
1486      }
1487      else
1488      {
1489        c.setCachedSchema(pooledSchema.getSecond());
1490      }
1491    }
1492
1493
1494    // Finish setting up the connection.
1495    c.setConnectionPoolName(connectionPoolName);
1496    poolStatistics.incrementNumSuccessfulConnectionAttempts();
1497    Debug.debugConnectionPool(Level.INFO, this, c,
1498         "Successfully created a new pooled connection", null);
1499
1500    return c;
1501  }
1502
1503
1504
1505  /**
1506   * {@inheritDoc}
1507   */
1508  @Override()
1509  public void close()
1510  {
1511    close(true, 1);
1512  }
1513
1514
1515
1516  /**
1517   * {@inheritDoc}
1518   */
1519  @Override()
1520  public void close(final boolean unbind, final int numThreads)
1521  {
1522    try
1523    {
1524      final boolean healthCheckThreadAlreadySignaled = closed;
1525      closed = true;
1526      healthCheckThread.stopRunning(! healthCheckThreadAlreadySignaled);
1527
1528      try
1529      {
1530        serverSet.shutDown();
1531      }
1532      catch (final Exception e)
1533      {
1534        Debug.debugException(e);
1535      }
1536
1537      if (numThreads > 1)
1538      {
1539        final ArrayList<LDAPConnection> connList =
1540             new ArrayList<>(availableConnections.size());
1541        availableConnections.drainTo(connList);
1542
1543        if (! connList.isEmpty())
1544        {
1545          final ParallelPoolCloser closer =
1546               new ParallelPoolCloser(connList, unbind, numThreads);
1547          closer.closeConnections();
1548        }
1549      }
1550      else
1551      {
1552        while (true)
1553        {
1554          final LDAPConnection conn = availableConnections.poll();
1555          if (conn == null)
1556          {
1557            return;
1558          }
1559          else
1560          {
1561            poolStatistics.incrementNumConnectionsClosedUnneeded();
1562            Debug.debugConnectionPool(Level.INFO, this, conn,
1563                 "Closed a connection as part of closing the connection pool",
1564                 null);
1565            conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
1566            if (unbind)
1567            {
1568              conn.terminate(null);
1569            }
1570            else
1571            {
1572              conn.setClosed();
1573            }
1574          }
1575        }
1576      }
1577    }
1578    finally
1579    {
1580      Debug.debugConnectionPool(Level.INFO, this, null,
1581           "Closed the connection pool", null);
1582    }
1583  }
1584
1585
1586
1587  /**
1588   * {@inheritDoc}
1589   */
1590  @Override()
1591  public boolean isClosed()
1592  {
1593    return closed;
1594  }
1595
1596
1597
1598  /**
1599   * Processes a simple bind using a connection from this connection pool, and
1600   * then reverts that authentication by re-binding as the same user used to
1601   * authenticate new connections.  If new connections are unauthenticated, then
1602   * the subsequent bind will be an anonymous simple bind.  This method attempts
1603   * to ensure that processing the provided bind operation does not have a
1604   * lasting impact the authentication state of the connection used to process
1605   * it.
1606   * <BR><BR>
1607   * If the second bind attempt (the one used to restore the authentication
1608   * identity) fails, the connection will be closed as defunct so that a new
1609   * connection will be created to take its place.
1610   *
1611   * @param  bindDN    The bind DN for the simple bind request.
1612   * @param  password  The password for the simple bind request.
1613   * @param  controls  The optional set of controls for the simple bind request.
1614   *
1615   * @return  The result of processing the provided bind operation.
1616   *
1617   * @throws  LDAPException  If the server rejects the bind request, or if a
1618   *                         problem occurs while sending the request or reading
1619   *                         the response.
1620   */
1621  @NotNull()
1622  public BindResult bindAndRevertAuthentication(@Nullable final String bindDN,
1623                         @Nullable final String password,
1624                         @Nullable final Control... controls)
1625         throws LDAPException
1626  {
1627    return bindAndRevertAuthentication(
1628         new SimpleBindRequest(bindDN, password, controls));
1629  }
1630
1631
1632
1633  /**
1634   * Processes the provided bind request using a connection from this connection
1635   * pool, and then reverts that authentication by re-binding as the same user
1636   * used to authenticate new connections.  If new connections are
1637   * unauthenticated, then the subsequent bind will be an anonymous simple bind.
1638   * This method attempts to ensure that processing the provided bind operation
1639   * does not have a lasting impact the authentication state of the connection
1640   * used to process it.
1641   * <BR><BR>
1642   * If the second bind attempt (the one used to restore the authentication
1643   * identity) fails, the connection will be closed as defunct so that a new
1644   * connection will be created to take its place.
1645   *
1646   * @param  bindRequest  The bind request to be processed.  It must not be
1647   *                      {@code null}.
1648   *
1649   * @return  The result of processing the provided bind operation.
1650   *
1651   * @throws  LDAPException  If the server rejects the bind request, or if a
1652   *                         problem occurs while sending the request or reading
1653   *                         the response.
1654   */
1655  @NotNull()
1656  public BindResult bindAndRevertAuthentication(
1657                         @NotNull final BindRequest bindRequest)
1658         throws LDAPException
1659  {
1660    LDAPConnection conn = getConnection();
1661
1662    try
1663    {
1664      final BindResult result = conn.bind(bindRequest);
1665      releaseAndReAuthenticateConnection(conn);
1666      return result;
1667    }
1668    catch (final Throwable t)
1669    {
1670      Debug.debugException(t);
1671
1672      if (t instanceof LDAPException)
1673      {
1674        final LDAPException le = (LDAPException) t;
1675
1676        boolean shouldThrow;
1677        try
1678        {
1679          healthCheck.ensureConnectionValidAfterException(conn, le);
1680
1681          // The above call will throw an exception if the connection doesn't
1682          // seem to be valid, so if we've gotten here then we should assume
1683          // that it is valid and we will pass the exception onto the client
1684          // without retrying the operation.
1685          releaseAndReAuthenticateConnection(conn);
1686          shouldThrow = true;
1687        }
1688        catch (final Exception e)
1689        {
1690          Debug.debugException(e);
1691
1692          // This implies that the connection is not valid.  If the pool is
1693          // configured to re-try bind operations on a newly-established
1694          // connection, then that will be done later in this method.
1695          // Otherwise, release the connection as defunct and pass the bind
1696          // exception onto the client.
1697          if (! getOperationTypesToRetryDueToInvalidConnections().contains(
1698                     OperationType.BIND))
1699          {
1700            releaseDefunctConnection(conn);
1701            shouldThrow = true;
1702          }
1703          else
1704          {
1705            shouldThrow = false;
1706          }
1707        }
1708
1709        if (shouldThrow)
1710        {
1711          throw le;
1712        }
1713      }
1714      else
1715      {
1716        releaseDefunctConnection(conn);
1717        StaticUtils.rethrowIfError(t);
1718        throw new LDAPException(ResultCode.LOCAL_ERROR,
1719             ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t);
1720      }
1721    }
1722
1723
1724    // If we've gotten here, then the bind operation should be re-tried on a
1725    // newly-established connection.
1726    conn = replaceDefunctConnection(conn);
1727
1728    try
1729    {
1730      final BindResult result = conn.bind(bindRequest);
1731      releaseAndReAuthenticateConnection(conn);
1732      return result;
1733    }
1734    catch (final Throwable t)
1735    {
1736      Debug.debugException(t);
1737
1738      if (t instanceof LDAPException)
1739      {
1740        final LDAPException le = (LDAPException) t;
1741
1742        try
1743        {
1744          healthCheck.ensureConnectionValidAfterException(conn, le);
1745          releaseAndReAuthenticateConnection(conn);
1746        }
1747        catch (final Exception e)
1748        {
1749          Debug.debugException(e);
1750          releaseDefunctConnection(conn);
1751        }
1752
1753        throw le;
1754      }
1755      else
1756      {
1757        releaseDefunctConnection(conn);
1758        StaticUtils.rethrowIfError(t);
1759        throw new LDAPException(ResultCode.LOCAL_ERROR,
1760             ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t);
1761      }
1762    }
1763  }
1764
1765
1766
1767  /**
1768   * {@inheritDoc}
1769   */
1770  @Override()
1771  @NotNull()
1772  public LDAPConnection getConnection()
1773         throws LDAPException
1774  {
1775    if (closed)
1776    {
1777      poolStatistics.incrementNumFailedCheckouts();
1778      Debug.debugConnectionPool(Level.SEVERE, this, null,
1779           "Failed to get a connection to a closed connection pool", null);
1780      throw new LDAPException(ResultCode.CONNECT_ERROR,
1781                              ERR_POOL_CLOSED.get());
1782    }
1783
1784    LDAPConnection conn = availableConnections.poll();
1785    if (conn != null)
1786    {
1787      Exception connException = null;
1788      if (conn.isConnected())
1789      {
1790        try
1791        {
1792          healthCheck.ensureConnectionValidForCheckout(conn);
1793          poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1794          Debug.debugConnectionPool(Level.INFO, this, conn,
1795               "Checked out an immediately available pooled connection", null);
1796          return conn;
1797        }
1798        catch (final LDAPException le)
1799        {
1800          Debug.debugException(le);
1801          connException = le;
1802        }
1803      }
1804
1805      try
1806      {
1807        conn = replaceDefunctConnection(conn);
1808        poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1809        Debug.debugConnectionPool(Level.INFO, this, conn,
1810             "Returning a newly created connection after a checked-out " +
1811                  "connection was found to be invalid", null);
1812        return conn;
1813      }
1814      catch (final LDAPException e)
1815      {
1816        Debug.debugException(e);
1817      }
1818
1819      for (int i=0; i < numConnections; i++)
1820      {
1821        conn = availableConnections.poll();
1822        if (conn == null)
1823        {
1824          break;
1825        }
1826        else if (conn.isConnected())
1827        {
1828          try
1829          {
1830            healthCheck.ensureConnectionValidForCheckout(conn);
1831            poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
1832            Debug.debugConnectionPool(Level.INFO, this, conn,
1833                 "Checked out an immediately available pooled connection",
1834                 null);
1835            return conn;
1836          }
1837          catch (final LDAPException le)
1838          {
1839            Debug.debugException(le);
1840            poolStatistics.incrementNumConnectionsClosedDefunct();
1841            Debug.debugConnectionPool(Level.WARNING, this, conn,
1842                 "Closing a defunct connection encountered during checkout",
1843                 le);
1844            handleDefunctConnection(conn);
1845          }
1846        }
1847        else
1848        {
1849          poolStatistics.incrementNumConnectionsClosedDefunct();
1850          Debug.debugConnectionPool(Level.WARNING, this, conn,
1851               "Closing a defunct connection encountered during checkout",
1852               null);
1853          handleDefunctConnection(conn);
1854        }
1855      }
1856    }
1857
1858    if (failedReplaceCount.get() > 0)
1859    {
1860      final int newReplaceCount = failedReplaceCount.getAndDecrement();
1861      if (newReplaceCount > 0)
1862      {
1863        try
1864        {
1865          conn = createConnection();
1866          poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1867          Debug.debugConnectionPool(Level.INFO, this, conn,
1868               "Checked out a newly created connection", null);
1869          return conn;
1870        }
1871        catch (final LDAPException le)
1872        {
1873          Debug.debugException(le);
1874          failedReplaceCount.incrementAndGet();
1875          poolStatistics.incrementNumFailedCheckouts();
1876          Debug.debugConnectionPool(Level.SEVERE, this, conn,
1877               "Unable to create a new connection for checkout", le);
1878          throw le;
1879        }
1880      }
1881      else
1882      {
1883        failedReplaceCount.incrementAndGet();
1884      }
1885    }
1886
1887    if (maxWaitTime > 0)
1888    {
1889      try
1890      {
1891        final long startWaitTime = System.currentTimeMillis();
1892        conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS);
1893        final long elapsedWaitTime = System.currentTimeMillis() - startWaitTime;
1894        if (conn != null)
1895        {
1896          try
1897          {
1898            healthCheck.ensureConnectionValidForCheckout(conn);
1899            poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting();
1900            Debug.debugConnectionPool(Level.INFO, this, conn,
1901                 "Checked out an existing connection after waiting " +
1902                      elapsedWaitTime + "ms for it to become available",
1903                 null);
1904            return conn;
1905          }
1906          catch (final LDAPException le)
1907          {
1908            Debug.debugException(le);
1909            poolStatistics.incrementNumConnectionsClosedDefunct();
1910            Debug.debugConnectionPool(Level.WARNING, this, conn,
1911                 "Got a connection for checkout after waiting " +
1912                      elapsedWaitTime + "ms for it to become available, but " +
1913                      "the connection failed the checkout health check",
1914                 le);
1915            handleDefunctConnection(conn);
1916          }
1917        }
1918      }
1919      catch (final InterruptedException ie)
1920      {
1921        Debug.debugException(ie);
1922        Thread.currentThread().interrupt();
1923        throw new LDAPException(ResultCode.LOCAL_ERROR,
1924             ERR_POOL_CHECKOUT_INTERRUPTED.get(), ie);
1925      }
1926    }
1927
1928    if (createIfNecessary)
1929    {
1930      try
1931      {
1932        conn = createConnection();
1933        poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
1934        Debug.debugConnectionPool(Level.INFO, this, conn,
1935             "Checked out a newly created connection", null);
1936        return conn;
1937      }
1938      catch (final LDAPException le)
1939      {
1940        Debug.debugException(le);
1941        poolStatistics.incrementNumFailedCheckouts();
1942        Debug.debugConnectionPool(Level.SEVERE, this, null,
1943             "Unable to create a new connection for checkout", le);
1944        throw le;
1945      }
1946    }
1947    else
1948    {
1949      poolStatistics.incrementNumFailedCheckouts();
1950      Debug.debugConnectionPool(Level.SEVERE, this, null,
1951           "Unable to check out a connection because none are available",
1952           null);
1953      throw new LDAPException(ResultCode.CONNECT_ERROR,
1954                              ERR_POOL_NO_CONNECTIONS.get());
1955    }
1956  }
1957
1958
1959
1960  /**
1961   * Attempts to retrieve a connection from the pool that is established to the
1962   * specified server.  Note that this method will only attempt to return an
1963   * existing connection that is currently available, and will not create a
1964   * connection or wait for any checked-out connections to be returned.
1965   *
1966   * @param  host  The address of the server to which the desired connection
1967   *               should be established.  This must not be {@code null}, and
1968   *               this must exactly match the address provided for the initial
1969   *               connection or the {@code ServerSet} used to create the pool.
1970   * @param  port  The port of the server to which the desired connection should
1971   *               be established.
1972   *
1973   * @return  A connection that is established to the specified server, or
1974   *          {@code null} if there are no available connections established to
1975   *          the specified server.
1976   */
1977  @Nullable()
1978  public LDAPConnection getConnection(@NotNull final String host,
1979                                               final int port)
1980  {
1981    if (closed)
1982    {
1983      poolStatistics.incrementNumFailedCheckouts();
1984      Debug.debugConnectionPool(Level.WARNING, this, null,
1985           "Failed to get a connection to a closed connection pool", null);
1986      return null;
1987    }
1988
1989    final HashSet<LDAPConnection> examinedConnections =
1990         new HashSet<>(StaticUtils.computeMapCapacity(numConnections));
1991    while (true)
1992    {
1993      final LDAPConnection conn = availableConnections.poll();
1994      if (conn == null)
1995      {
1996        poolStatistics.incrementNumFailedCheckouts();
1997        Debug.debugConnectionPool(Level.SEVERE, this, null,
1998             "Failed to get an existing connection to " + host + ':' + port +
1999                  " because no connections are immediately available",
2000             null);
2001        return null;
2002      }
2003
2004      if (examinedConnections.contains(conn))
2005      {
2006        if (! availableConnections.offer(conn))
2007        {
2008          discardConnection(conn);
2009        }
2010
2011        poolStatistics.incrementNumFailedCheckouts();
2012        Debug.debugConnectionPool(Level.WARNING, this, null,
2013             "Failed to get an existing connection to " + host + ':' + port +
2014                  " because none of the available connections are " +
2015                  "established to that server",
2016             null);
2017        return null;
2018      }
2019
2020      if (conn.getConnectedAddress().equals(host) &&
2021          (port == conn.getConnectedPort()))
2022      {
2023        try
2024        {
2025          healthCheck.ensureConnectionValidForCheckout(conn);
2026          poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
2027          Debug.debugConnectionPool(Level.INFO, this, conn,
2028               "Successfully checked out an existing connection to requested " +
2029                    "server " + host + ':' + port,
2030               null);
2031          return conn;
2032        }
2033        catch (final LDAPException le)
2034        {
2035          Debug.debugException(le);
2036          poolStatistics.incrementNumConnectionsClosedDefunct();
2037          Debug.debugConnectionPool(Level.WARNING, this, conn,
2038               "Closing an existing connection to requested server " + host +
2039                    ':' + port + " because it failed the checkout health " +
2040                    "check",
2041               le);
2042          handleDefunctConnection(conn);
2043          continue;
2044        }
2045      }
2046
2047      if (availableConnections.offer(conn))
2048      {
2049        examinedConnections.add(conn);
2050      }
2051      else
2052      {
2053        discardConnection(conn);
2054      }
2055    }
2056  }
2057
2058
2059
2060  /**
2061   * {@inheritDoc}
2062   */
2063  @Override()
2064  public void releaseConnection(@NotNull final LDAPConnection connection)
2065  {
2066    if (connection == null)
2067    {
2068      return;
2069    }
2070
2071    connection.setConnectionPoolName(connectionPoolName);
2072    if (checkConnectionAgeOnRelease && connectionIsExpired(connection))
2073    {
2074      try
2075      {
2076        final LDAPConnection newConnection = createConnection();
2077        if (availableConnections.offer(newConnection))
2078        {
2079          connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
2080               null, null);
2081          connection.terminate(null);
2082          poolStatistics.incrementNumConnectionsClosedExpired();
2083          Debug.debugConnectionPool(Level.WARNING, this, connection,
2084               "Closing a released connection because it is expired", null);
2085          lastExpiredDisconnectTime = System.currentTimeMillis();
2086        }
2087        else
2088        {
2089          newConnection.setDisconnectInfo(
2090               DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
2091          newConnection.terminate(null);
2092          poolStatistics.incrementNumConnectionsClosedUnneeded();
2093          Debug.debugConnectionPool(Level.WARNING, this, connection,
2094               "Closing a released connection because the pool is already full",
2095               null);
2096        }
2097      }
2098      catch (final LDAPException le)
2099      {
2100        Debug.debugException(le);
2101      }
2102      return;
2103    }
2104
2105    try
2106    {
2107      healthCheck.ensureConnectionValidForRelease(connection);
2108    }
2109    catch (final LDAPException le)
2110    {
2111      releaseDefunctConnection(connection);
2112      return;
2113    }
2114
2115    if (availableConnections.offer(connection))
2116    {
2117      poolStatistics.incrementNumReleasedValid();
2118      Debug.debugConnectionPool(Level.INFO, this, connection,
2119           "Released a connection back to the pool", null);
2120    }
2121    else
2122    {
2123      // This means that the connection pool is full, which can happen if the
2124      // pool was empty when a request came in to retrieve a connection and
2125      // createIfNecessary was true.  In this case, we'll just close the
2126      // connection since we don't need it any more.
2127      connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2128                                   null, null);
2129      poolStatistics.incrementNumConnectionsClosedUnneeded();
2130      Debug.debugConnectionPool(Level.WARNING, this, connection,
2131           "Closing a released connection because the pool is already full",
2132           null);
2133      connection.terminate(null);
2134      return;
2135    }
2136
2137    if (closed)
2138    {
2139      close();
2140    }
2141  }
2142
2143
2144
2145  /**
2146   * Indicates that the provided connection should be removed from the pool,
2147   * and that no new connection should be created to take its place.  This may
2148   * be used to shrink the pool if such functionality is desired.
2149   *
2150   * @param  connection  The connection to be discarded.
2151   */
2152  public void discardConnection(@NotNull final LDAPConnection connection)
2153  {
2154    if (connection == null)
2155    {
2156      return;
2157    }
2158
2159    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2160         null, null);
2161    connection.terminate(null);
2162    poolStatistics.incrementNumConnectionsClosedUnneeded();
2163    Debug.debugConnectionPool(Level.INFO, this, connection,
2164         "Discareded a connection that is no longer needed", null);
2165
2166    if (availableConnections.remainingCapacity() > 0)
2167    {
2168      final int newReplaceCount = failedReplaceCount.incrementAndGet();
2169      if (newReplaceCount > numConnections)
2170      {
2171        failedReplaceCount.set(numConnections);
2172      }
2173    }
2174  }
2175
2176
2177
2178  /**
2179   * Performs a bind on the provided connection before releasing it back to the
2180   * pool, so that it will be authenticated as the same user as
2181   * newly-established connections.  If newly-established connections are
2182   * unauthenticated, then this method will perform an anonymous simple bind to
2183   * ensure that the resulting connection is unauthenticated.
2184   *
2185   * Releases the provided connection back to this pool.
2186   *
2187   * @param  connection  The connection to be released back to the pool after
2188   *                     being re-authenticated.
2189   */
2190  public void releaseAndReAuthenticateConnection(
2191                   @NotNull final LDAPConnection connection)
2192  {
2193    if (connection == null)
2194    {
2195      return;
2196    }
2197
2198    try
2199    {
2200      BindResult bindResult;
2201      try
2202      {
2203        if (bindRequest == null)
2204        {
2205          bindResult = connection.bind("", "");
2206        }
2207        else
2208        {
2209          bindResult = connection.bind(bindRequest.duplicate());
2210        }
2211      }
2212      catch (final LDAPBindException lbe)
2213      {
2214        Debug.debugException(lbe);
2215        bindResult = lbe.getBindResult();
2216      }
2217
2218      try
2219      {
2220        healthCheck.ensureConnectionValidAfterAuthentication(connection,
2221             bindResult);
2222        if (bindResult.getResultCode() != ResultCode.SUCCESS)
2223        {
2224          throw new LDAPBindException(bindResult);
2225        }
2226      }
2227      catch (final LDAPException le)
2228      {
2229        Debug.debugException(le);
2230
2231        try
2232        {
2233          connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
2234          connection.setClosed();
2235          releaseDefunctConnection(connection);
2236        }
2237        catch (final Exception e)
2238        {
2239          Debug.debugException(e);
2240        }
2241
2242        throw le;
2243      }
2244
2245      releaseConnection(connection);
2246    }
2247    catch (final Exception e)
2248    {
2249      Debug.debugException(e);
2250      releaseDefunctConnection(connection);
2251    }
2252  }
2253
2254
2255
2256  /**
2257   * {@inheritDoc}
2258   */
2259  @Override()
2260  public void releaseDefunctConnection(@NotNull final LDAPConnection connection)
2261  {
2262    if (connection == null)
2263    {
2264      return;
2265    }
2266
2267    connection.setConnectionPoolName(connectionPoolName);
2268    poolStatistics.incrementNumConnectionsClosedDefunct();
2269    Debug.debugConnectionPool(Level.WARNING, this, connection,
2270         "Releasing a defunct connection", null);
2271    handleDefunctConnection(connection);
2272  }
2273
2274
2275
2276  /**
2277   * Performs the real work of terminating a defunct connection and replacing it
2278   * with a new connection if possible.
2279   *
2280   * @param  connection  The defunct connection to be replaced.
2281   *
2282   * @return  The new connection created to take the place of the defunct
2283   *          connection, or {@code null} if no new connection was created.
2284   *          Note that if a connection is returned, it will have already been
2285   *          made available and the caller must not rely on it being unused for
2286   *          any other purpose.
2287   */
2288  @NotNull()
2289  private LDAPConnection handleDefunctConnection(
2290                              @NotNull final LDAPConnection connection)
2291  {
2292    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
2293                                 null);
2294    connection.setClosed();
2295
2296    if (closed)
2297    {
2298      return null;
2299    }
2300
2301    if (createIfNecessary && (availableConnections.remainingCapacity() <= 0))
2302    {
2303      return null;
2304    }
2305
2306    try
2307    {
2308      final LDAPConnection conn = createConnection();
2309      if (maxDefunctReplacementConnectionAge != null)
2310      {
2311        // Only set the maximum age if there isn't one already set for the
2312        // connection (i.e., because it was defined by the server set).
2313        if (conn.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE) == null)
2314        {
2315          conn.setAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE,
2316               maxDefunctReplacementConnectionAge);
2317        }
2318      }
2319
2320      if (! availableConnections.offer(conn))
2321      {
2322        conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
2323                               null, null);
2324        conn.terminate(null);
2325        return null;
2326      }
2327
2328      return conn;
2329    }
2330    catch (final LDAPException le)
2331    {
2332      Debug.debugException(le);
2333      final int newReplaceCount = failedReplaceCount.incrementAndGet();
2334      if (newReplaceCount > numConnections)
2335      {
2336        failedReplaceCount.set(numConnections);
2337      }
2338      return null;
2339    }
2340  }
2341
2342
2343
2344  /**
2345   * {@inheritDoc}
2346   */
2347  @Override()
2348  @NotNull()
2349  public LDAPConnection replaceDefunctConnection(
2350                             @NotNull final LDAPConnection connection)
2351         throws LDAPException
2352  {
2353    poolStatistics.incrementNumConnectionsClosedDefunct();
2354    Debug.debugConnectionPool(Level.WARNING, this, connection,
2355         "Releasing a defunct connection that is to be replaced", null);
2356    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
2357                                 null);
2358    connection.setClosed();
2359
2360    if (closed)
2361    {
2362      throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
2363    }
2364
2365    try
2366    {
2367      return createConnection();
2368    }
2369    catch (final LDAPException le)
2370    {
2371      Debug.debugException(le);
2372      failedReplaceCount.incrementAndGet();
2373      throw le;
2374    }
2375  }
2376
2377
2378
2379  /**
2380   * {@inheritDoc}
2381   */
2382  @Override()
2383  @NotNull()
2384  public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
2385  {
2386    return retryOperationTypes.get();
2387  }
2388
2389
2390
2391  /**
2392   * {@inheritDoc}
2393   */
2394  @Override()
2395  public void setRetryFailedOperationsDueToInvalidConnections(
2396                   @Nullable final Set<OperationType> operationTypes)
2397  {
2398    if ((operationTypes == null) || operationTypes.isEmpty())
2399    {
2400      retryOperationTypes.set(
2401           Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
2402    }
2403    else
2404    {
2405      final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
2406      s.addAll(operationTypes);
2407      retryOperationTypes.set(Collections.unmodifiableSet(s));
2408    }
2409  }
2410
2411
2412
2413  /**
2414   * Indicates whether the provided connection should be considered expired.
2415   *
2416   * @param  connection  The connection for which to make the determination.
2417   *
2418   * @return  {@code true} if the provided connection should be considered
2419   *          expired, or {@code false} if not.
2420   */
2421  private boolean connectionIsExpired(@NotNull final LDAPConnection connection)
2422  {
2423    // There may be a custom maximum connection age for the connection.  If that
2424    // is the case, then use that custom max age rather than the pool-default
2425    // max age.
2426    final long maxAge;
2427    final Object maxAgeObj =
2428         connection.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE);
2429    if ((maxAgeObj != null) && (maxAgeObj instanceof Long))
2430    {
2431      maxAge = (Long) maxAgeObj;
2432    }
2433    else
2434    {
2435      maxAge = maxConnectionAge;
2436    }
2437
2438    // If connection expiration is not enabled, then there is nothing to do.
2439    if (maxAge <= 0L)
2440    {
2441      return false;
2442    }
2443
2444    // If there is a minimum disconnect interval, then make sure that we have
2445    // not closed another expired connection too recently.
2446    final long currentTime = System.currentTimeMillis();
2447    if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
2448    {
2449      return false;
2450    }
2451
2452    // Get the age of the connection and see if it is expired.
2453    final long connectionAge = currentTime - connection.getConnectTime();
2454    return (connectionAge > maxAge);
2455  }
2456
2457
2458
2459  /**
2460   * Specifies the bind request that will be used to authenticate subsequent new
2461   * connections that are established by this connection pool.  The
2462   * authentication state for existing connections will not be altered unless
2463   * one of the {@code bindAndRevertAuthentication} or
2464   * {@code releaseAndReAuthenticateConnection} methods are invoked on those
2465   * connections.
2466   *
2467   * @param  bindRequest  The bind request that will be used to authenticate new
2468   *                      connections that are established by this pool, or
2469   *                      that will be applied to existing connections via the
2470   *                      {@code bindAndRevertAuthentication} or
2471   *                      {@code releaseAndReAuthenticateConnection} method.  It
2472   *                      may be {@code null} if new connections should be
2473   *                      unauthenticated.
2474   */
2475  public void setBindRequest(@Nullable final BindRequest bindRequest)
2476  {
2477    this.bindRequest = bindRequest;
2478  }
2479
2480
2481
2482  /**
2483   * Retrieves the server set that should be used to establish new connections
2484   * for use in this connection pool.
2485   *
2486   * @return  The server set that should be used to establish new connections
2487   *          for use in this connection pool.
2488   */
2489  @NotNull()
2490  public ServerSet getServerSet()
2491  {
2492    return serverSet;
2493  }
2494
2495
2496
2497  /**
2498   * Specifies the server set that should be used to establish new connections
2499   * for use in this connection pool.  Existing connections will not be
2500   * affected.
2501   *
2502   * @param  serverSet  The server set that should be used to establish new
2503   *                    connections for use in this connection pool.  It must
2504   *                    not be {@code null}.
2505   */
2506  public void setServerSet(@Nullable final ServerSet serverSet)
2507  {
2508    Validator.ensureNotNull(serverSet);
2509    this.serverSet = serverSet;
2510  }
2511
2512
2513
2514  /**
2515   * {@inheritDoc}
2516   */
2517  @Override()
2518  @Nullable()
2519  public String getConnectionPoolName()
2520  {
2521    return connectionPoolName;
2522  }
2523
2524
2525
2526  /**
2527   * {@inheritDoc}
2528   */
2529  @Override()
2530  public void setConnectionPoolName(@Nullable final String connectionPoolName)
2531  {
2532    this.connectionPoolName = connectionPoolName;
2533    for (final LDAPConnection c : availableConnections)
2534    {
2535      c.setConnectionPoolName(connectionPoolName);
2536    }
2537  }
2538
2539
2540
2541  /**
2542   * Indicates whether the connection pool should create a new connection if one
2543   * is requested when there are none available.
2544   *
2545   * @return  {@code true} if a new connection should be created if none are
2546   *          available when a request is received, or {@code false} if an
2547   *          exception should be thrown to indicate that no connection is
2548   *          available.
2549   */
2550  public boolean getCreateIfNecessary()
2551  {
2552    return createIfNecessary;
2553  }
2554
2555
2556
2557  /**
2558   * Specifies whether the connection pool should create a new connection if one
2559   * is requested when there are none available.
2560   *
2561   * @param  createIfNecessary  Specifies whether the connection pool should
2562   *                            create a new connection if one is requested when
2563   *                            there are none available.
2564   */
2565  public void setCreateIfNecessary(final boolean createIfNecessary)
2566  {
2567    this.createIfNecessary = createIfNecessary;
2568  }
2569
2570
2571
2572  /**
2573   * Retrieves the maximum length of time in milliseconds to wait for a
2574   * connection to become available when trying to obtain a connection from the
2575   * pool.
2576   *
2577   * @return  The maximum length of time in milliseconds to wait for a
2578   *          connection to become available when trying to obtain a connection
2579   *          from the pool, or zero to indicate that the pool should not block
2580   *          at all if no connections are available and that it should either
2581   *          create a new connection or throw an exception.
2582   */
2583  public long getMaxWaitTimeMillis()
2584  {
2585    return maxWaitTime;
2586  }
2587
2588
2589
2590  /**
2591   * Specifies the maximum length of time in milliseconds to wait for a
2592   * connection to become available when trying to obtain a connection from the
2593   * pool.
2594   *
2595   * @param  maxWaitTime  The maximum length of time in milliseconds to wait for
2596   *                      a connection to become available when trying to obtain
2597   *                      a connection from the pool.  A value of zero should be
2598   *                      used to indicate that the pool should not block at all
2599   *                      if no connections are available and that it should
2600   *                      either create a new connection or throw an exception.
2601   */
2602  public void setMaxWaitTimeMillis(final long maxWaitTime)
2603  {
2604    if (maxWaitTime > 0L)
2605    {
2606      this.maxWaitTime = maxWaitTime;
2607    }
2608    else
2609    {
2610      this.maxWaitTime = 0L;
2611    }
2612  }
2613
2614
2615
2616  /**
2617   * Retrieves the maximum length of time in milliseconds that a connection in
2618   * this pool may be established before it is closed and replaced with another
2619   * connection.
2620   *
2621   * @return  The maximum length of time in milliseconds that a connection in
2622   *          this pool may be established before it is closed and replaced with
2623   *          another connection, or {@code 0L} if no maximum age should be
2624   *          enforced.
2625   */
2626  public long getMaxConnectionAgeMillis()
2627  {
2628    return maxConnectionAge;
2629  }
2630
2631
2632
2633  /**
2634   * Specifies the maximum length of time in milliseconds that a connection in
2635   * this pool may be established before it should be closed and replaced with
2636   * another connection.
2637   *
2638   * @param  maxConnectionAge  The maximum length of time in milliseconds that a
2639   *                           connection in this pool may be established before
2640   *                           it should be closed and replaced with another
2641   *                           connection.  A value of zero indicates that no
2642   *                           maximum age should be enforced.
2643   */
2644  public void setMaxConnectionAgeMillis(final long maxConnectionAge)
2645  {
2646    if (maxConnectionAge > 0L)
2647    {
2648      this.maxConnectionAge = maxConnectionAge;
2649    }
2650    else
2651    {
2652      this.maxConnectionAge = 0L;
2653    }
2654  }
2655
2656
2657
2658  /**
2659   * Retrieves the maximum connection age that should be used for connections
2660   * that were created in order to replace defunct connections.  It is possible
2661   * to define a custom maximum connection age for these connections to allow
2662   * them to be closed and re-established more quickly to allow for a
2663   * potentially quicker fail-back to a normal state.  Note, that if this
2664   * capability is to be used, then the maximum age for these connections should
2665   * be long enough to allow the problematic server to become available again
2666   * under normal circumstances (e.g., it should be long enough for at least a
2667   * shutdown and restart of the server, plus some overhead for potentially
2668   * performing routine maintenance while the server is offline, or a chance for
2669   * an administrator to be made available that a server has gone down).
2670   *
2671   * @return  The maximum connection age that should be used for connections
2672   *          that were created in order to replace defunct connections, a value
2673   *          of zero to indicate that no maximum age should be enforced, or
2674   *          {@code null} if the value returned by the
2675   *          {@link #getMaxConnectionAgeMillis()} method should be used.
2676   */
2677  @Nullable()
2678  public Long getMaxDefunctReplacementConnectionAgeMillis()
2679  {
2680    return maxDefunctReplacementConnectionAge;
2681  }
2682
2683
2684
2685  /**
2686   * Specifies the maximum connection age that should be used for connections
2687   * that were created in order to replace defunct connections.  It is possible
2688   * to define a custom maximum connection age for these connections to allow
2689   * them to be closed and re-established more quickly to allow for a
2690   * potentially quicker fail-back to a normal state.  Note, that if this
2691   * capability is to be used, then the maximum age for these connections should
2692   * be long enough to allow the problematic server to become available again
2693   * under normal circumstances (e.g., it should be long enough for at least a
2694   * shutdown and restart of the server, plus some overhead for potentially
2695   * performing routine maintenance while the server is offline, or a chance for
2696   * an administrator to be made available that a server has gone down).
2697   *
2698   * @param  maxDefunctReplacementConnectionAge  The maximum connection age that
2699   *              should be used for connections that were created in order to
2700   *              replace defunct connections.  It may be zero if no maximum age
2701   *              should be enforced for such connections, or it may be
2702   *              {@code null} if the value returned by the
2703   *              {@link #getMaxConnectionAgeMillis()} method should be used.
2704   */
2705  public void setMaxDefunctReplacementConnectionAgeMillis(
2706                   @Nullable final Long maxDefunctReplacementConnectionAge)
2707  {
2708    if (maxDefunctReplacementConnectionAge == null)
2709    {
2710      this.maxDefunctReplacementConnectionAge = null;
2711    }
2712    else if (maxDefunctReplacementConnectionAge > 0L)
2713    {
2714      this.maxDefunctReplacementConnectionAge =
2715           maxDefunctReplacementConnectionAge;
2716    }
2717    else
2718    {
2719      this.maxDefunctReplacementConnectionAge = 0L;
2720    }
2721  }
2722
2723
2724
2725  /**
2726   * Indicates whether to check the age of a connection against the configured
2727   * maximum connection age whenever it is released to the pool.  By default,
2728   * connection age is evaluated in the background using the health check
2729   * thread, but it is also possible to configure the pool to additionally
2730   * examine the age of a connection when it is returned to the pool.
2731   * <BR><BR>
2732   * Performing connection age evaluation only in the background will ensure
2733   * that connections are only closed and re-established in a single-threaded
2734   * manner, which helps minimize the load against the target server, but only
2735   * checks connections that are not in use when the health check thread is
2736   * active.  If the pool is configured to also evaluate the connection age when
2737   * connections are returned to the pool, then it may help ensure that the
2738   * maximum connection age is honored more strictly for all connections, but
2739   * in busy applications may lead to cases in which multiple connections are
2740   * closed and re-established simultaneously, which may increase load against
2741   * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2742   * method may be used to help mitigate the potential performance impact of
2743   * closing and re-establishing multiple connections simultaneously.
2744   *
2745   * @return  {@code true} if the connection pool should check connection age in
2746   *          both the background health check thread and when connections are
2747   *          released to the pool, or {@code false} if the connection age
2748   *          should only be checked by the background health check thread.
2749   */
2750  public boolean checkConnectionAgeOnRelease()
2751  {
2752    return checkConnectionAgeOnRelease;
2753  }
2754
2755
2756
2757  /**
2758   * Specifies whether to check the age of a connection against the configured
2759   * maximum connection age whenever it is released to the pool.  By default,
2760   * connection age is evaluated in the background using the health check
2761   * thread, but it is also possible to configure the pool to additionally
2762   * examine the age of a connection when it is returned to the pool.
2763   * <BR><BR>
2764   * Performing connection age evaluation only in the background will ensure
2765   * that connections are only closed and re-established in a single-threaded
2766   * manner, which helps minimize the load against the target server, but only
2767   * checks connections that are not in use when the health check thread is
2768   * active.  If the pool is configured to also evaluate the connection age when
2769   * connections are returned to the pool, then it may help ensure that the
2770   * maximum connection age is honored more strictly for all connections, but
2771   * in busy applications may lead to cases in which multiple connections are
2772   * closed and re-established simultaneously, which may increase load against
2773   * the directory server.  The {@link #setMinDisconnectIntervalMillis(long)}
2774   * method may be used to help mitigate the potential performance impact of
2775   * closing and re-establishing multiple connections simultaneously.
2776   *
2777   * @param  checkConnectionAgeOnRelease  If {@code true}, this indicates that
2778   *                                      the connection pool should check
2779   *                                      connection age in both the background
2780   *                                      health check thread and when
2781   *                                      connections are released to the pool.
2782   *                                      If {@code false}, this indicates that
2783   *                                      the connection pool should check
2784   *                                      connection age only in the background
2785   *                                      health check thread.
2786   */
2787  public void setCheckConnectionAgeOnRelease(
2788                   final boolean checkConnectionAgeOnRelease)
2789  {
2790    this.checkConnectionAgeOnRelease = checkConnectionAgeOnRelease;
2791  }
2792
2793
2794
2795  /**
2796   * Retrieves the minimum length of time in milliseconds that should pass
2797   * between connections closed because they have been established for longer
2798   * than the maximum connection age.
2799   *
2800   * @return  The minimum length of time in milliseconds that should pass
2801   *          between connections closed because they have been established for
2802   *          longer than the maximum connection age, or {@code 0L} if expired
2803   *          connections may be closed as quickly as they are identified.
2804   */
2805  public long getMinDisconnectIntervalMillis()
2806  {
2807    return minDisconnectInterval;
2808  }
2809
2810
2811
2812  /**
2813   * Specifies the minimum length of time in milliseconds that should pass
2814   * between connections closed because they have been established for longer
2815   * than the maximum connection age.
2816   *
2817   * @param  minDisconnectInterval  The minimum length of time in milliseconds
2818   *                                that should pass between connections closed
2819   *                                because they have been established for
2820   *                                longer than the maximum connection age.  A
2821   *                                value less than or equal to zero indicates
2822   *                                that no minimum time should be enforced.
2823   */
2824  public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
2825  {
2826    if (minDisconnectInterval > 0)
2827    {
2828      this.minDisconnectInterval = minDisconnectInterval;
2829    }
2830    else
2831    {
2832      this.minDisconnectInterval = 0L;
2833    }
2834  }
2835
2836
2837
2838  /**
2839   * {@inheritDoc}
2840   */
2841  @Override()
2842  @NotNull()
2843  public LDAPConnectionPoolHealthCheck getHealthCheck()
2844  {
2845    return healthCheck;
2846  }
2847
2848
2849
2850  /**
2851   * Sets the health check implementation for this connection pool.
2852   *
2853   * @param  healthCheck  The health check implementation for this connection
2854   *                      pool.  It must not be {@code null}.
2855   */
2856  public void setHealthCheck(
2857                   @NotNull final LDAPConnectionPoolHealthCheck healthCheck)
2858  {
2859    Validator.ensureNotNull(healthCheck);
2860    this.healthCheck = healthCheck;
2861  }
2862
2863
2864
2865  /**
2866   * {@inheritDoc}
2867   */
2868  @Override()
2869  public long getHealthCheckIntervalMillis()
2870  {
2871    return healthCheckInterval;
2872  }
2873
2874
2875
2876  /**
2877   * {@inheritDoc}
2878   */
2879  @Override()
2880  public void setHealthCheckIntervalMillis(final long healthCheckInterval)
2881  {
2882    Validator.ensureTrue(healthCheckInterval > 0L,
2883         "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
2884    this.healthCheckInterval = healthCheckInterval;
2885    healthCheckThread.wakeUp();
2886  }
2887
2888
2889
2890  /**
2891   * Indicates whether health check processing for connections operating in
2892   * synchronous mode should include attempting to perform a read from each
2893   * connection with a very short timeout.  This can help detect unsolicited
2894   * responses and unexpected connection closures in a more timely manner.  This
2895   * will be ignored for connections not operating in synchronous mode.
2896   *
2897   * @return  {@code true} if health check processing for connections operating
2898   *          in synchronous mode should include a read attempt with a very
2899   *          short timeout, or {@code false} if not.
2900   */
2901  public boolean trySynchronousReadDuringHealthCheck()
2902  {
2903    return trySynchronousReadDuringHealthCheck;
2904  }
2905
2906
2907
2908  /**
2909   * Specifies whether health check processing for connections operating in
2910   * synchronous mode should include attempting to perform a read from each
2911   * connection with a very short timeout.
2912   *
2913   * @param  trySynchronousReadDuringHealthCheck  Indicates whether health check
2914   *                                              processing for connections
2915   *                                              operating in synchronous mode
2916   *                                              should include attempting to
2917   *                                              perform a read from each
2918   *                                              connection with a very short
2919   *                                              timeout.
2920   */
2921  public void setTrySynchronousReadDuringHealthCheck(
2922                   final boolean trySynchronousReadDuringHealthCheck)
2923  {
2924    this.trySynchronousReadDuringHealthCheck =
2925         trySynchronousReadDuringHealthCheck;
2926  }
2927
2928
2929
2930  /**
2931   * {@inheritDoc}
2932   */
2933  @Override()
2934  protected void doHealthCheck()
2935  {
2936    invokeHealthCheck(null, true);
2937  }
2938
2939
2940
2941  /**
2942   * Invokes a synchronous one-time health-check against the connections in this
2943   * pool that are not currently in use.  This will be independent of any
2944   * background health checking that may be automatically performed by the pool.
2945   *
2946   * @param  healthCheck         The health check to use.  If this is
2947   *                             {@code null}, then the pool's
2948   *                             currently-configured health check (if any) will
2949   *                             be used.  If this is {@code null} and there is
2950   *                             no health check configured for the pool, then
2951   *                             only a basic set of checks.
2952   * @param  checkForExpiration  Indicates whether to check to see if any
2953   *                             connections have been established for longer
2954   *                             than the maximum connection age.  If this is
2955   *                             {@code true} then any expired connections will
2956   *                             be closed and replaced with newly-established
2957   *                             connections.
2958   *
2959   * @return  An object with information about the result of the health check
2960   *          processing.
2961   */
2962  @NotNull()
2963  public LDAPConnectionPoolHealthCheckResult invokeHealthCheck(
2964              @Nullable final LDAPConnectionPoolHealthCheck healthCheck,
2965              final boolean checkForExpiration)
2966  {
2967    return invokeHealthCheck(healthCheck, checkForExpiration,
2968         checkForExpiration);
2969  }
2970
2971
2972
2973  /**
2974   * Invokes a synchronous one-time health-check against the connections in this
2975   * pool that are not currently in use.  This will be independent of any
2976   * background health checking that may be automatically performed by the pool.
2977   *
2978   * @param  healthCheck             The health check to use.  If this is
2979   *                                 {@code null}, then the pool's
2980   *                                 currently-configured health check (if any)
2981   *                                 will be used.  If this is {@code null} and
2982   *                                 there is no health check configured for the
2983   *                                 pool, then only a basic set of checks.
2984   * @param  checkForExpiration      Indicates whether to check to see if any
2985   *                                 connections have been established for
2986   *                                 longer than the maximum connection age.  If
2987   *                                 this is {@code true} then any expired
2988   *                                 connections will be closed and replaced
2989   *                                 with newly-established connections.
2990   * @param  checkMinConnectionGoal  Indicates whether to check to see if the
2991   *                                 currently-available number of connections
2992   *                                 is less than the minimum available
2993   *                                 connection goal.  If this is {@code true}
2994   *                                 the minimum available connection goal is
2995   *                                 greater than zero, and the number of
2996   *                                 currently-available connections is less
2997   *                                 than the goal, then this method will
2998   *                                 attempt to create enough new connections to
2999   *                                 reach the goal.
3000   *
3001   * @return  An object with information about the result of the health check
3002   *          processing.
3003   */
3004  @NotNull()
3005  public LDAPConnectionPoolHealthCheckResult invokeHealthCheck(
3006              @Nullable final LDAPConnectionPoolHealthCheck healthCheck,
3007              final boolean checkForExpiration,
3008              final boolean checkMinConnectionGoal)
3009  {
3010    // Determine which health check to use.
3011    final LDAPConnectionPoolHealthCheck hc;
3012    if (healthCheck == null)
3013    {
3014      hc = this.healthCheck;
3015    }
3016    else
3017    {
3018      hc = healthCheck;
3019    }
3020
3021
3022    // Create a set used to hold connections that we've already examined.  If we
3023    // encounter the same connection twice, then we know that we don't need to
3024    // do any more work.
3025    final HashSet<LDAPConnection> examinedConnections =
3026         new HashSet<>(StaticUtils.computeMapCapacity(numConnections));
3027    int numExamined = 0;
3028    int numDefunct = 0;
3029    int numExpired = 0;
3030
3031    for (int i=0; i < numConnections; i++)
3032    {
3033      LDAPConnection conn = availableConnections.poll();
3034      if (conn == null)
3035      {
3036        break;
3037      }
3038      else if (examinedConnections.contains(conn))
3039      {
3040        if (! availableConnections.offer(conn))
3041        {
3042          conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3043                                 null, null);
3044          poolStatistics.incrementNumConnectionsClosedUnneeded();
3045          Debug.debugConnectionPool(Level.INFO, this, conn,
3046               "Closing a connection that had just been health checked " +
3047                    "because the pool is now full", null);
3048          conn.terminate(null);
3049        }
3050        break;
3051      }
3052
3053      numExamined++;
3054      if (! conn.isConnected())
3055      {
3056        numDefunct++;
3057        poolStatistics.incrementNumConnectionsClosedDefunct();
3058        Debug.debugConnectionPool(Level.WARNING, this, conn,
3059             "Closing a connection that was identified as not established " +
3060                  "during health check processing",
3061             null);
3062        conn = handleDefunctConnection(conn);
3063        if (conn != null)
3064        {
3065          examinedConnections.add(conn);
3066        }
3067      }
3068      else
3069      {
3070        if (checkForExpiration && connectionIsExpired(conn))
3071        {
3072          numExpired++;
3073
3074          try
3075          {
3076            final LDAPConnection newConnection = createConnection();
3077            if (availableConnections.offer(newConnection))
3078            {
3079              examinedConnections.add(newConnection);
3080              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
3081                   null, null);
3082              conn.terminate(null);
3083              poolStatistics.incrementNumConnectionsClosedExpired();
3084              Debug.debugConnectionPool(Level.INFO, this, conn,
3085                   "Closing a connection that was identified as expired " +
3086                        "during health check processing",
3087                   null);
3088              lastExpiredDisconnectTime = System.currentTimeMillis();
3089              continue;
3090            }
3091            else
3092            {
3093              newConnection.setDisconnectInfo(
3094                   DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null);
3095              newConnection.terminate(null);
3096              poolStatistics.incrementNumConnectionsClosedUnneeded();
3097              Debug.debugConnectionPool(Level.INFO, this, newConnection,
3098                   "Closing a newly created connection created to replace " +
3099                        "an expired connection because the pool is already " +
3100                        "full",
3101                   null);
3102            }
3103          }
3104          catch (final LDAPException le)
3105          {
3106            Debug.debugException(le);
3107          }
3108        }
3109
3110
3111        // If the connection is operating in synchronous mode, then try to read
3112        // a message on it using an extremely short timeout.  This can help
3113        // detect a connection closure or unsolicited notification in a more
3114        // timely manner than if we had to wait for the client code to try to
3115        // use the connection.
3116        if (trySynchronousReadDuringHealthCheck && conn.synchronousMode())
3117        {
3118          int previousTimeout = Integer.MIN_VALUE;
3119          Socket s = null;
3120          try
3121          {
3122            s = conn.getConnectionInternals(true).getSocket();
3123            previousTimeout = s.getSoTimeout();
3124            InternalSDKHelper.setSoTimeout(conn, 1);
3125
3126            final LDAPResponse response = conn.readResponse(0);
3127            if (response instanceof ConnectionClosedResponse)
3128            {
3129              numDefunct++;
3130              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3131                   ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
3132              poolStatistics.incrementNumConnectionsClosedDefunct();
3133              Debug.debugConnectionPool(Level.WARNING, this, conn,
3134                   "Closing existing connection discovered to be " +
3135                        "disconnected during health check processing",
3136                   null);
3137              conn = handleDefunctConnection(conn);
3138              if (conn != null)
3139              {
3140                examinedConnections.add(conn);
3141              }
3142              continue;
3143            }
3144            else if (response instanceof ExtendedResult)
3145            {
3146              // This means we got an unsolicited response.  It could be a
3147              // notice of disconnection, or it could be something else, but in
3148              // any case we'll send it to the connection's unsolicited
3149              // notification handler (if one is defined).
3150              final UnsolicitedNotificationHandler h = conn.
3151                   getConnectionOptions().getUnsolicitedNotificationHandler();
3152              if (h != null)
3153              {
3154                h.handleUnsolicitedNotification(conn,
3155                     (ExtendedResult) response);
3156              }
3157            }
3158            else if (response instanceof LDAPResult)
3159            {
3160              final LDAPResult r = (LDAPResult) response;
3161              if (r.getResultCode() == ResultCode.SERVER_DOWN)
3162              {
3163                numDefunct++;
3164                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3165                     ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null);
3166                poolStatistics.incrementNumConnectionsClosedDefunct();
3167                Debug.debugConnectionPool(Level.WARNING, this, conn,
3168                     "Closing existing connection discovered to be invalid " +
3169                          "with result " + r + " during health check " +
3170                          "processing",
3171                     null);
3172                conn = handleDefunctConnection(conn);
3173                if (conn != null)
3174                {
3175                  examinedConnections.add(conn);
3176                }
3177                continue;
3178              }
3179            }
3180          }
3181          catch (final LDAPException le)
3182          {
3183            if (le.getResultCode() == ResultCode.TIMEOUT)
3184            {
3185              Debug.debugException(Level.FINEST, le);
3186            }
3187            else
3188            {
3189              Debug.debugException(le);
3190              numDefunct++;
3191              conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3192                   ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
3193                        StaticUtils.getExceptionMessage(le)), le);
3194              poolStatistics.incrementNumConnectionsClosedDefunct();
3195              Debug.debugConnectionPool(Level.WARNING, this, conn,
3196                   "Closing existing connection discovered to be invalid " +
3197                        "during health check processing",
3198                   le);
3199              conn = handleDefunctConnection(conn);
3200              if (conn != null)
3201              {
3202                examinedConnections.add(conn);
3203              }
3204              continue;
3205            }
3206          }
3207          catch (final Exception e)
3208          {
3209            Debug.debugException(e);
3210            numDefunct++;
3211            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3212                 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(
3213                      StaticUtils.getExceptionMessage(e)),
3214                 e);
3215            poolStatistics.incrementNumConnectionsClosedDefunct();
3216            Debug.debugConnectionPool(Level.SEVERE, this, conn,
3217                 "Closing existing connection discovered to be invalid " +
3218                      "with an unexpected exception type during health check " +
3219                      "processing",
3220                 e);
3221            conn = handleDefunctConnection(conn);
3222            if (conn != null)
3223            {
3224              examinedConnections.add(conn);
3225            }
3226            continue;
3227          }
3228          finally
3229          {
3230            if (previousTimeout != Integer.MIN_VALUE)
3231            {
3232              try
3233              {
3234                if (s != null)
3235                {
3236                  InternalSDKHelper.setSoTimeout(conn, previousTimeout);
3237                }
3238              }
3239              catch (final Exception e)
3240              {
3241                Debug.debugException(e);
3242                numDefunct++;
3243                conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT,
3244                     null, e);
3245                poolStatistics.incrementNumConnectionsClosedDefunct();
3246                Debug.debugConnectionPool(Level.SEVERE, this, conn,
3247                     "Closing existing connection during health check " +
3248                          "processing because an error occurred while " +
3249                          "attempting to set the SO_TIMEOUT",
3250                     e);
3251                conn = handleDefunctConnection(conn);
3252                if (conn != null)
3253                {
3254                  examinedConnections.add(conn);
3255                }
3256                continue;
3257              }
3258            }
3259          }
3260        }
3261
3262        try
3263        {
3264          hc.ensureConnectionValidForContinuedUse(conn);
3265          if (availableConnections.offer(conn))
3266          {
3267            examinedConnections.add(conn);
3268          }
3269          else
3270          {
3271            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3272                                   null, null);
3273            poolStatistics.incrementNumConnectionsClosedUnneeded();
3274            Debug.debugConnectionPool(Level.INFO, this, conn,
3275                 "Closing existing connection that passed health check " +
3276                      "processing because the pool is already full",
3277                 null);
3278            conn.terminate(null);
3279          }
3280        }
3281        catch (final Exception e)
3282        {
3283          Debug.debugException(e);
3284          numDefunct++;
3285          poolStatistics.incrementNumConnectionsClosedDefunct();
3286          Debug.debugConnectionPool(Level.WARNING, this, conn,
3287               "Closing existing connection that failed health check " +
3288                    "processing",
3289               e);
3290          conn = handleDefunctConnection(conn);
3291          if (conn != null)
3292          {
3293            examinedConnections.add(conn);
3294          }
3295        }
3296      }
3297    }
3298
3299    if (checkMinConnectionGoal)
3300    {
3301      try
3302      {
3303        final int neededConnections =
3304             minConnectionGoal - availableConnections.size();
3305        for (int i=0; i < neededConnections; i++)
3306        {
3307          final LDAPConnection conn = createConnection(hc);
3308          if (! availableConnections.offer(conn))
3309          {
3310            conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED,
3311                                   null, null);
3312            poolStatistics.incrementNumConnectionsClosedUnneeded();
3313            Debug.debugConnectionPool(Level.INFO, this, conn,
3314                 "Closing a new connection that was created during health " +
3315                      "check processing in achieve the minimum connection " +
3316                      "goal, but the pool had already become full after the " +
3317                      "connection was created",
3318                 null);
3319            conn.terminate(null);
3320            break;
3321          }
3322        }
3323      }
3324      catch (final Exception e)
3325      {
3326        Debug.debugException(e);
3327      }
3328    }
3329
3330    return new LDAPConnectionPoolHealthCheckResult(numExamined, numExpired,
3331         numDefunct);
3332  }
3333
3334
3335
3336  /**
3337   * {@inheritDoc}
3338   */
3339  @Override()
3340  public int getCurrentAvailableConnections()
3341  {
3342    return availableConnections.size();
3343  }
3344
3345
3346
3347  /**
3348   * {@inheritDoc}
3349   */
3350  @Override()
3351  public int getMaximumAvailableConnections()
3352  {
3353    return numConnections;
3354  }
3355
3356
3357
3358  /**
3359   * Retrieves the goal for the minimum number of available connections that the
3360   * pool should try to maintain for immediate use.  If this goal is greater
3361   * than zero, then the health checking process will attempt to create enough
3362   * new connections to achieve this goal.
3363   *
3364   * @return  The goal for the minimum number of available connections that the
3365   *          pool should try to maintain for immediate use, or zero if it will
3366   *          not try to maintain a minimum number of available connections.
3367   */
3368  public int getMinimumAvailableConnectionGoal()
3369  {
3370    return minConnectionGoal;
3371  }
3372
3373
3374
3375  /**
3376   * Specifies the goal for the minimum number of available connections that the
3377   * pool should try to maintain for immediate use.  If this goal is greater
3378   * than zero, then the health checking process will attempt to create enough
3379   * new connections to achieve this goal.
3380   *
3381   * @param  goal  The goal for the minimum number of available connections that
3382   *               the pool should try to maintain for immediate use.  A value
3383   *               less than or equal to zero indicates that the pool should not
3384   *               try to maintain a minimum number of available connections.
3385   */
3386  public void setMinimumAvailableConnectionGoal(final int goal)
3387  {
3388    if (goal > numConnections)
3389    {
3390      minConnectionGoal = numConnections;
3391    }
3392    else if (goal > 0)
3393    {
3394      minConnectionGoal = goal;
3395    }
3396    else
3397    {
3398      minConnectionGoal = 0;
3399    }
3400  }
3401
3402
3403
3404  /**
3405   * {@inheritDoc}
3406   */
3407  @Override()
3408  @NotNull()
3409  public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
3410  {
3411    return poolStatistics;
3412  }
3413
3414
3415
3416  /**
3417   * Attempts to reduce the number of connections available for use in the pool.
3418   * Note that this will be a best-effort attempt to reach the desired number
3419   * of connections, as other threads interacting with the connection pool may
3420   * check out and/or release connections that cause the number of available
3421   * connections to fluctuate.
3422   *
3423   * @param  connectionsToRetain  The number of connections that should be
3424   *                              retained for use in the connection pool.
3425   */
3426  public void shrinkPool(final int connectionsToRetain)
3427  {
3428    while (availableConnections.size() > connectionsToRetain)
3429    {
3430      final LDAPConnection conn;
3431      try
3432      {
3433        conn = getConnection();
3434      }
3435      catch (final LDAPException le)
3436      {
3437        return;
3438      }
3439
3440      if (availableConnections.size() >= connectionsToRetain)
3441      {
3442        discardConnection(conn);
3443      }
3444      else
3445      {
3446        releaseConnection(conn);
3447        return;
3448      }
3449    }
3450  }
3451
3452
3453
3454  /**
3455   * Closes this connection pool in the event that it becomes unreferenced.
3456   *
3457   * @throws  Throwable  If an unexpected problem occurs.
3458   */
3459  @Override()
3460  protected void finalize()
3461            throws Throwable
3462  {
3463    super.finalize();
3464
3465    close();
3466  }
3467
3468
3469
3470  /**
3471   * {@inheritDoc}
3472   */
3473  @Override()
3474  public void toString(@NotNull final StringBuilder buffer)
3475  {
3476    buffer.append("LDAPConnectionPool(");
3477
3478    final String name = connectionPoolName;
3479    if (name != null)
3480    {
3481      buffer.append("name='");
3482      buffer.append(name);
3483      buffer.append("', ");
3484    }
3485
3486    buffer.append("serverSet=");
3487    serverSet.toString(buffer);
3488    buffer.append(", maxConnections=");
3489    buffer.append(numConnections);
3490    buffer.append(')');
3491  }
3492}