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