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