001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.net.Socket; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.EnumSet; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032import java.util.logging.Level; 033import java.util.concurrent.LinkedBlockingQueue; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.atomic.AtomicInteger; 036import java.util.concurrent.atomic.AtomicReference; 037 038import com.unboundid.ldap.protocol.LDAPResponse; 039import com.unboundid.ldap.sdk.schema.Schema; 040import com.unboundid.util.ObjectPair; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043 044import static com.unboundid.ldap.sdk.LDAPMessages.*; 045import static com.unboundid.util.Debug.*; 046import static com.unboundid.util.StaticUtils.*; 047import 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) 150public 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 /** 162 * The name of the connection property that may be used to indicate that a 163 * particular connection should have a different maximum connection age than 164 * the default for this pool. 165 */ 166 static final String ATTACHMENT_NAME_MAX_CONNECTION_AGE = 167 LDAPConnectionPool.class.getName() + ".maxConnectionAge"; 168 169 170 171 // A counter used to keep track of the number of times that the pool failed to 172 // replace a defunct connection. It may also be initialized to the difference 173 // between the initial and maximum number of connections that should be 174 // included in the pool. 175 private final AtomicInteger failedReplaceCount; 176 177 // The types of operations that should be retried if they fail in a manner 178 // that may be the result of a connection that is no longer valid. 179 private final AtomicReference<Set<OperationType>> retryOperationTypes; 180 181 // Indicates whether this connection pool has been closed. 182 private volatile boolean closed; 183 184 // Indicates whether to create a new connection if necessary rather than 185 // waiting for a connection to become available. 186 private boolean createIfNecessary; 187 188 // Indicates whether to check the connection age when releasing a connection 189 // back to the pool. 190 private volatile boolean checkConnectionAgeOnRelease; 191 192 // Indicates whether health check processing for connections in synchronous 193 // mode should include attempting to read with a very short timeout to attempt 194 // to detect closures and unsolicited notifications in a more timely manner. 195 private volatile boolean trySynchronousReadDuringHealthCheck; 196 197 // The bind request to use to perform authentication whenever a new connection 198 // is established. 199 private final BindRequest bindRequest; 200 201 // The number of connections to be held in this pool. 202 private final int numConnections; 203 204 // The health check implementation that should be used for this connection 205 // pool. 206 private LDAPConnectionPoolHealthCheck healthCheck; 207 208 // The thread that will be used to perform periodic background health checks 209 // for this connection pool. 210 private final LDAPConnectionPoolHealthCheckThread healthCheckThread; 211 212 // The statistics for this connection pool. 213 private final LDAPConnectionPoolStatistics poolStatistics; 214 215 // The set of connections that are currently available for use. 216 private final LinkedBlockingQueue<LDAPConnection> availableConnections; 217 218 // The length of time in milliseconds between periodic health checks against 219 // the available connections in this pool. 220 private volatile long healthCheckInterval; 221 222 // The time that the last expired connection was closed. 223 private volatile long lastExpiredDisconnectTime; 224 225 // The maximum length of time in milliseconds that a connection should be 226 // allowed to be established before terminating and re-establishing the 227 // connection. 228 private volatile long maxConnectionAge; 229 230 // The maximum connection age that should be used for connections created to 231 // replace connections that are released as defunct. 232 private volatile Long maxDefunctReplacementConnectionAge; 233 234 // The maximum length of time in milliseconds to wait for a connection to be 235 // available. 236 private long maxWaitTime; 237 238 // The minimum length of time in milliseconds that must pass between 239 // disconnects of connections that have exceeded the maximum connection age. 240 private volatile long minDisconnectInterval; 241 242 // The schema that should be shared for connections in this pool, along with 243 // its expiration time. 244 private volatile ObjectPair<Long,Schema> pooledSchema; 245 246 // The post-connect processor for this connection pool, if any. 247 private final PostConnectProcessor postConnectProcessor; 248 249 // The server set to use for establishing connections for use by this pool. 250 private final ServerSet serverSet; 251 252 // The user-friendly name assigned to this connection pool. 253 private String connectionPoolName; 254 255 256 257 258 /** 259 * Creates a new LDAP connection pool with up to the specified number of 260 * connections, created as clones of the provided connection. Initially, only 261 * the provided connection will be included in the pool, but additional 262 * connections will be created as needed until the pool has reached its full 263 * capacity, at which point the create if necessary and max wait time settings 264 * will be used to determine how to behave if a connection is requested but 265 * none are available. 266 * 267 * @param connection The connection to use to provide the template for 268 * the other connections to be created. This 269 * connection will be included in the pool. It must 270 * not be {@code null}, and it must be established to 271 * the target server. It does not necessarily need to 272 * be authenticated if all connections in the pool are 273 * to be unauthenticated. 274 * @param numConnections The total number of connections that should be 275 * created in the pool. It must be greater than or 276 * equal to one. 277 * 278 * @throws LDAPException If the provided connection cannot be used to 279 * initialize the pool, or if a problem occurs while 280 * attempting to establish any of the connections. If 281 * this is thrown, then all connections associated 282 * with the pool (including the one provided as an 283 * argument) will be closed. 284 */ 285 public LDAPConnectionPool(final LDAPConnection connection, 286 final int numConnections) 287 throws LDAPException 288 { 289 this(connection, 1, numConnections, null); 290 } 291 292 293 294 /** 295 * Creates a new LDAP connection pool with the specified number of 296 * connections, created as clones of the provided connection. 297 * 298 * @param connection The connection to use to provide the template 299 * for the other connections to be created. This 300 * connection will be included in the pool. It 301 * must not be {@code null}, and it must be 302 * established to the target server. It does not 303 * necessarily need to be authenticated if all 304 * connections in the pool are to be 305 * unauthenticated. 306 * @param initialConnections The number of connections to initially 307 * establish when the pool is created. It must be 308 * greater than or equal to one. 309 * @param maxConnections The maximum number of connections that should 310 * be maintained in the pool. It must be greater 311 * than or equal to the initial number of 312 * connections. 313 * 314 * @throws LDAPException If the provided connection cannot be used to 315 * initialize the pool, or if a problem occurs while 316 * attempting to establish any of the connections. If 317 * this is thrown, then all connections associated 318 * with the pool (including the one provided as an 319 * argument) will be closed. 320 */ 321 public LDAPConnectionPool(final LDAPConnection connection, 322 final int initialConnections, 323 final int maxConnections) 324 throws LDAPException 325 { 326 this(connection, initialConnections, maxConnections, null); 327 } 328 329 330 331 /** 332 * Creates a new LDAP connection pool with the specified number of 333 * connections, created as clones of the provided connection. 334 * 335 * @param connection The connection to use to provide the template 336 * for the other connections to be created. 337 * This connection will be included in the pool. 338 * It must not be {@code null}, and it must be 339 * established to the target server. It does 340 * not necessarily need to be authenticated if 341 * all connections in the pool are to be 342 * unauthenticated. 343 * @param initialConnections The number of connections to initially 344 * establish when the pool is created. It must 345 * be greater than or equal to one. 346 * @param maxConnections The maximum number of connections that should 347 * be maintained in the pool. It must be 348 * greater than or equal to the initial number 349 * of connections. 350 * @param postConnectProcessor A processor that should be used to perform 351 * any post-connect processing for connections 352 * in this pool. It may be {@code null} if no 353 * special processing is needed. Note that this 354 * processing will not be invoked on the 355 * provided connection that will be used as the 356 * first connection in the pool. 357 * 358 * @throws LDAPException If the provided connection cannot be used to 359 * initialize the pool, or if a problem occurs while 360 * attempting to establish any of the connections. If 361 * this is thrown, then all connections associated 362 * with the pool (including the one provided as an 363 * argument) will be closed. 364 */ 365 public LDAPConnectionPool(final LDAPConnection connection, 366 final int initialConnections, 367 final int maxConnections, 368 final PostConnectProcessor postConnectProcessor) 369 throws LDAPException 370 { 371 this(connection, initialConnections, maxConnections, postConnectProcessor, 372 true); 373 } 374 375 376 377 /** 378 * Creates a new LDAP connection pool with the specified number of 379 * connections, created as clones of the provided connection. 380 * 381 * @param connection The connection to use to provide the 382 * template for the other connections to be 383 * created. This connection will be included 384 * in the pool. It must not be {@code null}, 385 * and it must be established to the target 386 * server. It does not necessarily need to be 387 * authenticated if all connections in the pool 388 * are to be unauthenticated. 389 * @param initialConnections The number of connections to initially 390 * establish when the pool is created. It must 391 * be greater than or equal to one. 392 * @param maxConnections The maximum number of connections that 393 * should be maintained in the pool. It must 394 * be greater than or equal to the initial 395 * number of connections. 396 * @param postConnectProcessor A processor that should be used to perform 397 * any post-connect processing for connections 398 * in this pool. It may be {@code null} if no 399 * special processing is needed. Note that 400 * this processing will not be invoked on the 401 * provided connection that will be used as the 402 * first connection in the pool. 403 * @param throwOnConnectFailure If an exception should be thrown if a 404 * problem is encountered while attempting to 405 * create the specified initial number of 406 * connections. If {@code true}, then the 407 * attempt to create the pool will fail.if any 408 * connection cannot be established. If 409 * {@code false}, then the pool will be created 410 * but may have fewer than the initial number 411 * of connections (or possibly no connections). 412 * 413 * @throws LDAPException If the provided connection cannot be used to 414 * initialize the pool, or if a problem occurs while 415 * attempting to establish any of the connections. If 416 * this is thrown, then all connections associated 417 * with the pool (including the one provided as an 418 * argument) will be closed. 419 */ 420 public LDAPConnectionPool(final LDAPConnection connection, 421 final int initialConnections, 422 final int maxConnections, 423 final PostConnectProcessor postConnectProcessor, 424 final boolean throwOnConnectFailure) 425 throws LDAPException 426 { 427 this(connection, initialConnections, maxConnections, 1, 428 postConnectProcessor, throwOnConnectFailure); 429 } 430 431 432 433 /** 434 * Creates a new LDAP connection pool with the specified number of 435 * connections, created as clones of the provided connection. 436 * 437 * @param connection The connection to use to provide the 438 * template for the other connections to be 439 * created. This connection will be included 440 * in the pool. It must not be {@code null}, 441 * and it must be established to the target 442 * server. It does not necessarily need to be 443 * authenticated if all connections in the pool 444 * are to be unauthenticated. 445 * @param initialConnections The number of connections to initially 446 * establish when the pool is created. It must 447 * be greater than or equal to one. 448 * @param maxConnections The maximum number of connections that 449 * should be maintained in the pool. It must 450 * be greater than or equal to the initial 451 * number of connections. 452 * @param initialConnectThreads The number of concurrent threads to use to 453 * establish the initial set of connections. 454 * A value greater than one indicates that the 455 * attempt to establish connections should be 456 * parallelized. 457 * @param postConnectProcessor A processor that should be used to perform 458 * any post-connect processing for connections 459 * in this pool. It may be {@code null} if no 460 * special processing is needed. Note that 461 * this processing will not be invoked on the 462 * provided connection that will be used as the 463 * first connection in the pool. 464 * @param throwOnConnectFailure If an exception should be thrown if a 465 * problem is encountered while attempting to 466 * create the specified initial number of 467 * connections. If {@code true}, then the 468 * attempt to create the pool will fail.if any 469 * connection cannot be established. If 470 * {@code false}, then the pool will be created 471 * but may have fewer than the initial number 472 * of connections (or possibly no connections). 473 * 474 * @throws LDAPException If the provided connection cannot be used to 475 * initialize the pool, or if a problem occurs while 476 * attempting to establish any of the connections. If 477 * this is thrown, then all connections associated 478 * with the pool (including the one provided as an 479 * argument) will be closed. 480 */ 481 public LDAPConnectionPool(final LDAPConnection connection, 482 final int initialConnections, 483 final int maxConnections, 484 final int initialConnectThreads, 485 final PostConnectProcessor postConnectProcessor, 486 final boolean throwOnConnectFailure) 487 throws LDAPException 488 { 489 this(connection, initialConnections, maxConnections, initialConnectThreads, 490 postConnectProcessor, throwOnConnectFailure, null); 491 } 492 493 494 495 /** 496 * Creates a new LDAP connection pool with the specified number of 497 * connections, created as clones of the provided connection. 498 * 499 * @param connection The connection to use to provide the 500 * template for the other connections to be 501 * created. This connection will be included 502 * in the pool. It must not be {@code null}, 503 * and it must be established to the target 504 * server. It does not necessarily need to be 505 * authenticated if all connections in the pool 506 * are to be unauthenticated. 507 * @param initialConnections The number of connections to initially 508 * establish when the pool is created. It must 509 * be greater than or equal to one. 510 * @param maxConnections The maximum number of connections that 511 * should be maintained in the pool. It must 512 * be greater than or equal to the initial 513 * number of connections. 514 * @param initialConnectThreads The number of concurrent threads to use to 515 * establish the initial set of connections. 516 * A value greater than one indicates that the 517 * attempt to establish connections should be 518 * parallelized. 519 * @param postConnectProcessor A processor that should be used to perform 520 * any post-connect processing for connections 521 * in this pool. It may be {@code null} if no 522 * special processing is needed. Note that 523 * this processing will not be invoked on the 524 * provided connection that will be used as the 525 * first connection in the pool. 526 * @param throwOnConnectFailure If an exception should be thrown if a 527 * problem is encountered while attempting to 528 * create the specified initial number of 529 * connections. If {@code true}, then the 530 * attempt to create the pool will fail.if any 531 * connection cannot be established. If 532 * {@code false}, then the pool will be created 533 * but may have fewer than the initial number 534 * of connections (or possibly no connections). 535 * @param healthCheck The health check that should be used for 536 * connections in this pool. It may be 537 * {@code null} if the default health check 538 * should be used. 539 * 540 * @throws LDAPException If the provided connection cannot be used to 541 * initialize the pool, or if a problem occurs while 542 * attempting to establish any of the connections. If 543 * this is thrown, then all connections associated 544 * with the pool (including the one provided as an 545 * argument) will be closed. 546 */ 547 public LDAPConnectionPool(final LDAPConnection connection, 548 final int initialConnections, 549 final int maxConnections, 550 final int initialConnectThreads, 551 final PostConnectProcessor postConnectProcessor, 552 final boolean throwOnConnectFailure, 553 final LDAPConnectionPoolHealthCheck healthCheck) 554 throws LDAPException 555 { 556 ensureNotNull(connection); 557 ensureTrue(initialConnections >= 1, 558 "LDAPConnectionPool.initialConnections must be at least 1."); 559 ensureTrue(maxConnections >= initialConnections, 560 "LDAPConnectionPool.initialConnections must not be greater " + 561 "than maxConnections."); 562 563 this.postConnectProcessor = postConnectProcessor; 564 565 trySynchronousReadDuringHealthCheck = true; 566 healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; 567 poolStatistics = new LDAPConnectionPoolStatistics(this); 568 pooledSchema = null; 569 connectionPoolName = null; 570 retryOperationTypes = new AtomicReference<Set<OperationType>>( 571 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 572 numConnections = maxConnections; 573 availableConnections = 574 new LinkedBlockingQueue<LDAPConnection>(numConnections); 575 576 if (! connection.isConnected()) 577 { 578 throw new LDAPException(ResultCode.PARAM_ERROR, 579 ERR_POOL_CONN_NOT_ESTABLISHED.get()); 580 } 581 582 if (healthCheck == null) 583 { 584 this.healthCheck = new LDAPConnectionPoolHealthCheck(); 585 } 586 else 587 { 588 this.healthCheck = healthCheck; 589 } 590 591 592 serverSet = new SingleServerSet(connection.getConnectedAddress(), 593 connection.getConnectedPort(), 594 connection.getLastUsedSocketFactory(), 595 connection.getConnectionOptions()); 596 bindRequest = connection.getLastBindRequest(); 597 598 final LDAPConnectionOptions opts = connection.getConnectionOptions(); 599 if (opts.usePooledSchema()) 600 { 601 try 602 { 603 final Schema schema = connection.getSchema(); 604 if (schema != null) 605 { 606 connection.setCachedSchema(schema); 607 608 final long currentTime = System.currentTimeMillis(); 609 final long timeout = opts.getPooledSchemaTimeoutMillis(); 610 if ((timeout <= 0L) || (timeout+currentTime <= 0L)) 611 { 612 pooledSchema = new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema); 613 } 614 else 615 { 616 pooledSchema = 617 new ObjectPair<Long,Schema>(timeout+currentTime, schema); 618 } 619 } 620 } 621 catch (final Exception e) 622 { 623 debugException(e); 624 } 625 } 626 627 final List<LDAPConnection> connList; 628 if (initialConnectThreads > 1) 629 { 630 connList = Collections.synchronizedList( 631 new ArrayList<LDAPConnection>(initialConnections)); 632 final ParallelPoolConnector connector = new ParallelPoolConnector(this, 633 connList, initialConnections, initialConnectThreads, 634 throwOnConnectFailure); 635 connector.establishConnections(); 636 } 637 else 638 { 639 connList = new ArrayList<LDAPConnection>(initialConnections); 640 connection.setConnectionName(null); 641 connection.setConnectionPool(this); 642 connList.add(connection); 643 for (int i=1; i < initialConnections; i++) 644 { 645 try 646 { 647 connList.add(createConnection()); 648 } 649 catch (LDAPException le) 650 { 651 debugException(le); 652 653 if (throwOnConnectFailure) 654 { 655 for (final LDAPConnection c : connList) 656 { 657 try 658 { 659 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, 660 le); 661 c.terminate(null); 662 } 663 catch (Exception e) 664 { 665 debugException(e); 666 } 667 } 668 669 throw le; 670 } 671 } 672 } 673 } 674 675 availableConnections.addAll(connList); 676 677 failedReplaceCount = 678 new AtomicInteger(maxConnections - availableConnections.size()); 679 createIfNecessary = true; 680 checkConnectionAgeOnRelease = false; 681 maxConnectionAge = 0L; 682 maxDefunctReplacementConnectionAge = null; 683 minDisconnectInterval = 0L; 684 lastExpiredDisconnectTime = 0L; 685 maxWaitTime = 5000L; 686 closed = false; 687 688 healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); 689 healthCheckThread.start(); 690 } 691 692 693 694 /** 695 * Creates a new LDAP connection pool with the specified number of 696 * connections, created using the provided server set. Initially, only 697 * one will be created and included in the pool, but additional connections 698 * will be created as needed until the pool has reached its full capacity, at 699 * which point the create if necessary and max wait time settings will be used 700 * to determine how to behave if a connection is requested but none are 701 * available. 702 * 703 * @param serverSet The server set to use to create the connections. 704 * It is acceptable for the server set to create the 705 * connections across multiple servers. 706 * @param bindRequest The bind request to use to authenticate the 707 * connections that are established. It may be 708 * {@code null} if no authentication should be 709 * performed on the connections. 710 * @param numConnections The total number of connections that should be 711 * created in the pool. It must be greater than or 712 * equal to one. 713 * 714 * @throws LDAPException If a problem occurs while attempting to establish 715 * any of the connections. If this is thrown, then 716 * all connections associated with the pool will be 717 * closed. 718 */ 719 public LDAPConnectionPool(final ServerSet serverSet, 720 final BindRequest bindRequest, 721 final int numConnections) 722 throws LDAPException 723 { 724 this(serverSet, bindRequest, 1, numConnections, null); 725 } 726 727 728 729 /** 730 * Creates a new LDAP connection pool with the specified number of 731 * connections, created using the provided server set. 732 * 733 * @param serverSet The server set to use to create the 734 * connections. It is acceptable for the server 735 * set to create the connections across multiple 736 * servers. 737 * @param bindRequest The bind request to use to authenticate the 738 * connections that are established. It may be 739 * {@code null} if no authentication should be 740 * performed on the connections. 741 * @param initialConnections The number of connections to initially 742 * establish when the pool is created. It must be 743 * greater than or equal to zero. 744 * @param maxConnections The maximum number of connections that should 745 * be maintained in the pool. It must be greater 746 * than or equal to the initial number of 747 * connections, and must not be zero. 748 * 749 * @throws LDAPException If a problem occurs while attempting to establish 750 * any of the connections. If this is thrown, then 751 * all connections associated with the pool will be 752 * closed. 753 */ 754 public LDAPConnectionPool(final ServerSet serverSet, 755 final BindRequest bindRequest, 756 final int initialConnections, 757 final int maxConnections) 758 throws LDAPException 759 { 760 this(serverSet, bindRequest, initialConnections, maxConnections, null); 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 server 771 * set to create the connections across multiple 772 * 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 should 781 * be maintained in the pool. It must be 782 * greater than or equal to the initial number 783 * of connections, and must not be zero. 784 * @param postConnectProcessor A processor that should be used to perform 785 * any post-connect processing for connections 786 * in this pool. It may be {@code null} if no 787 * special processing is needed. 788 * 789 * @throws LDAPException If a problem occurs while attempting to establish 790 * any of the connections. If this is thrown, then 791 * all connections associated with the pool will be 792 * closed. 793 */ 794 public LDAPConnectionPool(final ServerSet serverSet, 795 final BindRequest bindRequest, 796 final int initialConnections, 797 final int maxConnections, 798 final PostConnectProcessor postConnectProcessor) 799 throws LDAPException 800 { 801 this(serverSet, bindRequest, initialConnections, maxConnections, 802 postConnectProcessor, true); 803 } 804 805 806 807 /** 808 * Creates a new LDAP connection pool with the specified number of 809 * connections, created using the provided server set. 810 * 811 * @param serverSet The server set to use to create the 812 * connections. It is acceptable for the 813 * server set to create the connections across 814 * multiple servers. 815 * @param bindRequest The bind request to use to authenticate the 816 * connections that are established. It may be 817 * {@code null} if no authentication should be 818 * performed on the connections. 819 * @param initialConnections The number of connections to initially 820 * establish when the pool is created. It must 821 * be greater than or equal to zero. 822 * @param maxConnections The maximum number of connections that 823 * should be maintained in the pool. It must 824 * be greater than or equal to the initial 825 * number of connections, and must not be zero. 826 * @param postConnectProcessor A processor that should be used to perform 827 * any post-connect processing for connections 828 * in this pool. It may be {@code null} if no 829 * special processing is needed. 830 * @param throwOnConnectFailure If an exception should be thrown if a 831 * problem is encountered while attempting to 832 * create the specified initial number of 833 * connections. If {@code true}, then the 834 * attempt to create the pool will fail.if any 835 * connection cannot be established. If 836 * {@code false}, then the pool will be created 837 * but may have fewer than the initial number 838 * of connections (or possibly no connections). 839 * 840 * @throws LDAPException If a problem occurs while attempting to establish 841 * any of the connections and 842 * {@code throwOnConnectFailure} is true. If this is 843 * thrown, then all connections associated with the 844 * pool will be closed. 845 */ 846 public LDAPConnectionPool(final ServerSet serverSet, 847 final BindRequest bindRequest, 848 final int initialConnections, 849 final int maxConnections, 850 final PostConnectProcessor postConnectProcessor, 851 final boolean throwOnConnectFailure) 852 throws LDAPException 853 { 854 this(serverSet, bindRequest, initialConnections, maxConnections, 1, 855 postConnectProcessor, throwOnConnectFailure); 856 } 857 858 859 860 /** 861 * Creates a new LDAP connection pool with the specified number of 862 * connections, created using the provided server set. 863 * 864 * @param serverSet The server set to use to create the 865 * connections. It is acceptable for the 866 * server set to create the connections across 867 * multiple servers. 868 * @param bindRequest The bind request to use to authenticate the 869 * connections that are established. It may be 870 * {@code null} if no authentication should be 871 * performed on the connections. 872 * @param initialConnections The number of connections to initially 873 * establish when the pool is created. It must 874 * be greater than or equal to zero. 875 * @param maxConnections The maximum number of connections that 876 * should be maintained in the pool. It must 877 * be greater than or equal to the initial 878 * number of connections, and must not be zero. 879 * @param initialConnectThreads The number of concurrent threads to use to 880 * establish the initial set of connections. 881 * A value greater than one indicates that the 882 * attempt to establish connections should be 883 * parallelized. 884 * @param postConnectProcessor A processor that should be used to perform 885 * any post-connect processing for connections 886 * in this pool. It may be {@code null} if no 887 * special processing is needed. 888 * @param throwOnConnectFailure If an exception should be thrown if a 889 * problem is encountered while attempting to 890 * create the specified initial number of 891 * connections. If {@code true}, then the 892 * attempt to create the pool will fail.if any 893 * connection cannot be established. If 894 * {@code false}, then the pool will be created 895 * but may have fewer than the initial number 896 * of connections (or possibly no connections). 897 * 898 * @throws LDAPException If a problem occurs while attempting to establish 899 * any of the connections and 900 * {@code throwOnConnectFailure} is true. If this is 901 * thrown, then all connections associated with the 902 * pool will be closed. 903 */ 904 public LDAPConnectionPool(final ServerSet serverSet, 905 final BindRequest bindRequest, 906 final int initialConnections, 907 final int maxConnections, 908 final int initialConnectThreads, 909 final PostConnectProcessor postConnectProcessor, 910 final boolean throwOnConnectFailure) 911 throws LDAPException 912 { 913 this(serverSet, bindRequest, initialConnections, maxConnections, 914 initialConnectThreads, postConnectProcessor, throwOnConnectFailure, 915 null); 916 } 917 918 919 920 /** 921 * Creates a new LDAP connection pool with the specified number of 922 * connections, created using the provided server set. 923 * 924 * @param serverSet The server set to use to create the 925 * connections. It is acceptable for the 926 * server set to create the connections across 927 * multiple servers. 928 * @param bindRequest The bind request to use to authenticate the 929 * connections that are established. It may be 930 * {@code null} if no authentication should be 931 * performed on the connections. 932 * @param initialConnections The number of connections to initially 933 * establish when the pool is created. It must 934 * be greater than or equal to zero. 935 * @param maxConnections The maximum number of connections that 936 * should be maintained in the pool. It must 937 * be greater than or equal to the initial 938 * number of connections, and must not be zero. 939 * @param initialConnectThreads The number of concurrent threads to use to 940 * establish the initial set of connections. 941 * A value greater than one indicates that the 942 * attempt to establish connections should be 943 * parallelized. 944 * @param postConnectProcessor A processor that should be used to perform 945 * any post-connect processing for connections 946 * in this pool. It may be {@code null} if no 947 * special processing is needed. 948 * @param throwOnConnectFailure If an exception should be thrown if a 949 * problem is encountered while attempting to 950 * create the specified initial number of 951 * connections. If {@code true}, then the 952 * attempt to create the pool will fail.if any 953 * connection cannot be established. If 954 * {@code false}, then the pool will be created 955 * but may have fewer than the initial number 956 * of connections (or possibly no connections). 957 * @param healthCheck The health check that should be used for 958 * connections in this pool. It may be 959 * {@code null} if the default health check 960 * should be used. 961 * 962 * @throws LDAPException If a problem occurs while attempting to establish 963 * any of the connections and 964 * {@code throwOnConnectFailure} is true. If this is 965 * thrown, then all connections associated with the 966 * pool will be closed. 967 */ 968 public LDAPConnectionPool(final ServerSet serverSet, 969 final BindRequest bindRequest, 970 final int initialConnections, 971 final int maxConnections, 972 final int initialConnectThreads, 973 final PostConnectProcessor postConnectProcessor, 974 final boolean throwOnConnectFailure, 975 final LDAPConnectionPoolHealthCheck healthCheck) 976 throws LDAPException 977 { 978 ensureNotNull(serverSet); 979 ensureTrue(initialConnections >= 0, 980 "LDAPConnectionPool.initialConnections must be greater than " + 981 "or equal to 0."); 982 ensureTrue(maxConnections > 0, 983 "LDAPConnectionPool.maxConnections must be greater than 0."); 984 ensureTrue(maxConnections >= initialConnections, 985 "LDAPConnectionPool.initialConnections must not be greater " + 986 "than maxConnections."); 987 988 this.serverSet = serverSet; 989 this.bindRequest = bindRequest; 990 this.postConnectProcessor = postConnectProcessor; 991 992 trySynchronousReadDuringHealthCheck = false; 993 healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; 994 poolStatistics = new LDAPConnectionPoolStatistics(this); 995 pooledSchema = null; 996 connectionPoolName = null; 997 retryOperationTypes = new AtomicReference<Set<OperationType>>( 998 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 999 1000 if (healthCheck == null) 1001 { 1002 this.healthCheck = new LDAPConnectionPoolHealthCheck(); 1003 } 1004 else 1005 { 1006 this.healthCheck = healthCheck; 1007 } 1008 1009 final List<LDAPConnection> connList; 1010 if (initialConnectThreads > 1) 1011 { 1012 connList = Collections.synchronizedList( 1013 new ArrayList<LDAPConnection>(initialConnections)); 1014 final ParallelPoolConnector connector = new ParallelPoolConnector(this, 1015 connList, initialConnections, initialConnectThreads, 1016 throwOnConnectFailure); 1017 connector.establishConnections(); 1018 } 1019 else 1020 { 1021 connList = new ArrayList<LDAPConnection>(initialConnections); 1022 for (int i=0; i < initialConnections; i++) 1023 { 1024 try 1025 { 1026 connList.add(createConnection()); 1027 } 1028 catch (LDAPException le) 1029 { 1030 debugException(le); 1031 1032 if (throwOnConnectFailure) 1033 { 1034 for (final LDAPConnection c : connList) 1035 { 1036 try 1037 { 1038 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, 1039 le); 1040 c.terminate(null); 1041 } catch (Exception e) 1042 { 1043 debugException(e); 1044 } 1045 } 1046 1047 throw le; 1048 } 1049 } 1050 } 1051 } 1052 1053 numConnections = maxConnections; 1054 1055 availableConnections = 1056 new LinkedBlockingQueue<LDAPConnection>(numConnections); 1057 availableConnections.addAll(connList); 1058 1059 failedReplaceCount = 1060 new AtomicInteger(maxConnections - availableConnections.size()); 1061 createIfNecessary = true; 1062 checkConnectionAgeOnRelease = false; 1063 maxConnectionAge = 0L; 1064 maxDefunctReplacementConnectionAge = null; 1065 minDisconnectInterval = 0L; 1066 lastExpiredDisconnectTime = 0L; 1067 maxWaitTime = 5000L; 1068 closed = false; 1069 1070 healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); 1071 healthCheckThread.start(); 1072 } 1073 1074 1075 1076 /** 1077 * Creates a new LDAP connection for use in this pool. 1078 * 1079 * @return A new connection created for use in this pool. 1080 * 1081 * @throws LDAPException If a problem occurs while attempting to establish 1082 * the connection. If a connection had been created, 1083 * it will be closed. 1084 */ 1085 LDAPConnection createConnection() 1086 throws LDAPException 1087 { 1088 final LDAPConnection c = serverSet.getConnection(healthCheck); 1089 c.setConnectionPool(this); 1090 1091 // Auto-reconnect must be disabled for pooled connections, so turn it off 1092 // if the associated connection options have it enabled for some reason. 1093 LDAPConnectionOptions opts = c.getConnectionOptions(); 1094 if (opts.autoReconnect()) 1095 { 1096 opts = opts.duplicate(); 1097 opts.setAutoReconnect(false); 1098 c.setConnectionOptions(opts); 1099 } 1100 1101 if (postConnectProcessor != null) 1102 { 1103 try 1104 { 1105 postConnectProcessor.processPreAuthenticatedConnection(c); 1106 } 1107 catch (Exception e) 1108 { 1109 debugException(e); 1110 1111 try 1112 { 1113 poolStatistics.incrementNumFailedConnectionAttempts(); 1114 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); 1115 c.terminate(null); 1116 } 1117 catch (Exception e2) 1118 { 1119 debugException(e2); 1120 } 1121 1122 if (e instanceof LDAPException) 1123 { 1124 throw ((LDAPException) e); 1125 } 1126 else 1127 { 1128 throw new LDAPException(ResultCode.CONNECT_ERROR, 1129 ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e); 1130 } 1131 } 1132 } 1133 1134 try 1135 { 1136 if (bindRequest != null) 1137 { 1138 c.bind(bindRequest.duplicate()); 1139 } 1140 } 1141 catch (Exception e) 1142 { 1143 debugException(e); 1144 try 1145 { 1146 poolStatistics.incrementNumFailedConnectionAttempts(); 1147 c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e); 1148 c.terminate(null); 1149 } 1150 catch (Exception e2) 1151 { 1152 debugException(e2); 1153 } 1154 1155 if (e instanceof LDAPException) 1156 { 1157 throw ((LDAPException) e); 1158 } 1159 else 1160 { 1161 throw new LDAPException(ResultCode.CONNECT_ERROR, 1162 ERR_POOL_CONNECT_ERROR.get(getExceptionMessage(e)), e); 1163 } 1164 } 1165 1166 if (postConnectProcessor != null) 1167 { 1168 try 1169 { 1170 postConnectProcessor.processPostAuthenticatedConnection(c); 1171 } 1172 catch (Exception e) 1173 { 1174 debugException(e); 1175 try 1176 { 1177 poolStatistics.incrementNumFailedConnectionAttempts(); 1178 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); 1179 c.terminate(null); 1180 } 1181 catch (Exception e2) 1182 { 1183 debugException(e2); 1184 } 1185 1186 if (e instanceof LDAPException) 1187 { 1188 throw ((LDAPException) e); 1189 } 1190 else 1191 { 1192 throw new LDAPException(ResultCode.CONNECT_ERROR, 1193 ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e); 1194 } 1195 } 1196 } 1197 1198 if (opts.usePooledSchema()) 1199 { 1200 final long currentTime = System.currentTimeMillis(); 1201 if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst())) 1202 { 1203 try 1204 { 1205 final Schema schema = c.getSchema(); 1206 if (schema != null) 1207 { 1208 c.setCachedSchema(schema); 1209 1210 final long timeout = opts.getPooledSchemaTimeoutMillis(); 1211 if ((timeout <= 0L) || (currentTime + timeout <= 0L)) 1212 { 1213 pooledSchema = 1214 new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema); 1215 } 1216 else 1217 { 1218 pooledSchema = 1219 new ObjectPair<Long,Schema>((currentTime+timeout), schema); 1220 } 1221 } 1222 } 1223 catch (final Exception e) 1224 { 1225 debugException(e); 1226 1227 // There was a problem retrieving the schema from the server, but if 1228 // we have an earlier copy then we can assume it's still valid. 1229 if (pooledSchema != null) 1230 { 1231 c.setCachedSchema(pooledSchema.getSecond()); 1232 } 1233 } 1234 } 1235 else 1236 { 1237 c.setCachedSchema(pooledSchema.getSecond()); 1238 } 1239 } 1240 1241 c.setConnectionPoolName(connectionPoolName); 1242 poolStatistics.incrementNumSuccessfulConnectionAttempts(); 1243 1244 return c; 1245 } 1246 1247 1248 1249 /** 1250 * {@inheritDoc} 1251 */ 1252 @Override() 1253 public void close() 1254 { 1255 close(true, 1); 1256 } 1257 1258 1259 1260 /** 1261 * {@inheritDoc} 1262 */ 1263 @Override() 1264 public void close(final boolean unbind, final int numThreads) 1265 { 1266 closed = true; 1267 healthCheckThread.stopRunning(); 1268 1269 if (numThreads > 1) 1270 { 1271 final ArrayList<LDAPConnection> connList = 1272 new ArrayList<LDAPConnection>(availableConnections.size()); 1273 availableConnections.drainTo(connList); 1274 1275 if (! connList.isEmpty()) 1276 { 1277 final ParallelPoolCloser closer = 1278 new ParallelPoolCloser(connList, unbind, numThreads); 1279 closer.closeConnections(); 1280 } 1281 } 1282 else 1283 { 1284 while (true) 1285 { 1286 final LDAPConnection conn = availableConnections.poll(); 1287 if (conn == null) 1288 { 1289 return; 1290 } 1291 else 1292 { 1293 poolStatistics.incrementNumConnectionsClosedUnneeded(); 1294 conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null); 1295 if (unbind) 1296 { 1297 conn.terminate(null); 1298 } 1299 else 1300 { 1301 conn.setClosed(); 1302 } 1303 } 1304 } 1305 } 1306 } 1307 1308 1309 1310 /** 1311 * {@inheritDoc} 1312 */ 1313 @Override() 1314 public boolean isClosed() 1315 { 1316 return closed; 1317 } 1318 1319 1320 1321 /** 1322 * Processes a simple bind using a connection from this connection pool, and 1323 * then reverts that authentication by re-binding as the same user used to 1324 * authenticate new connections. If new connections are unauthenticated, then 1325 * the subsequent bind will be an anonymous simple bind. This method attempts 1326 * to ensure that processing the provided bind operation does not have a 1327 * lasting impact the authentication state of the connection used to process 1328 * it. 1329 * <BR><BR> 1330 * If the second bind attempt (the one used to restore the authentication 1331 * identity) fails, the connection will be closed as defunct so that a new 1332 * connection will be created to take its place. 1333 * 1334 * @param bindDN The bind DN for the simple bind request. 1335 * @param password The password for the simple bind request. 1336 * @param controls The optional set of controls for the simple bind request. 1337 * 1338 * @return The result of processing the provided bind operation. 1339 * 1340 * @throws LDAPException If the server rejects the bind request, or if a 1341 * problem occurs while sending the request or reading 1342 * the response. 1343 */ 1344 public BindResult bindAndRevertAuthentication(final String bindDN, 1345 final String password, 1346 final Control... controls) 1347 throws LDAPException 1348 { 1349 return bindAndRevertAuthentication( 1350 new SimpleBindRequest(bindDN, password, controls)); 1351 } 1352 1353 1354 1355 /** 1356 * Processes the provided bind request using a connection from this connection 1357 * pool, and then reverts that authentication by re-binding as the same user 1358 * used to authenticate new connections. If new connections are 1359 * unauthenticated, then the subsequent bind will be an anonymous simple bind. 1360 * This method attempts to ensure that processing the provided bind operation 1361 * does not have a lasting impact the authentication state of the connection 1362 * used to process it. 1363 * <BR><BR> 1364 * If the second bind attempt (the one used to restore the authentication 1365 * identity) fails, the connection will be closed as defunct so that a new 1366 * connection will be created to take its place. 1367 * 1368 * @param bindRequest The bind request to be processed. It must not be 1369 * {@code null}. 1370 * 1371 * @return The result of processing the provided bind operation. 1372 * 1373 * @throws LDAPException If the server rejects the bind request, or if a 1374 * problem occurs while sending the request or reading 1375 * the response. 1376 */ 1377 public BindResult bindAndRevertAuthentication(final BindRequest bindRequest) 1378 throws LDAPException 1379 { 1380 LDAPConnection conn = getConnection(); 1381 1382 try 1383 { 1384 final BindResult result = conn.bind(bindRequest); 1385 releaseAndReAuthenticateConnection(conn); 1386 return result; 1387 } 1388 catch (final Throwable t) 1389 { 1390 debugException(t); 1391 1392 if (t instanceof LDAPException) 1393 { 1394 final LDAPException le = (LDAPException) t; 1395 1396 boolean shouldThrow; 1397 try 1398 { 1399 healthCheck.ensureConnectionValidAfterException(conn, le); 1400 1401 // The above call will throw an exception if the connection doesn't 1402 // seem to be valid, so if we've gotten here then we should assume 1403 // that it is valid and we will pass the exception onto the client 1404 // without retrying the operation. 1405 releaseAndReAuthenticateConnection(conn); 1406 shouldThrow = true; 1407 } 1408 catch (final Exception e) 1409 { 1410 debugException(e); 1411 1412 // This implies that the connection is not valid. If the pool is 1413 // configured to re-try bind operations on a newly-established 1414 // connection, then that will be done later in this method. 1415 // Otherwise, release the connection as defunct and pass the bind 1416 // exception onto the client. 1417 if (! getOperationTypesToRetryDueToInvalidConnections().contains( 1418 OperationType.BIND)) 1419 { 1420 releaseDefunctConnection(conn); 1421 shouldThrow = true; 1422 } 1423 else 1424 { 1425 shouldThrow = false; 1426 } 1427 } 1428 1429 if (shouldThrow) 1430 { 1431 throw le; 1432 } 1433 } 1434 else 1435 { 1436 releaseDefunctConnection(conn); 1437 throw new LDAPException(ResultCode.LOCAL_ERROR, 1438 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t); 1439 } 1440 } 1441 1442 1443 // If we've gotten here, then the bind operation should be re-tried on a 1444 // newly-established connection. 1445 conn = replaceDefunctConnection(conn); 1446 1447 try 1448 { 1449 final BindResult result = conn.bind(bindRequest); 1450 releaseAndReAuthenticateConnection(conn); 1451 return result; 1452 } 1453 catch (final Throwable t) 1454 { 1455 debugException(t); 1456 1457 if (t instanceof LDAPException) 1458 { 1459 final LDAPException le = (LDAPException) t; 1460 1461 try 1462 { 1463 healthCheck.ensureConnectionValidAfterException(conn, le); 1464 releaseAndReAuthenticateConnection(conn); 1465 } 1466 catch (final Exception e) 1467 { 1468 debugException(e); 1469 releaseDefunctConnection(conn); 1470 } 1471 1472 throw le; 1473 } 1474 else 1475 { 1476 releaseDefunctConnection(conn); 1477 throw new LDAPException(ResultCode.LOCAL_ERROR, 1478 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t); 1479 } 1480 } 1481 } 1482 1483 1484 1485 /** 1486 * {@inheritDoc} 1487 */ 1488 @Override() 1489 public LDAPConnection getConnection() 1490 throws LDAPException 1491 { 1492 if (closed) 1493 { 1494 poolStatistics.incrementNumFailedCheckouts(); 1495 throw new LDAPException(ResultCode.CONNECT_ERROR, 1496 ERR_POOL_CLOSED.get()); 1497 } 1498 1499 LDAPConnection conn = availableConnections.poll(); 1500 if (conn != null) 1501 { 1502 if (conn.isConnected()) 1503 { 1504 try 1505 { 1506 healthCheck.ensureConnectionValidForCheckout(conn); 1507 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 1508 return conn; 1509 } 1510 catch (LDAPException le) 1511 { 1512 debugException(le); 1513 } 1514 } 1515 1516 handleDefunctConnection(conn); 1517 for (int i=0; i < numConnections; i++) 1518 { 1519 conn = availableConnections.poll(); 1520 if (conn == null) 1521 { 1522 break; 1523 } 1524 else if (conn.isConnected()) 1525 { 1526 try 1527 { 1528 healthCheck.ensureConnectionValidForCheckout(conn); 1529 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 1530 return conn; 1531 } 1532 catch (LDAPException le) 1533 { 1534 debugException(le); 1535 handleDefunctConnection(conn); 1536 } 1537 } 1538 else 1539 { 1540 handleDefunctConnection(conn); 1541 } 1542 } 1543 } 1544 1545 if (failedReplaceCount.get() > 0) 1546 { 1547 final int newReplaceCount = failedReplaceCount.getAndDecrement(); 1548 if (newReplaceCount > 0) 1549 { 1550 try 1551 { 1552 conn = createConnection(); 1553 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 1554 return conn; 1555 } 1556 catch (LDAPException le) 1557 { 1558 debugException(le); 1559 failedReplaceCount.incrementAndGet(); 1560 poolStatistics.incrementNumFailedCheckouts(); 1561 throw le; 1562 } 1563 } 1564 else 1565 { 1566 failedReplaceCount.incrementAndGet(); 1567 poolStatistics.incrementNumFailedCheckouts(); 1568 throw new LDAPException(ResultCode.CONNECT_ERROR, 1569 ERR_POOL_NO_CONNECTIONS.get()); 1570 } 1571 } 1572 1573 if (maxWaitTime > 0) 1574 { 1575 try 1576 { 1577 conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS); 1578 if (conn != null) 1579 { 1580 try 1581 { 1582 healthCheck.ensureConnectionValidForCheckout(conn); 1583 poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting(); 1584 return conn; 1585 } 1586 catch (LDAPException le) 1587 { 1588 debugException(le); 1589 handleDefunctConnection(conn); 1590 } 1591 } 1592 } 1593 catch (InterruptedException ie) 1594 { 1595 debugException(ie); 1596 } 1597 } 1598 1599 if (createIfNecessary) 1600 { 1601 try 1602 { 1603 conn = createConnection(); 1604 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 1605 return conn; 1606 } 1607 catch (LDAPException le) 1608 { 1609 debugException(le); 1610 poolStatistics.incrementNumFailedCheckouts(); 1611 throw le; 1612 } 1613 } 1614 else 1615 { 1616 poolStatistics.incrementNumFailedCheckouts(); 1617 throw new LDAPException(ResultCode.CONNECT_ERROR, 1618 ERR_POOL_NO_CONNECTIONS.get()); 1619 } 1620 } 1621 1622 1623 1624 /** 1625 * Attempts to retrieve a connection from the pool that is established to the 1626 * specified server. Note that this method will only attempt to return an 1627 * existing connection that is currently available, and will not create a 1628 * connection or wait for any checked-out connections to be returned. 1629 * 1630 * @param host The address of the server to which the desired connection 1631 * should be established. This must not be {@code null}, and 1632 * this must exactly match the address provided for the initial 1633 * connection or the {@code ServerSet} used to create the pool. 1634 * @param port The port of the server to which the desired connection should 1635 * be established. 1636 * 1637 * @return A connection that is established to the specified server, or 1638 * {@code null} if there are no available connections established to 1639 * the specified server. 1640 */ 1641 public LDAPConnection getConnection(final String host, final int port) 1642 { 1643 if (closed) 1644 { 1645 poolStatistics.incrementNumFailedCheckouts(); 1646 return null; 1647 } 1648 1649 final HashSet<LDAPConnection> examinedConnections = 1650 new HashSet<LDAPConnection>(numConnections); 1651 while (true) 1652 { 1653 final LDAPConnection conn = availableConnections.poll(); 1654 if (conn == null) 1655 { 1656 poolStatistics.incrementNumFailedCheckouts(); 1657 return null; 1658 } 1659 1660 if (examinedConnections.contains(conn)) 1661 { 1662 availableConnections.offer(conn); 1663 poolStatistics.incrementNumFailedCheckouts(); 1664 return null; 1665 } 1666 1667 if (conn.getConnectedAddress().equals(host) && 1668 (port == conn.getConnectedPort())) 1669 { 1670 try 1671 { 1672 healthCheck.ensureConnectionValidForCheckout(conn); 1673 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 1674 return conn; 1675 } 1676 catch (final LDAPException le) 1677 { 1678 debugException(le); 1679 handleDefunctConnection(conn); 1680 continue; 1681 } 1682 } 1683 1684 if (availableConnections.offer(conn)) 1685 { 1686 examinedConnections.add(conn); 1687 } 1688 } 1689 } 1690 1691 1692 1693 /** 1694 * {@inheritDoc} 1695 */ 1696 @Override() 1697 public void releaseConnection(final LDAPConnection connection) 1698 { 1699 if (connection == null) 1700 { 1701 return; 1702 } 1703 1704 connection.setConnectionPoolName(connectionPoolName); 1705 if (checkConnectionAgeOnRelease && connectionIsExpired(connection)) 1706 { 1707 try 1708 { 1709 final LDAPConnection newConnection = createConnection(); 1710 if (availableConnections.offer(newConnection)) 1711 { 1712 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 1713 null, null); 1714 connection.terminate(null); 1715 poolStatistics.incrementNumConnectionsClosedExpired(); 1716 lastExpiredDisconnectTime = System.currentTimeMillis(); 1717 } 1718 else 1719 { 1720 newConnection.setDisconnectInfo( 1721 DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null); 1722 newConnection.terminate(null); 1723 poolStatistics.incrementNumConnectionsClosedUnneeded(); 1724 } 1725 } 1726 catch (final LDAPException le) 1727 { 1728 debugException(le); 1729 } 1730 return; 1731 } 1732 1733 try 1734 { 1735 healthCheck.ensureConnectionValidForRelease(connection); 1736 } 1737 catch (LDAPException le) 1738 { 1739 releaseDefunctConnection(connection); 1740 return; 1741 } 1742 1743 if (availableConnections.offer(connection)) 1744 { 1745 poolStatistics.incrementNumReleasedValid(); 1746 } 1747 else 1748 { 1749 // This means that the connection pool is full, which can happen if the 1750 // pool was empty when a request came in to retrieve a connection and 1751 // createIfNecessary was true. In this case, we'll just close the 1752 // connection since we don't need it any more. 1753 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 1754 null, null); 1755 poolStatistics.incrementNumConnectionsClosedUnneeded(); 1756 connection.terminate(null); 1757 return; 1758 } 1759 1760 if (closed) 1761 { 1762 close(); 1763 } 1764 } 1765 1766 1767 1768 /** 1769 * Indicates that the provided connection should be removed from the pool, 1770 * and that no new connection should be created to take its place. This may 1771 * be used to shrink the pool if such functionality is desired. 1772 * 1773 * @param connection The connection to be discarded. 1774 */ 1775 public void discardConnection(final LDAPConnection connection) 1776 { 1777 if (connection == null) 1778 { 1779 return; 1780 } 1781 1782 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 1783 null, null); 1784 connection.terminate(null); 1785 poolStatistics.incrementNumConnectionsClosedUnneeded(); 1786 1787 if (availableConnections.remainingCapacity() > 0) 1788 { 1789 final int newReplaceCount = failedReplaceCount.incrementAndGet(); 1790 if (newReplaceCount > numConnections) 1791 { 1792 failedReplaceCount.set(numConnections); 1793 } 1794 } 1795 } 1796 1797 1798 1799 /** 1800 * Performs a bind on the provided connection before releasing it back to the 1801 * pool, so that it will be authenticated as the same user as 1802 * newly-established connections. If newly-established connections are 1803 * unauthenticated, then this method will perform an anonymous simple bind to 1804 * ensure that the resulting connection is unauthenticated. 1805 * 1806 * Releases the provided connection back to this pool. 1807 * 1808 * @param connection The connection to be released back to the pool after 1809 * being re-authenticated. 1810 */ 1811 public void releaseAndReAuthenticateConnection( 1812 final LDAPConnection connection) 1813 { 1814 if (connection == null) 1815 { 1816 return; 1817 } 1818 1819 try 1820 { 1821 if (bindRequest == null) 1822 { 1823 connection.bind("", ""); 1824 } 1825 else 1826 { 1827 connection.bind(bindRequest); 1828 } 1829 1830 releaseConnection(connection); 1831 } 1832 catch (final Exception e) 1833 { 1834 debugException(e); 1835 releaseDefunctConnection(connection); 1836 } 1837 } 1838 1839 1840 1841 /** 1842 * {@inheritDoc} 1843 */ 1844 @Override() 1845 public void releaseDefunctConnection(final LDAPConnection connection) 1846 { 1847 if (connection == null) 1848 { 1849 return; 1850 } 1851 1852 connection.setConnectionPoolName(connectionPoolName); 1853 poolStatistics.incrementNumConnectionsClosedDefunct(); 1854 handleDefunctConnection(connection); 1855 } 1856 1857 1858 1859 /** 1860 * Performs the real work of terminating a defunct connection and replacing it 1861 * with a new connection if possible. 1862 * 1863 * @param connection The defunct connection to be replaced. 1864 * 1865 * @return The new connection created to take the place of the defunct 1866 * connection, or {@code null} if no new connection was created. 1867 * Note that if a connection is returned, it will have already been 1868 * made available and the caller must not rely on it being unused for 1869 * any other purpose. 1870 */ 1871 private LDAPConnection handleDefunctConnection( 1872 final LDAPConnection connection) 1873 { 1874 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1875 null); 1876 connection.terminate(null); 1877 1878 if (closed) 1879 { 1880 return null; 1881 } 1882 1883 if (createIfNecessary && (availableConnections.remainingCapacity() <= 0)) 1884 { 1885 return null; 1886 } 1887 1888 try 1889 { 1890 final LDAPConnection conn = createConnection(); 1891 if (maxDefunctReplacementConnectionAge != null) 1892 { 1893 // Only set the maximum age if there isn't one already set for the 1894 // connection (i.e., because it was defined by the server set). 1895 if (conn.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE) == null) 1896 { 1897 conn.setAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE, 1898 maxDefunctReplacementConnectionAge); 1899 } 1900 } 1901 1902 if (! availableConnections.offer(conn)) 1903 { 1904 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 1905 null, null); 1906 conn.terminate(null); 1907 return null; 1908 } 1909 1910 return conn; 1911 } 1912 catch (LDAPException le) 1913 { 1914 debugException(le); 1915 final int newReplaceCount = failedReplaceCount.incrementAndGet(); 1916 if (newReplaceCount > numConnections) 1917 { 1918 failedReplaceCount.set(numConnections); 1919 } 1920 return null; 1921 } 1922 } 1923 1924 1925 1926 /** 1927 * {@inheritDoc} 1928 */ 1929 @Override() 1930 public LDAPConnection replaceDefunctConnection( 1931 final LDAPConnection connection) 1932 throws LDAPException 1933 { 1934 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1935 null); 1936 connection.terminate(null); 1937 1938 if (closed) 1939 { 1940 throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); 1941 } 1942 1943 return createConnection(); 1944 } 1945 1946 1947 1948 /** 1949 * {@inheritDoc} 1950 */ 1951 @Override() 1952 public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections() 1953 { 1954 return retryOperationTypes.get(); 1955 } 1956 1957 1958 1959 /** 1960 * {@inheritDoc} 1961 */ 1962 @Override() 1963 public void setRetryFailedOperationsDueToInvalidConnections( 1964 final Set<OperationType> operationTypes) 1965 { 1966 if ((operationTypes == null) || operationTypes.isEmpty()) 1967 { 1968 retryOperationTypes.set( 1969 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 1970 } 1971 else 1972 { 1973 final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class); 1974 s.addAll(operationTypes); 1975 retryOperationTypes.set(Collections.unmodifiableSet(s)); 1976 } 1977 } 1978 1979 1980 1981 /** 1982 * Indicates whether the provided connection should be considered expired. 1983 * 1984 * @param connection The connection for which to make the determination. 1985 * 1986 * @return {@code true} if the provided connection should be considered 1987 * expired, or {@code false} if not. 1988 */ 1989 private boolean connectionIsExpired(final LDAPConnection connection) 1990 { 1991 // There may be a custom maximum connection age for the connection. If that 1992 // is the case, then use that custom max age rather than the pool-default 1993 // max age. 1994 final long maxAge; 1995 final Object maxAgeObj = 1996 connection.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE); 1997 if ((maxAgeObj != null) && (maxAgeObj instanceof Long)) 1998 { 1999 maxAge = (Long) maxAgeObj; 2000 } 2001 else 2002 { 2003 maxAge = maxConnectionAge; 2004 } 2005 2006 // If connection expiration is not enabled, then there is nothing to do. 2007 if (maxAge <= 0L) 2008 { 2009 return false; 2010 } 2011 2012 // If there is a minimum disconnect interval, then make sure that we have 2013 // not closed another expired connection too recently. 2014 final long currentTime = System.currentTimeMillis(); 2015 if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) 2016 { 2017 return false; 2018 } 2019 2020 // Get the age of the connection and see if it is expired. 2021 final long connectionAge = currentTime - connection.getConnectTime(); 2022 return (connectionAge > maxAge); 2023 } 2024 2025 2026 2027 /** 2028 * {@inheritDoc} 2029 */ 2030 @Override() 2031 public String getConnectionPoolName() 2032 { 2033 return connectionPoolName; 2034 } 2035 2036 2037 2038 /** 2039 * {@inheritDoc} 2040 */ 2041 @Override() 2042 public void setConnectionPoolName(final String connectionPoolName) 2043 { 2044 this.connectionPoolName = connectionPoolName; 2045 for (final LDAPConnection c : availableConnections) 2046 { 2047 c.setConnectionPoolName(connectionPoolName); 2048 } 2049 } 2050 2051 2052 2053 /** 2054 * Indicates whether the connection pool should create a new connection if one 2055 * is requested when there are none available. 2056 * 2057 * @return {@code true} if a new connection should be created if none are 2058 * available when a request is received, or {@code false} if an 2059 * exception should be thrown to indicate that no connection is 2060 * available. 2061 */ 2062 public boolean getCreateIfNecessary() 2063 { 2064 return createIfNecessary; 2065 } 2066 2067 2068 2069 /** 2070 * Specifies whether the connection pool should create a new connection if one 2071 * is requested when there are none available. 2072 * 2073 * @param createIfNecessary Specifies whether the connection pool should 2074 * create a new connection if one is requested when 2075 * there are none available. 2076 */ 2077 public void setCreateIfNecessary(final boolean createIfNecessary) 2078 { 2079 this.createIfNecessary = createIfNecessary; 2080 } 2081 2082 2083 2084 /** 2085 * Retrieves the maximum length of time in milliseconds to wait for a 2086 * connection to become available when trying to obtain a connection from the 2087 * pool. 2088 * 2089 * @return The maximum length of time in milliseconds to wait for a 2090 * connection to become available when trying to obtain a connection 2091 * from the pool, or zero to indicate that the pool should not block 2092 * at all if no connections are available and that it should either 2093 * create a new connection or throw an exception. 2094 */ 2095 public long getMaxWaitTimeMillis() 2096 { 2097 return maxWaitTime; 2098 } 2099 2100 2101 2102 /** 2103 * Specifies the maximum length of time in milliseconds to wait for a 2104 * connection to become available when trying to obtain a connection from the 2105 * pool. 2106 * 2107 * @param maxWaitTime The maximum length of time in milliseconds to wait for 2108 * a connection to become available when trying to obtain 2109 * a connection from the pool. A value of zero should be 2110 * used to indicate that the pool should not block at all 2111 * if no connections are available and that it should 2112 * either create a new connection or throw an exception. 2113 */ 2114 public void setMaxWaitTimeMillis(final long maxWaitTime) 2115 { 2116 if (maxWaitTime > 0L) 2117 { 2118 this.maxWaitTime = maxWaitTime; 2119 } 2120 else 2121 { 2122 this.maxWaitTime = 0L; 2123 } 2124 } 2125 2126 2127 2128 /** 2129 * Retrieves the maximum length of time in milliseconds that a connection in 2130 * this pool may be established before it is closed and replaced with another 2131 * connection. 2132 * 2133 * @return The maximum length of time in milliseconds that a connection in 2134 * this pool may be established before it is closed and replaced with 2135 * another connection, or {@code 0L} if no maximum age should be 2136 * enforced. 2137 */ 2138 public long getMaxConnectionAgeMillis() 2139 { 2140 return maxConnectionAge; 2141 } 2142 2143 2144 2145 /** 2146 * Specifies the maximum length of time in milliseconds that a connection in 2147 * this pool may be established before it should be closed and replaced with 2148 * another connection. 2149 * 2150 * @param maxConnectionAge The maximum length of time in milliseconds that a 2151 * connection in this pool may be established before 2152 * it should be closed and replaced with another 2153 * connection. A value of zero indicates that no 2154 * maximum age should be enforced. 2155 */ 2156 public void setMaxConnectionAgeMillis(final long maxConnectionAge) 2157 { 2158 if (maxConnectionAge > 0L) 2159 { 2160 this.maxConnectionAge = maxConnectionAge; 2161 } 2162 else 2163 { 2164 this.maxConnectionAge = 0L; 2165 } 2166 } 2167 2168 2169 2170 /** 2171 * Retrieves the maximum connection age that should be used for connections 2172 * that were created in order to replace defunct connections. It is possible 2173 * to define a custom maximum connection age for these connections to allow 2174 * them to be closed and re-established more quickly to allow for a 2175 * potentially quicker fail-back to a normal state. Note, that if this 2176 * capability is to be used, then the maximum age for these connections should 2177 * be long enough to allow the problematic server to become available again 2178 * under normal circumstances (e.g., it should be long enough for at least a 2179 * shutdown and restart of the server, plus some overhead for potentially 2180 * performing routine maintenance while the server is offline, or a chance for 2181 * an administrator to be made available that a server has gone down). 2182 * 2183 * @return The maximum connection age that should be used for connections 2184 * that were created in order to replace defunct connections, a value 2185 * of zero to indicate that no maximum age should be enforced, or 2186 * {@code null} if the value returned by the 2187 * {@link #getMaxConnectionAgeMillis()} method should be used. 2188 */ 2189 public Long getMaxDefunctReplacementConnectionAgeMillis() 2190 { 2191 return maxDefunctReplacementConnectionAge; 2192 } 2193 2194 2195 2196 /** 2197 * Specifies the maximum connection age that should be used for connections 2198 * that were created in order to replace defunct connections. It is possible 2199 * to define a custom maximum connection age for these connections to allow 2200 * them to be closed and re-established more quickly to allow for a 2201 * potentially quicker fail-back to a normal state. Note, that if this 2202 * capability is to be used, then the maximum age for these connections should 2203 * be long enough to allow the problematic server to become available again 2204 * under normal circumstances (e.g., it should be long enough for at least a 2205 * shutdown and restart of the server, plus some overhead for potentially 2206 * performing routine maintenance while the server is offline, or a chance for 2207 * an administrator to be made available that a server has gone down). 2208 * 2209 * @param maxDefunctReplacementConnectionAge The maximum connection age that 2210 * should be used for connections that were created in order to 2211 * replace defunct connections. It may be zero if no maximum age 2212 * should be enforced for such connections, or it may be 2213 * {@code null} if the value returned by the 2214 * {@link #getMaxConnectionAgeMillis()} method should be used. 2215 */ 2216 public void setMaxDefunctReplacementConnectionAgeMillis( 2217 final Long maxDefunctReplacementConnectionAge) 2218 { 2219 if (maxDefunctReplacementConnectionAge == null) 2220 { 2221 this.maxDefunctReplacementConnectionAge = null; 2222 } 2223 else if (maxDefunctReplacementConnectionAge > 0L) 2224 { 2225 this.maxDefunctReplacementConnectionAge = 2226 maxDefunctReplacementConnectionAge; 2227 } 2228 else 2229 { 2230 this.maxDefunctReplacementConnectionAge = 0L; 2231 } 2232 } 2233 2234 2235 2236 /** 2237 * Indicates whether to check the age of a connection against the configured 2238 * maximum connection age whenever it is released to the pool. By default, 2239 * connection age is evaluated in the background using the health check 2240 * thread, but it is also possible to configure the pool to additionally 2241 * examine the age of a connection when it is returned to the pool. 2242 * <BR><BR> 2243 * Performing connection age evaluation only in the background will ensure 2244 * that connections are only closed and re-established in a single-threaded 2245 * manner, which helps minimize the load against the target server, but only 2246 * checks connections that are not in use when the health check thread is 2247 * active. If the pool is configured to also evaluate the connection age when 2248 * connections are returned to the pool, then it may help ensure that the 2249 * maximum connection age is honored more strictly for all connections, but 2250 * in busy applications may lead to cases in which multiple connections are 2251 * closed and re-established simultaneously, which may increase load against 2252 * the directory server. The {@link #setMinDisconnectIntervalMillis(long)} 2253 * method may be used to help mitigate the potential performance impact of 2254 * closing and re-establishing multiple connections simultaneously. 2255 * 2256 * @return {@code true} if the connection pool should check connection age in 2257 * both the background health check thread and when connections are 2258 * released to the pool, or {@code false} if the connection age 2259 * should only be checked by the background health check thread. 2260 */ 2261 public boolean checkConnectionAgeOnRelease() 2262 { 2263 return checkConnectionAgeOnRelease; 2264 } 2265 2266 2267 2268 /** 2269 * Specifies whether to check the age of a connection against the configured 2270 * maximum connection age whenever it is released to the pool. By default, 2271 * connection age is evaluated in the background using the health check 2272 * thread, but it is also possible to configure the pool to additionally 2273 * examine the age of a connection when it is returned to the pool. 2274 * <BR><BR> 2275 * Performing connection age evaluation only in the background will ensure 2276 * that connections are only closed and re-established in a single-threaded 2277 * manner, which helps minimize the load against the target server, but only 2278 * checks connections that are not in use when the health check thread is 2279 * active. If the pool is configured to also evaluate the connection age when 2280 * connections are returned to the pool, then it may help ensure that the 2281 * maximum connection age is honored more strictly for all connections, but 2282 * in busy applications may lead to cases in which multiple connections are 2283 * closed and re-established simultaneously, which may increase load against 2284 * the directory server. The {@link #setMinDisconnectIntervalMillis(long)} 2285 * method may be used to help mitigate the potential performance impact of 2286 * closing and re-establishing multiple connections simultaneously. 2287 * 2288 * @param checkConnectionAgeOnRelease If {@code true}, this indicates that 2289 * the connection pool should check 2290 * connection age in both the background 2291 * health check thread and when 2292 * connections are released to the pool. 2293 * If {@code false}, this indicates that 2294 * the connection pool should check 2295 * connection age only in the background 2296 * health check thread. 2297 */ 2298 public void setCheckConnectionAgeOnRelease( 2299 final boolean checkConnectionAgeOnRelease) 2300 { 2301 this.checkConnectionAgeOnRelease = checkConnectionAgeOnRelease; 2302 } 2303 2304 2305 2306 /** 2307 * Retrieves the minimum length of time in milliseconds that should pass 2308 * between connections closed because they have been established for longer 2309 * than the maximum connection age. 2310 * 2311 * @return The minimum length of time in milliseconds that should pass 2312 * between connections closed because they have been established for 2313 * longer than the maximum connection age, or {@code 0L} if expired 2314 * connections may be closed as quickly as they are identified. 2315 */ 2316 public long getMinDisconnectIntervalMillis() 2317 { 2318 return minDisconnectInterval; 2319 } 2320 2321 2322 2323 /** 2324 * Specifies the minimum length of time in milliseconds that should pass 2325 * between connections closed because they have been established for longer 2326 * than the maximum connection age. 2327 * 2328 * @param minDisconnectInterval The minimum length of time in milliseconds 2329 * that should pass between connections closed 2330 * because they have been established for 2331 * longer than the maximum connection age. A 2332 * value less than or equal to zero indicates 2333 * that no minimum time should be enforced. 2334 */ 2335 public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) 2336 { 2337 if (minDisconnectInterval > 0) 2338 { 2339 this.minDisconnectInterval = minDisconnectInterval; 2340 } 2341 else 2342 { 2343 this.minDisconnectInterval = 0L; 2344 } 2345 } 2346 2347 2348 2349 /** 2350 * {@inheritDoc} 2351 */ 2352 @Override() 2353 public LDAPConnectionPoolHealthCheck getHealthCheck() 2354 { 2355 return healthCheck; 2356 } 2357 2358 2359 2360 /** 2361 * Sets the health check implementation for this connection pool. 2362 * 2363 * @param healthCheck The health check implementation for this connection 2364 * pool. It must not be {@code null}. 2365 */ 2366 public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck) 2367 { 2368 ensureNotNull(healthCheck); 2369 this.healthCheck = healthCheck; 2370 } 2371 2372 2373 2374 /** 2375 * {@inheritDoc} 2376 */ 2377 @Override() 2378 public long getHealthCheckIntervalMillis() 2379 { 2380 return healthCheckInterval; 2381 } 2382 2383 2384 2385 /** 2386 * {@inheritDoc} 2387 */ 2388 @Override() 2389 public void setHealthCheckIntervalMillis(final long healthCheckInterval) 2390 { 2391 ensureTrue(healthCheckInterval > 0L, 2392 "LDAPConnectionPool.healthCheckInterval must be greater than 0."); 2393 this.healthCheckInterval = healthCheckInterval; 2394 healthCheckThread.wakeUp(); 2395 } 2396 2397 2398 2399 /** 2400 * Indicates whether health check processing for connections operating in 2401 * synchronous mode should include attempting to perform a read from each 2402 * connection with a very short timeout. This can help detect unsolicited 2403 * responses and unexpected connection closures in a more timely manner. This 2404 * will be ignored for connections not operating in synchronous mode. 2405 * 2406 * @return {@code true} if health check processing for connections operating 2407 * in synchronous mode should include a read attempt with a very 2408 * short timeout, or {@code false} if not. 2409 */ 2410 public boolean trySynchronousReadDuringHealthCheck() 2411 { 2412 return trySynchronousReadDuringHealthCheck; 2413 } 2414 2415 2416 2417 /** 2418 * Specifies whether health check processing for connections operating in 2419 * synchronous mode should include attempting to perform a read from each 2420 * connection with a very short timeout. 2421 * 2422 * @param trySynchronousReadDuringHealthCheck Indicates whether health check 2423 * processing for connections 2424 * operating in synchronous mode 2425 * should include attempting to 2426 * perform a read from each 2427 * connection with a very short 2428 * timeout. 2429 */ 2430 public void setTrySynchronousReadDuringHealthCheck( 2431 final boolean trySynchronousReadDuringHealthCheck) 2432 { 2433 this.trySynchronousReadDuringHealthCheck = 2434 trySynchronousReadDuringHealthCheck; 2435 } 2436 2437 2438 2439 /** 2440 * {@inheritDoc} 2441 */ 2442 @Override() 2443 protected void doHealthCheck() 2444 { 2445 // Create a set used to hold connections that we've already examined. If we 2446 // encounter the same connection twice, then we know that we don't need to 2447 // do any more work. 2448 final HashSet<LDAPConnection> examinedConnections = 2449 new HashSet<LDAPConnection>(numConnections); 2450 2451 for (int i=0; i < numConnections; i++) 2452 { 2453 LDAPConnection conn = availableConnections.poll(); 2454 if (conn == null) 2455 { 2456 break; 2457 } 2458 else if (examinedConnections.contains(conn)) 2459 { 2460 if (! availableConnections.offer(conn)) 2461 { 2462 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 2463 null, null); 2464 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2465 conn.terminate(null); 2466 } 2467 break; 2468 } 2469 2470 if (! conn.isConnected()) 2471 { 2472 conn = handleDefunctConnection(conn); 2473 if (conn != null) 2474 { 2475 examinedConnections.add(conn); 2476 } 2477 } 2478 else 2479 { 2480 if (connectionIsExpired(conn)) 2481 { 2482 try 2483 { 2484 final LDAPConnection newConnection = createConnection(); 2485 if (availableConnections.offer(newConnection)) 2486 { 2487 examinedConnections.add(newConnection); 2488 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 2489 null, null); 2490 conn.terminate(null); 2491 poolStatistics.incrementNumConnectionsClosedExpired(); 2492 lastExpiredDisconnectTime = System.currentTimeMillis(); 2493 continue; 2494 } 2495 else 2496 { 2497 newConnection.setDisconnectInfo( 2498 DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null); 2499 newConnection.terminate(null); 2500 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2501 } 2502 } 2503 catch (final LDAPException le) 2504 { 2505 debugException(le); 2506 } 2507 } 2508 2509 2510 // If the connection is operating in synchronous mode, then try to read 2511 // a message on it using an extremely short timeout. This can help 2512 // detect a connection closure or unsolicited notification in a more 2513 // timely manner than if we had to wait for the client code to try to 2514 // use the connection. 2515 if (trySynchronousReadDuringHealthCheck && conn.synchronousMode()) 2516 { 2517 int previousTimeout = Integer.MIN_VALUE; 2518 Socket s = null; 2519 try 2520 { 2521 s = conn.getConnectionInternals(true).getSocket(); 2522 previousTimeout = s.getSoTimeout(); 2523 s.setSoTimeout(1); 2524 2525 final LDAPResponse response = conn.readResponse(0); 2526 if (response instanceof ConnectionClosedResponse) 2527 { 2528 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 2529 ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null); 2530 poolStatistics.incrementNumConnectionsClosedDefunct(); 2531 conn = handleDefunctConnection(conn); 2532 if (conn != null) 2533 { 2534 examinedConnections.add(conn); 2535 } 2536 continue; 2537 } 2538 else if (response instanceof ExtendedResult) 2539 { 2540 // This means we got an unsolicited response. It could be a 2541 // notice of disconnection, or it could be something else, but in 2542 // any case we'll send it to the connection's unsolicited 2543 // notification handler (if one is defined). 2544 final UnsolicitedNotificationHandler h = conn. 2545 getConnectionOptions().getUnsolicitedNotificationHandler(); 2546 if (h != null) 2547 { 2548 h.handleUnsolicitedNotification(conn, 2549 (ExtendedResult) response); 2550 } 2551 } 2552 else if (response instanceof LDAPResult) 2553 { 2554 final LDAPResult r = (LDAPResult) response; 2555 if (r.getResultCode() == ResultCode.SERVER_DOWN) 2556 { 2557 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 2558 ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null); 2559 poolStatistics.incrementNumConnectionsClosedDefunct(); 2560 conn = handleDefunctConnection(conn); 2561 if (conn != null) 2562 { 2563 examinedConnections.add(conn); 2564 } 2565 continue; 2566 } 2567 } 2568 } 2569 catch (final LDAPException le) 2570 { 2571 if (le.getResultCode() == ResultCode.TIMEOUT) 2572 { 2573 debugException(Level.FINEST, le); 2574 } 2575 else 2576 { 2577 debugException(le); 2578 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 2579 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get( 2580 getExceptionMessage(le)), le); 2581 poolStatistics.incrementNumConnectionsClosedDefunct(); 2582 conn = handleDefunctConnection(conn); 2583 if (conn != null) 2584 { 2585 examinedConnections.add(conn); 2586 } 2587 continue; 2588 } 2589 } 2590 catch (final Exception e) 2591 { 2592 debugException(e); 2593 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 2594 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get(getExceptionMessage(e)), 2595 e); 2596 poolStatistics.incrementNumConnectionsClosedDefunct(); 2597 conn = handleDefunctConnection(conn); 2598 if (conn != null) 2599 { 2600 examinedConnections.add(conn); 2601 } 2602 continue; 2603 } 2604 finally 2605 { 2606 if (previousTimeout != Integer.MIN_VALUE) 2607 { 2608 try 2609 { 2610 s.setSoTimeout(previousTimeout); 2611 } 2612 catch (final Exception e) 2613 { 2614 debugException(e); 2615 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 2616 null, e); 2617 poolStatistics.incrementNumConnectionsClosedDefunct(); 2618 conn = handleDefunctConnection(conn); 2619 if (conn != null) 2620 { 2621 examinedConnections.add(conn); 2622 } 2623 continue; 2624 } 2625 } 2626 } 2627 } 2628 2629 try 2630 { 2631 healthCheck.ensureConnectionValidForContinuedUse(conn); 2632 if (availableConnections.offer(conn)) 2633 { 2634 examinedConnections.add(conn); 2635 } 2636 else 2637 { 2638 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 2639 null, null); 2640 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2641 conn.terminate(null); 2642 } 2643 } 2644 catch (Exception e) 2645 { 2646 debugException(e); 2647 conn = handleDefunctConnection(conn); 2648 if (conn != null) 2649 { 2650 examinedConnections.add(conn); 2651 } 2652 } 2653 } 2654 } 2655 } 2656 2657 2658 2659 /** 2660 * {@inheritDoc} 2661 */ 2662 @Override() 2663 public int getCurrentAvailableConnections() 2664 { 2665 return availableConnections.size(); 2666 } 2667 2668 2669 2670 /** 2671 * {@inheritDoc} 2672 */ 2673 @Override() 2674 public int getMaximumAvailableConnections() 2675 { 2676 return numConnections; 2677 } 2678 2679 2680 2681 /** 2682 * {@inheritDoc} 2683 */ 2684 @Override() 2685 public LDAPConnectionPoolStatistics getConnectionPoolStatistics() 2686 { 2687 return poolStatistics; 2688 } 2689 2690 2691 2692 /** 2693 * Attempts to reduce the number of connections available for use in the pool. 2694 * Note that this will be a best-effort attempt to reach the desired number 2695 * of connections, as other threads interacting with the connection pool may 2696 * check out and/or release connections that cause the number of available 2697 * connections to fluctuate. 2698 * 2699 * @param connectionsToRetain The number of connections that should be 2700 * retained for use in the connection pool. 2701 */ 2702 public void shrinkPool(final int connectionsToRetain) 2703 { 2704 while (availableConnections.size() > connectionsToRetain) 2705 { 2706 final LDAPConnection conn; 2707 try 2708 { 2709 conn = getConnection(); 2710 } 2711 catch (final LDAPException le) 2712 { 2713 return; 2714 } 2715 2716 if (availableConnections.size() >= connectionsToRetain) 2717 { 2718 discardConnection(conn); 2719 } 2720 else 2721 { 2722 releaseConnection(conn); 2723 return; 2724 } 2725 } 2726 } 2727 2728 2729 2730 /** 2731 * Closes this connection pool in the event that it becomes unreferenced. 2732 * 2733 * @throws Throwable If an unexpected problem occurs. 2734 */ 2735 @Override() 2736 protected void finalize() 2737 throws Throwable 2738 { 2739 super.finalize(); 2740 2741 close(); 2742 } 2743 2744 2745 2746 /** 2747 * {@inheritDoc} 2748 */ 2749 @Override() 2750 public void toString(final StringBuilder buffer) 2751 { 2752 buffer.append("LDAPConnectionPool("); 2753 2754 final String name = connectionPoolName; 2755 if (name != null) 2756 { 2757 buffer.append("name='"); 2758 buffer.append(name); 2759 buffer.append("', "); 2760 } 2761 2762 buffer.append("serverSet="); 2763 serverSet.toString(buffer); 2764 buffer.append(", maxConnections="); 2765 buffer.append(numConnections); 2766 buffer.append(')'); 2767 } 2768}