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