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