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