001/* 002 * Copyright 2007-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2023 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-2023 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 poolStatistics.incrementNumConnectionsClosedDefunct(); 1816 Debug.debugConnectionPool(Level.WARNING, this, conn, 1817 "Closing a defunct connection encountered during checkout", 1818 connException); 1819 handleDefunctConnection(conn); 1820 for (int i=0; i < numConnections; i++) 1821 { 1822 conn = availableConnections.poll(); 1823 if (conn == null) 1824 { 1825 break; 1826 } 1827 else if (conn.isConnected()) 1828 { 1829 try 1830 { 1831 healthCheck.ensureConnectionValidForCheckout(conn); 1832 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 1833 Debug.debugConnectionPool(Level.INFO, this, conn, 1834 "Checked out an immediately available pooled connection", 1835 null); 1836 return conn; 1837 } 1838 catch (final LDAPException le) 1839 { 1840 Debug.debugException(le); 1841 poolStatistics.incrementNumConnectionsClosedDefunct(); 1842 Debug.debugConnectionPool(Level.WARNING, this, conn, 1843 "Closing a defunct connection encountered during checkout", 1844 le); 1845 handleDefunctConnection(conn); 1846 } 1847 } 1848 else 1849 { 1850 poolStatistics.incrementNumConnectionsClosedDefunct(); 1851 Debug.debugConnectionPool(Level.WARNING, this, conn, 1852 "Closing a defunct connection encountered during checkout", 1853 null); 1854 handleDefunctConnection(conn); 1855 } 1856 } 1857 } 1858 1859 if (failedReplaceCount.get() > 0) 1860 { 1861 final int newReplaceCount = failedReplaceCount.getAndDecrement(); 1862 if (newReplaceCount > 0) 1863 { 1864 try 1865 { 1866 conn = createConnection(); 1867 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 1868 Debug.debugConnectionPool(Level.INFO, this, conn, 1869 "Checked out a newly created connection", null); 1870 return conn; 1871 } 1872 catch (final LDAPException le) 1873 { 1874 Debug.debugException(le); 1875 failedReplaceCount.incrementAndGet(); 1876 poolStatistics.incrementNumFailedCheckouts(); 1877 Debug.debugConnectionPool(Level.SEVERE, this, conn, 1878 "Unable to create a new connection for checkout", le); 1879 throw le; 1880 } 1881 } 1882 else 1883 { 1884 failedReplaceCount.incrementAndGet(); 1885 } 1886 } 1887 1888 if (maxWaitTime > 0) 1889 { 1890 try 1891 { 1892 final long startWaitTime = System.currentTimeMillis(); 1893 conn = availableConnections.poll(maxWaitTime, TimeUnit.MILLISECONDS); 1894 final long elapsedWaitTime = System.currentTimeMillis() - startWaitTime; 1895 if (conn != null) 1896 { 1897 try 1898 { 1899 healthCheck.ensureConnectionValidForCheckout(conn); 1900 poolStatistics.incrementNumSuccessfulCheckoutsAfterWaiting(); 1901 Debug.debugConnectionPool(Level.INFO, this, conn, 1902 "Checked out an existing connection after waiting " + 1903 elapsedWaitTime + "ms for it to become available", 1904 null); 1905 return conn; 1906 } 1907 catch (final LDAPException le) 1908 { 1909 Debug.debugException(le); 1910 poolStatistics.incrementNumConnectionsClosedDefunct(); 1911 Debug.debugConnectionPool(Level.WARNING, this, conn, 1912 "Got a connection for checkout after waiting " + 1913 elapsedWaitTime + "ms for it to become available, but " + 1914 "the connection failed the checkout health check", 1915 le); 1916 handleDefunctConnection(conn); 1917 } 1918 } 1919 } 1920 catch (final InterruptedException ie) 1921 { 1922 Debug.debugException(ie); 1923 Thread.currentThread().interrupt(); 1924 throw new LDAPException(ResultCode.LOCAL_ERROR, 1925 ERR_POOL_CHECKOUT_INTERRUPTED.get(), ie); 1926 } 1927 } 1928 1929 if (createIfNecessary) 1930 { 1931 try 1932 { 1933 conn = createConnection(); 1934 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 1935 Debug.debugConnectionPool(Level.INFO, this, conn, 1936 "Checked out a newly created connection", null); 1937 return conn; 1938 } 1939 catch (final LDAPException le) 1940 { 1941 Debug.debugException(le); 1942 poolStatistics.incrementNumFailedCheckouts(); 1943 Debug.debugConnectionPool(Level.SEVERE, this, null, 1944 "Unable to create a new connection for checkout", le); 1945 throw le; 1946 } 1947 } 1948 else 1949 { 1950 poolStatistics.incrementNumFailedCheckouts(); 1951 Debug.debugConnectionPool(Level.SEVERE, this, null, 1952 "Unable to check out a connection because none are available", 1953 null); 1954 throw new LDAPException(ResultCode.CONNECT_ERROR, 1955 ERR_POOL_NO_CONNECTIONS.get()); 1956 } 1957 } 1958 1959 1960 1961 /** 1962 * Attempts to retrieve a connection from the pool that is established to the 1963 * specified server. Note that this method will only attempt to return an 1964 * existing connection that is currently available, and will not create a 1965 * connection or wait for any checked-out connections to be returned. 1966 * 1967 * @param host The address of the server to which the desired connection 1968 * should be established. This must not be {@code null}, and 1969 * this must exactly match the address provided for the initial 1970 * connection or the {@code ServerSet} used to create the pool. 1971 * @param port The port of the server to which the desired connection should 1972 * be established. 1973 * 1974 * @return A connection that is established to the specified server, or 1975 * {@code null} if there are no available connections established to 1976 * the specified server. 1977 */ 1978 @Nullable() 1979 public LDAPConnection getConnection(@NotNull final String host, 1980 final int port) 1981 { 1982 if (closed) 1983 { 1984 poolStatistics.incrementNumFailedCheckouts(); 1985 Debug.debugConnectionPool(Level.WARNING, this, null, 1986 "Failed to get a connection to a closed connection pool", null); 1987 return null; 1988 } 1989 1990 final HashSet<LDAPConnection> examinedConnections = 1991 new HashSet<>(StaticUtils.computeMapCapacity(numConnections)); 1992 while (true) 1993 { 1994 final LDAPConnection conn = availableConnections.poll(); 1995 if (conn == null) 1996 { 1997 poolStatistics.incrementNumFailedCheckouts(); 1998 Debug.debugConnectionPool(Level.SEVERE, this, null, 1999 "Failed to get an existing connection to " + host + ':' + port + 2000 " because no connections are immediately available", 2001 null); 2002 return null; 2003 } 2004 2005 if (examinedConnections.contains(conn)) 2006 { 2007 if (! availableConnections.offer(conn)) 2008 { 2009 discardConnection(conn); 2010 } 2011 2012 poolStatistics.incrementNumFailedCheckouts(); 2013 Debug.debugConnectionPool(Level.WARNING, this, null, 2014 "Failed to get an existing connection to " + host + ':' + port + 2015 " because none of the available connections are " + 2016 "established to that server", 2017 null); 2018 return null; 2019 } 2020 2021 if (conn.getConnectedAddress().equals(host) && 2022 (port == conn.getConnectedPort())) 2023 { 2024 try 2025 { 2026 healthCheck.ensureConnectionValidForCheckout(conn); 2027 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 2028 Debug.debugConnectionPool(Level.INFO, this, conn, 2029 "Successfully checked out an existing connection to requested " + 2030 "server " + host + ':' + port, 2031 null); 2032 return conn; 2033 } 2034 catch (final LDAPException le) 2035 { 2036 Debug.debugException(le); 2037 poolStatistics.incrementNumConnectionsClosedDefunct(); 2038 Debug.debugConnectionPool(Level.WARNING, this, conn, 2039 "Closing an existing connection to requested server " + host + 2040 ':' + port + " because it failed the checkout health " + 2041 "check", 2042 le); 2043 handleDefunctConnection(conn); 2044 continue; 2045 } 2046 } 2047 2048 if (availableConnections.offer(conn)) 2049 { 2050 examinedConnections.add(conn); 2051 } 2052 else 2053 { 2054 discardConnection(conn); 2055 } 2056 } 2057 } 2058 2059 2060 2061 /** 2062 * {@inheritDoc} 2063 */ 2064 @Override() 2065 public void releaseConnection(@NotNull final LDAPConnection connection) 2066 { 2067 if (connection == null) 2068 { 2069 return; 2070 } 2071 2072 connection.setConnectionPoolName(connectionPoolName); 2073 if (checkConnectionAgeOnRelease && connectionIsExpired(connection)) 2074 { 2075 try 2076 { 2077 final LDAPConnection newConnection = createConnection(); 2078 if (availableConnections.offer(newConnection)) 2079 { 2080 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 2081 null, null); 2082 connection.terminate(null); 2083 poolStatistics.incrementNumConnectionsClosedExpired(); 2084 Debug.debugConnectionPool(Level.WARNING, this, connection, 2085 "Closing a released connection because it is expired", null); 2086 lastExpiredDisconnectTime = System.currentTimeMillis(); 2087 } 2088 else 2089 { 2090 newConnection.setDisconnectInfo( 2091 DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null); 2092 newConnection.terminate(null); 2093 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2094 Debug.debugConnectionPool(Level.WARNING, this, connection, 2095 "Closing a released connection because the pool is already full", 2096 null); 2097 } 2098 } 2099 catch (final LDAPException le) 2100 { 2101 Debug.debugException(le); 2102 } 2103 return; 2104 } 2105 2106 try 2107 { 2108 healthCheck.ensureConnectionValidForRelease(connection); 2109 } 2110 catch (final LDAPException le) 2111 { 2112 releaseDefunctConnection(connection); 2113 return; 2114 } 2115 2116 if (availableConnections.offer(connection)) 2117 { 2118 poolStatistics.incrementNumReleasedValid(); 2119 Debug.debugConnectionPool(Level.INFO, this, connection, 2120 "Released a connection back to the pool", null); 2121 } 2122 else 2123 { 2124 // This means that the connection pool is full, which can happen if the 2125 // pool was empty when a request came in to retrieve a connection and 2126 // createIfNecessary was true. In this case, we'll just close the 2127 // connection since we don't need it any more. 2128 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 2129 null, null); 2130 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2131 Debug.debugConnectionPool(Level.WARNING, this, connection, 2132 "Closing a released connection because the pool is already full", 2133 null); 2134 connection.terminate(null); 2135 return; 2136 } 2137 2138 if (closed) 2139 { 2140 close(); 2141 } 2142 } 2143 2144 2145 2146 /** 2147 * Indicates that the provided connection should be removed from the pool, 2148 * and that no new connection should be created to take its place. This may 2149 * be used to shrink the pool if such functionality is desired. 2150 * 2151 * @param connection The connection to be discarded. 2152 */ 2153 public void discardConnection(@NotNull final LDAPConnection connection) 2154 { 2155 if (connection == null) 2156 { 2157 return; 2158 } 2159 2160 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 2161 null, null); 2162 connection.terminate(null); 2163 poolStatistics.incrementNumConnectionsClosedUnneeded(); 2164 Debug.debugConnectionPool(Level.INFO, this, connection, 2165 "Discareded a connection that is no longer needed", null); 2166 2167 if (availableConnections.remainingCapacity() > 0) 2168 { 2169 final int newReplaceCount = failedReplaceCount.incrementAndGet(); 2170 if (newReplaceCount > numConnections) 2171 { 2172 failedReplaceCount.set(numConnections); 2173 } 2174 } 2175 } 2176 2177 2178 2179 /** 2180 * Performs a bind on the provided connection before releasing it back to the 2181 * pool, so that it will be authenticated as the same user as 2182 * newly-established connections. If newly-established connections are 2183 * unauthenticated, then this method will perform an anonymous simple bind to 2184 * ensure that the resulting connection is unauthenticated. 2185 * 2186 * Releases the provided connection back to this pool. 2187 * 2188 * @param connection The connection to be released back to the pool after 2189 * being re-authenticated. 2190 */ 2191 public void releaseAndReAuthenticateConnection( 2192 @NotNull final LDAPConnection connection) 2193 { 2194 if (connection == null) 2195 { 2196 return; 2197 } 2198 2199 try 2200 { 2201 BindResult bindResult; 2202 try 2203 { 2204 if (bindRequest == null) 2205 { 2206 bindResult = connection.bind("", ""); 2207 } 2208 else 2209 { 2210 bindResult = connection.bind(bindRequest.duplicate()); 2211 } 2212 } 2213 catch (final LDAPBindException lbe) 2214 { 2215 Debug.debugException(lbe); 2216 bindResult = lbe.getBindResult(); 2217 } 2218 2219 try 2220 { 2221 healthCheck.ensureConnectionValidAfterAuthentication(connection, 2222 bindResult); 2223 if (bindResult.getResultCode() != ResultCode.SUCCESS) 2224 { 2225 throw new LDAPBindException(bindResult); 2226 } 2227 } 2228 catch (final LDAPException le) 2229 { 2230 Debug.debugException(le); 2231 2232 try 2233 { 2234 connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); 2235 connection.setClosed(); 2236 releaseDefunctConnection(connection); 2237 } 2238 catch (final Exception e) 2239 { 2240 Debug.debugException(e); 2241 } 2242 2243 throw le; 2244 } 2245 2246 releaseConnection(connection); 2247 } 2248 catch (final Exception e) 2249 { 2250 Debug.debugException(e); 2251 releaseDefunctConnection(connection); 2252 } 2253 } 2254 2255 2256 2257 /** 2258 * {@inheritDoc} 2259 */ 2260 @Override() 2261 public void releaseDefunctConnection(@NotNull final LDAPConnection connection) 2262 { 2263 if (connection == null) 2264 { 2265 return; 2266 } 2267 2268 connection.setConnectionPoolName(connectionPoolName); 2269 poolStatistics.incrementNumConnectionsClosedDefunct(); 2270 Debug.debugConnectionPool(Level.WARNING, this, connection, 2271 "Releasing a defunct connection", null); 2272 handleDefunctConnection(connection); 2273 } 2274 2275 2276 2277 /** 2278 * Performs the real work of terminating a defunct connection and replacing it 2279 * with a new connection if possible. 2280 * 2281 * @param connection The defunct connection to be replaced. 2282 * 2283 * @return The new connection created to take the place of the defunct 2284 * connection, or {@code null} if no new connection was created. 2285 * Note that if a connection is returned, it will have already been 2286 * made available and the caller must not rely on it being unused for 2287 * any other purpose. 2288 */ 2289 @NotNull() 2290 private LDAPConnection handleDefunctConnection( 2291 @NotNull final LDAPConnection connection) 2292 { 2293 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 2294 null); 2295 connection.setClosed(); 2296 2297 if (closed) 2298 { 2299 return null; 2300 } 2301 2302 if (createIfNecessary && (availableConnections.remainingCapacity() <= 0)) 2303 { 2304 return null; 2305 } 2306 2307 try 2308 { 2309 final LDAPConnection conn = createConnection(); 2310 if (maxDefunctReplacementConnectionAge != null) 2311 { 2312 // Only set the maximum age if there isn't one already set for the 2313 // connection (i.e., because it was defined by the server set). 2314 if (conn.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE) == null) 2315 { 2316 conn.setAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE, 2317 maxDefunctReplacementConnectionAge); 2318 } 2319 } 2320 2321 if (! availableConnections.offer(conn)) 2322 { 2323 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 2324 null, null); 2325 conn.terminate(null); 2326 return null; 2327 } 2328 2329 return conn; 2330 } 2331 catch (final LDAPException le) 2332 { 2333 Debug.debugException(le); 2334 final int newReplaceCount = failedReplaceCount.incrementAndGet(); 2335 if (newReplaceCount > numConnections) 2336 { 2337 failedReplaceCount.set(numConnections); 2338 } 2339 return null; 2340 } 2341 } 2342 2343 2344 2345 /** 2346 * {@inheritDoc} 2347 */ 2348 @Override() 2349 @NotNull() 2350 public LDAPConnection replaceDefunctConnection( 2351 @NotNull final LDAPConnection connection) 2352 throws LDAPException 2353 { 2354 poolStatistics.incrementNumConnectionsClosedDefunct(); 2355 Debug.debugConnectionPool(Level.WARNING, this, connection, 2356 "Releasing a defunct connection that is to be replaced", null); 2357 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 2358 null); 2359 connection.setClosed(); 2360 2361 if (closed) 2362 { 2363 throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); 2364 } 2365 2366 try 2367 { 2368 return createConnection(); 2369 } 2370 catch (final LDAPException le) 2371 { 2372 Debug.debugException(le); 2373 failedReplaceCount.incrementAndGet(); 2374 throw le; 2375 } 2376 } 2377 2378 2379 2380 /** 2381 * {@inheritDoc} 2382 */ 2383 @Override() 2384 @NotNull() 2385 public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections() 2386 { 2387 return retryOperationTypes.get(); 2388 } 2389 2390 2391 2392 /** 2393 * {@inheritDoc} 2394 */ 2395 @Override() 2396 public void setRetryFailedOperationsDueToInvalidConnections( 2397 @Nullable final Set<OperationType> operationTypes) 2398 { 2399 if ((operationTypes == null) || operationTypes.isEmpty()) 2400 { 2401 retryOperationTypes.set( 2402 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 2403 } 2404 else 2405 { 2406 final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class); 2407 s.addAll(operationTypes); 2408 retryOperationTypes.set(Collections.unmodifiableSet(s)); 2409 } 2410 } 2411 2412 2413 2414 /** 2415 * Indicates whether the provided connection should be considered expired. 2416 * 2417 * @param connection The connection for which to make the determination. 2418 * 2419 * @return {@code true} if the provided connection should be considered 2420 * expired, or {@code false} if not. 2421 */ 2422 private boolean connectionIsExpired(@NotNull final LDAPConnection connection) 2423 { 2424 // There may be a custom maximum connection age for the connection. If that 2425 // is the case, then use that custom max age rather than the pool-default 2426 // max age. 2427 final long maxAge; 2428 final Object maxAgeObj = 2429 connection.getAttachment(ATTACHMENT_NAME_MAX_CONNECTION_AGE); 2430 if ((maxAgeObj != null) && (maxAgeObj instanceof Long)) 2431 { 2432 maxAge = (Long) maxAgeObj; 2433 } 2434 else 2435 { 2436 maxAge = maxConnectionAge; 2437 } 2438 2439 // If connection expiration is not enabled, then there is nothing to do. 2440 if (maxAge <= 0L) 2441 { 2442 return false; 2443 } 2444 2445 // If there is a minimum disconnect interval, then make sure that we have 2446 // not closed another expired connection too recently. 2447 final long currentTime = System.currentTimeMillis(); 2448 if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) 2449 { 2450 return false; 2451 } 2452 2453 // Get the age of the connection and see if it is expired. 2454 final long connectionAge = currentTime - connection.getConnectTime(); 2455 return (connectionAge > maxAge); 2456 } 2457 2458 2459 2460 /** 2461 * Specifies the bind request that will be used to authenticate subsequent new 2462 * connections that are established by this connection pool. The 2463 * authentication state for existing connections will not be altered unless 2464 * one of the {@code bindAndRevertAuthentication} or 2465 * {@code releaseAndReAuthenticateConnection} methods are invoked on those 2466 * connections. 2467 * 2468 * @param bindRequest The bind request that will be used to authenticate new 2469 * connections that are established by this pool, or 2470 * that will be applied to existing connections via the 2471 * {@code bindAndRevertAuthentication} or 2472 * {@code releaseAndReAuthenticateConnection} method. It 2473 * may be {@code null} if new connections should be 2474 * unauthenticated. 2475 */ 2476 public void setBindRequest(@Nullable final BindRequest bindRequest) 2477 { 2478 this.bindRequest = bindRequest; 2479 } 2480 2481 2482 2483 /** 2484 * Retrieves the server set that should be used to establish new connections 2485 * for use in this connection pool. 2486 * 2487 * @return The server set that should be used to establish new connections 2488 * for use in this connection pool. 2489 */ 2490 @NotNull() 2491 public ServerSet getServerSet() 2492 { 2493 return serverSet; 2494 } 2495 2496 2497 2498 /** 2499 * Specifies the server set that should be used to establish new connections 2500 * for use in this connection pool. Existing connections will not be 2501 * affected. 2502 * 2503 * @param serverSet The server set that should be used to establish new 2504 * connections for use in this connection pool. It must 2505 * not be {@code null}. 2506 */ 2507 public void setServerSet(@Nullable final ServerSet serverSet) 2508 { 2509 Validator.ensureNotNull(serverSet); 2510 this.serverSet = serverSet; 2511 } 2512 2513 2514 2515 /** 2516 * {@inheritDoc} 2517 */ 2518 @Override() 2519 @Nullable() 2520 public String getConnectionPoolName() 2521 { 2522 return connectionPoolName; 2523 } 2524 2525 2526 2527 /** 2528 * {@inheritDoc} 2529 */ 2530 @Override() 2531 public void setConnectionPoolName(@Nullable final String connectionPoolName) 2532 { 2533 this.connectionPoolName = connectionPoolName; 2534 for (final LDAPConnection c : availableConnections) 2535 { 2536 c.setConnectionPoolName(connectionPoolName); 2537 } 2538 } 2539 2540 2541 2542 /** 2543 * Indicates whether the connection pool should create a new connection if one 2544 * is requested when there are none available. 2545 * 2546 * @return {@code true} if a new connection should be created if none are 2547 * available when a request is received, or {@code false} if an 2548 * exception should be thrown to indicate that no connection is 2549 * available. 2550 */ 2551 public boolean getCreateIfNecessary() 2552 { 2553 return createIfNecessary; 2554 } 2555 2556 2557 2558 /** 2559 * Specifies whether the connection pool should create a new connection if one 2560 * is requested when there are none available. 2561 * 2562 * @param createIfNecessary Specifies whether the connection pool should 2563 * create a new connection if one is requested when 2564 * there are none available. 2565 */ 2566 public void setCreateIfNecessary(final boolean createIfNecessary) 2567 { 2568 this.createIfNecessary = createIfNecessary; 2569 } 2570 2571 2572 2573 /** 2574 * Retrieves the maximum length of time in milliseconds to wait for a 2575 * connection to become available when trying to obtain a connection from the 2576 * pool. 2577 * 2578 * @return The maximum length of time in milliseconds to wait for a 2579 * connection to become available when trying to obtain a connection 2580 * from the pool, or zero to indicate that the pool should not block 2581 * at all if no connections are available and that it should either 2582 * create a new connection or throw an exception. 2583 */ 2584 public long getMaxWaitTimeMillis() 2585 { 2586 return maxWaitTime; 2587 } 2588 2589 2590 2591 /** 2592 * Specifies the maximum length of time in milliseconds to wait for a 2593 * connection to become available when trying to obtain a connection from the 2594 * pool. 2595 * 2596 * @param maxWaitTime The maximum length of time in milliseconds to wait for 2597 * a connection to become available when trying to obtain 2598 * a connection from the pool. A value of zero should be 2599 * used to indicate that the pool should not block at all 2600 * if no connections are available and that it should 2601 * either create a new connection or throw an exception. 2602 */ 2603 public void setMaxWaitTimeMillis(final long maxWaitTime) 2604 { 2605 if (maxWaitTime > 0L) 2606 { 2607 this.maxWaitTime = maxWaitTime; 2608 } 2609 else 2610 { 2611 this.maxWaitTime = 0L; 2612 } 2613 } 2614 2615 2616 2617 /** 2618 * Retrieves the maximum length of time in milliseconds that a connection in 2619 * this pool may be established before it is closed and replaced with another 2620 * connection. 2621 * 2622 * @return The maximum length of time in milliseconds that a connection in 2623 * this pool may be established before it is closed and replaced with 2624 * another connection, or {@code 0L} if no maximum age should be 2625 * enforced. 2626 */ 2627 public long getMaxConnectionAgeMillis() 2628 { 2629 return maxConnectionAge; 2630 } 2631 2632 2633 2634 /** 2635 * Specifies the maximum length of time in milliseconds that a connection in 2636 * this pool may be established before it should be closed and replaced with 2637 * another connection. 2638 * 2639 * @param maxConnectionAge The maximum length of time in milliseconds that a 2640 * connection in this pool may be established before 2641 * it should be closed and replaced with another 2642 * connection. A value of zero indicates that no 2643 * maximum age should be enforced. 2644 */ 2645 public void setMaxConnectionAgeMillis(final long maxConnectionAge) 2646 { 2647 if (maxConnectionAge > 0L) 2648 { 2649 this.maxConnectionAge = maxConnectionAge; 2650 } 2651 else 2652 { 2653 this.maxConnectionAge = 0L; 2654 } 2655 } 2656 2657 2658 2659 /** 2660 * Retrieves the maximum connection age that should be used for connections 2661 * that were created in order to replace defunct connections. It is possible 2662 * to define a custom maximum connection age for these connections to allow 2663 * them to be closed and re-established more quickly to allow for a 2664 * potentially quicker fail-back to a normal state. Note, that if this 2665 * capability is to be used, then the maximum age for these connections should 2666 * be long enough to allow the problematic server to become available again 2667 * under normal circumstances (e.g., it should be long enough for at least a 2668 * shutdown and restart of the server, plus some overhead for potentially 2669 * performing routine maintenance while the server is offline, or a chance for 2670 * an administrator to be made available that a server has gone down). 2671 * 2672 * @return The maximum connection age that should be used for connections 2673 * that were created in order to replace defunct connections, a value 2674 * of zero to indicate that no maximum age should be enforced, or 2675 * {@code null} if the value returned by the 2676 * {@link #getMaxConnectionAgeMillis()} method should be used. 2677 */ 2678 @Nullable() 2679 public Long getMaxDefunctReplacementConnectionAgeMillis() 2680 { 2681 return maxDefunctReplacementConnectionAge; 2682 } 2683 2684 2685 2686 /** 2687 * Specifies the maximum connection age that should be used for connections 2688 * that were created in order to replace defunct connections. It is possible 2689 * to define a custom maximum connection age for these connections to allow 2690 * them to be closed and re-established more quickly to allow for a 2691 * potentially quicker fail-back to a normal state. Note, that if this 2692 * capability is to be used, then the maximum age for these connections should 2693 * be long enough to allow the problematic server to become available again 2694 * under normal circumstances (e.g., it should be long enough for at least a 2695 * shutdown and restart of the server, plus some overhead for potentially 2696 * performing routine maintenance while the server is offline, or a chance for 2697 * an administrator to be made available that a server has gone down). 2698 * 2699 * @param maxDefunctReplacementConnectionAge The maximum connection age that 2700 * should be used for connections that were created in order to 2701 * replace defunct connections. It may be zero if no maximum age 2702 * should be enforced for such connections, or it may be 2703 * {@code null} if the value returned by the 2704 * {@link #getMaxConnectionAgeMillis()} method should be used. 2705 */ 2706 public void setMaxDefunctReplacementConnectionAgeMillis( 2707 @Nullable final Long maxDefunctReplacementConnectionAge) 2708 { 2709 if (maxDefunctReplacementConnectionAge == null) 2710 { 2711 this.maxDefunctReplacementConnectionAge = null; 2712 } 2713 else if (maxDefunctReplacementConnectionAge > 0L) 2714 { 2715 this.maxDefunctReplacementConnectionAge = 2716 maxDefunctReplacementConnectionAge; 2717 } 2718 else 2719 { 2720 this.maxDefunctReplacementConnectionAge = 0L; 2721 } 2722 } 2723 2724 2725 2726 /** 2727 * Indicates whether to check the age of a connection against the configured 2728 * maximum connection age whenever it is released to the pool. By default, 2729 * connection age is evaluated in the background using the health check 2730 * thread, but it is also possible to configure the pool to additionally 2731 * examine the age of a connection when it is returned to the pool. 2732 * <BR><BR> 2733 * Performing connection age evaluation only in the background will ensure 2734 * that connections are only closed and re-established in a single-threaded 2735 * manner, which helps minimize the load against the target server, but only 2736 * checks connections that are not in use when the health check thread is 2737 * active. If the pool is configured to also evaluate the connection age when 2738 * connections are returned to the pool, then it may help ensure that the 2739 * maximum connection age is honored more strictly for all connections, but 2740 * in busy applications may lead to cases in which multiple connections are 2741 * closed and re-established simultaneously, which may increase load against 2742 * the directory server. The {@link #setMinDisconnectIntervalMillis(long)} 2743 * method may be used to help mitigate the potential performance impact of 2744 * closing and re-establishing multiple connections simultaneously. 2745 * 2746 * @return {@code true} if the connection pool should check connection age in 2747 * both the background health check thread and when connections are 2748 * released to the pool, or {@code false} if the connection age 2749 * should only be checked by the background health check thread. 2750 */ 2751 public boolean checkConnectionAgeOnRelease() 2752 { 2753 return checkConnectionAgeOnRelease; 2754 } 2755 2756 2757 2758 /** 2759 * Specifies whether to check the age of a connection against the configured 2760 * maximum connection age whenever it is released to the pool. By default, 2761 * connection age is evaluated in the background using the health check 2762 * thread, but it is also possible to configure the pool to additionally 2763 * examine the age of a connection when it is returned to the pool. 2764 * <BR><BR> 2765 * Performing connection age evaluation only in the background will ensure 2766 * that connections are only closed and re-established in a single-threaded 2767 * manner, which helps minimize the load against the target server, but only 2768 * checks connections that are not in use when the health check thread is 2769 * active. If the pool is configured to also evaluate the connection age when 2770 * connections are returned to the pool, then it may help ensure that the 2771 * maximum connection age is honored more strictly for all connections, but 2772 * in busy applications may lead to cases in which multiple connections are 2773 * closed and re-established simultaneously, which may increase load against 2774 * the directory server. The {@link #setMinDisconnectIntervalMillis(long)} 2775 * method may be used to help mitigate the potential performance impact of 2776 * closing and re-establishing multiple connections simultaneously. 2777 * 2778 * @param checkConnectionAgeOnRelease If {@code true}, this indicates that 2779 * the connection pool should check 2780 * connection age in both the background 2781 * health check thread and when 2782 * connections are released to the pool. 2783 * If {@code false}, this indicates that 2784 * the connection pool should check 2785 * connection age only in the background 2786 * health check thread. 2787 */ 2788 public void setCheckConnectionAgeOnRelease( 2789 final boolean checkConnectionAgeOnRelease) 2790 { 2791 this.checkConnectionAgeOnRelease = checkConnectionAgeOnRelease; 2792 } 2793 2794 2795 2796 /** 2797 * Retrieves the minimum length of time in milliseconds that should pass 2798 * between connections closed because they have been established for longer 2799 * than the maximum connection age. 2800 * 2801 * @return The minimum length of time in milliseconds that should pass 2802 * between connections closed because they have been established for 2803 * longer than the maximum connection age, or {@code 0L} if expired 2804 * connections may be closed as quickly as they are identified. 2805 */ 2806 public long getMinDisconnectIntervalMillis() 2807 { 2808 return minDisconnectInterval; 2809 } 2810 2811 2812 2813 /** 2814 * Specifies the minimum length of time in milliseconds that should pass 2815 * between connections closed because they have been established for longer 2816 * than the maximum connection age. 2817 * 2818 * @param minDisconnectInterval The minimum length of time in milliseconds 2819 * that should pass between connections closed 2820 * because they have been established for 2821 * longer than the maximum connection age. A 2822 * value less than or equal to zero indicates 2823 * that no minimum time should be enforced. 2824 */ 2825 public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) 2826 { 2827 if (minDisconnectInterval > 0) 2828 { 2829 this.minDisconnectInterval = minDisconnectInterval; 2830 } 2831 else 2832 { 2833 this.minDisconnectInterval = 0L; 2834 } 2835 } 2836 2837 2838 2839 /** 2840 * {@inheritDoc} 2841 */ 2842 @Override() 2843 @NotNull() 2844 public LDAPConnectionPoolHealthCheck getHealthCheck() 2845 { 2846 return healthCheck; 2847 } 2848 2849 2850 2851 /** 2852 * Sets the health check implementation for this connection pool. 2853 * 2854 * @param healthCheck The health check implementation for this connection 2855 * pool. It must not be {@code null}. 2856 */ 2857 public void setHealthCheck( 2858 @NotNull final LDAPConnectionPoolHealthCheck healthCheck) 2859 { 2860 Validator.ensureNotNull(healthCheck); 2861 this.healthCheck = healthCheck; 2862 } 2863 2864 2865 2866 /** 2867 * {@inheritDoc} 2868 */ 2869 @Override() 2870 public long getHealthCheckIntervalMillis() 2871 { 2872 return healthCheckInterval; 2873 } 2874 2875 2876 2877 /** 2878 * {@inheritDoc} 2879 */ 2880 @Override() 2881 public void setHealthCheckIntervalMillis(final long healthCheckInterval) 2882 { 2883 Validator.ensureTrue(healthCheckInterval > 0L, 2884 "LDAPConnectionPool.healthCheckInterval must be greater than 0."); 2885 this.healthCheckInterval = healthCheckInterval; 2886 healthCheckThread.wakeUp(); 2887 } 2888 2889 2890 2891 /** 2892 * Indicates whether health check processing for connections operating in 2893 * synchronous mode should include attempting to perform a read from each 2894 * connection with a very short timeout. This can help detect unsolicited 2895 * responses and unexpected connection closures in a more timely manner. This 2896 * will be ignored for connections not operating in synchronous mode. 2897 * 2898 * @return {@code true} if health check processing for connections operating 2899 * in synchronous mode should include a read attempt with a very 2900 * short timeout, or {@code false} if not. 2901 */ 2902 public boolean trySynchronousReadDuringHealthCheck() 2903 { 2904 return trySynchronousReadDuringHealthCheck; 2905 } 2906 2907 2908 2909 /** 2910 * Specifies whether health check processing for connections operating in 2911 * synchronous mode should include attempting to perform a read from each 2912 * connection with a very short timeout. 2913 * 2914 * @param trySynchronousReadDuringHealthCheck Indicates whether health check 2915 * processing for connections 2916 * operating in synchronous mode 2917 * should include attempting to 2918 * perform a read from each 2919 * connection with a very short 2920 * timeout. 2921 */ 2922 public void setTrySynchronousReadDuringHealthCheck( 2923 final boolean trySynchronousReadDuringHealthCheck) 2924 { 2925 this.trySynchronousReadDuringHealthCheck = 2926 trySynchronousReadDuringHealthCheck; 2927 } 2928 2929 2930 2931 /** 2932 * {@inheritDoc} 2933 */ 2934 @Override() 2935 protected void doHealthCheck() 2936 { 2937 invokeHealthCheck(null, true); 2938 } 2939 2940 2941 2942 /** 2943 * Invokes a synchronous one-time health-check against the connections in this 2944 * pool that are not currently in use. This will be independent of any 2945 * background health checking that may be automatically performed by the pool. 2946 * 2947 * @param healthCheck The health check to use. If this is 2948 * {@code null}, then the pool's 2949 * currently-configured health check (if any) will 2950 * be used. If this is {@code null} and there is 2951 * no health check configured for the pool, then 2952 * only a basic set of checks. 2953 * @param checkForExpiration Indicates whether to check to see if any 2954 * connections have been established for longer 2955 * than the maximum connection age. If this is 2956 * {@code true} then any expired connections will 2957 * be closed and replaced with newly-established 2958 * connections. 2959 * 2960 * @return An object with information about the result of the health check 2961 * processing. 2962 */ 2963 @NotNull() 2964 public LDAPConnectionPoolHealthCheckResult invokeHealthCheck( 2965 @Nullable final LDAPConnectionPoolHealthCheck healthCheck, 2966 final boolean checkForExpiration) 2967 { 2968 return invokeHealthCheck(healthCheck, checkForExpiration, 2969 checkForExpiration); 2970 } 2971 2972 2973 2974 /** 2975 * Invokes a synchronous one-time health-check against the connections in this 2976 * pool that are not currently in use. This will be independent of any 2977 * background health checking that may be automatically performed by the pool. 2978 * 2979 * @param healthCheck The health check to use. If this is 2980 * {@code null}, then the pool's 2981 * currently-configured health check (if any) 2982 * will be used. If this is {@code null} and 2983 * there is no health check configured for the 2984 * pool, then only a basic set of checks. 2985 * @param checkForExpiration Indicates whether to check to see if any 2986 * connections have been established for 2987 * longer than the maximum connection age. If 2988 * this is {@code true} then any expired 2989 * connections will be closed and replaced 2990 * with newly-established connections. 2991 * @param checkMinConnectionGoal Indicates whether to check to see if the 2992 * currently-available number of connections 2993 * is less than the minimum available 2994 * connection goal. If this is {@code true} 2995 * the minimum available connection goal is 2996 * greater than zero, and the number of 2997 * currently-available connections is less 2998 * than the goal, then this method will 2999 * attempt to create enough new connections to 3000 * reach the goal. 3001 * 3002 * @return An object with information about the result of the health check 3003 * processing. 3004 */ 3005 @NotNull() 3006 public LDAPConnectionPoolHealthCheckResult invokeHealthCheck( 3007 @Nullable final LDAPConnectionPoolHealthCheck healthCheck, 3008 final boolean checkForExpiration, 3009 final boolean checkMinConnectionGoal) 3010 { 3011 // Determine which health check to use. 3012 final LDAPConnectionPoolHealthCheck hc; 3013 if (healthCheck == null) 3014 { 3015 hc = this.healthCheck; 3016 } 3017 else 3018 { 3019 hc = healthCheck; 3020 } 3021 3022 3023 // Create a set used to hold connections that we've already examined. If we 3024 // encounter the same connection twice, then we know that we don't need to 3025 // do any more work. 3026 final HashSet<LDAPConnection> examinedConnections = 3027 new HashSet<>(StaticUtils.computeMapCapacity(numConnections)); 3028 int numExamined = 0; 3029 int numDefunct = 0; 3030 int numExpired = 0; 3031 3032 for (int i=0; i < numConnections; i++) 3033 { 3034 LDAPConnection conn = availableConnections.poll(); 3035 if (conn == null) 3036 { 3037 break; 3038 } 3039 else if (examinedConnections.contains(conn)) 3040 { 3041 if (! availableConnections.offer(conn)) 3042 { 3043 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 3044 null, null); 3045 poolStatistics.incrementNumConnectionsClosedUnneeded(); 3046 Debug.debugConnectionPool(Level.INFO, this, conn, 3047 "Closing a connection that had just been health checked " + 3048 "because the pool is now full", null); 3049 conn.terminate(null); 3050 } 3051 break; 3052 } 3053 3054 numExamined++; 3055 if (! conn.isConnected()) 3056 { 3057 numDefunct++; 3058 poolStatistics.incrementNumConnectionsClosedDefunct(); 3059 Debug.debugConnectionPool(Level.WARNING, this, conn, 3060 "Closing a connection that was identified as not established " + 3061 "during health check processing", 3062 null); 3063 conn = handleDefunctConnection(conn); 3064 if (conn != null) 3065 { 3066 examinedConnections.add(conn); 3067 } 3068 } 3069 else 3070 { 3071 if (checkForExpiration && connectionIsExpired(conn)) 3072 { 3073 numExpired++; 3074 3075 try 3076 { 3077 final LDAPConnection newConnection = createConnection(); 3078 if (availableConnections.offer(newConnection)) 3079 { 3080 examinedConnections.add(newConnection); 3081 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 3082 null, null); 3083 conn.terminate(null); 3084 poolStatistics.incrementNumConnectionsClosedExpired(); 3085 Debug.debugConnectionPool(Level.INFO, this, conn, 3086 "Closing a connection that was identified as expired " + 3087 "during health check processing", 3088 null); 3089 lastExpiredDisconnectTime = System.currentTimeMillis(); 3090 continue; 3091 } 3092 else 3093 { 3094 newConnection.setDisconnectInfo( 3095 DisconnectType.POOLED_CONNECTION_UNNEEDED, null, null); 3096 newConnection.terminate(null); 3097 poolStatistics.incrementNumConnectionsClosedUnneeded(); 3098 Debug.debugConnectionPool(Level.INFO, this, newConnection, 3099 "Closing a newly created connection created to replace " + 3100 "an expired connection because the pool is already " + 3101 "full", 3102 null); 3103 } 3104 } 3105 catch (final LDAPException le) 3106 { 3107 Debug.debugException(le); 3108 } 3109 } 3110 3111 3112 // If the connection is operating in synchronous mode, then try to read 3113 // a message on it using an extremely short timeout. This can help 3114 // detect a connection closure or unsolicited notification in a more 3115 // timely manner than if we had to wait for the client code to try to 3116 // use the connection. 3117 if (trySynchronousReadDuringHealthCheck && conn.synchronousMode()) 3118 { 3119 int previousTimeout = Integer.MIN_VALUE; 3120 Socket s = null; 3121 try 3122 { 3123 s = conn.getConnectionInternals(true).getSocket(); 3124 previousTimeout = s.getSoTimeout(); 3125 InternalSDKHelper.setSoTimeout(conn, 1); 3126 3127 final LDAPResponse response = conn.readResponse(0); 3128 if (response instanceof ConnectionClosedResponse) 3129 { 3130 numDefunct++; 3131 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 3132 ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null); 3133 poolStatistics.incrementNumConnectionsClosedDefunct(); 3134 Debug.debugConnectionPool(Level.WARNING, this, conn, 3135 "Closing existing connection discovered to be " + 3136 "disconnected during health check processing", 3137 null); 3138 conn = handleDefunctConnection(conn); 3139 if (conn != null) 3140 { 3141 examinedConnections.add(conn); 3142 } 3143 continue; 3144 } 3145 else if (response instanceof ExtendedResult) 3146 { 3147 // This means we got an unsolicited response. It could be a 3148 // notice of disconnection, or it could be something else, but in 3149 // any case we'll send it to the connection's unsolicited 3150 // notification handler (if one is defined). 3151 final UnsolicitedNotificationHandler h = conn. 3152 getConnectionOptions().getUnsolicitedNotificationHandler(); 3153 if (h != null) 3154 { 3155 h.handleUnsolicitedNotification(conn, 3156 (ExtendedResult) response); 3157 } 3158 } 3159 else if (response instanceof LDAPResult) 3160 { 3161 final LDAPResult r = (LDAPResult) response; 3162 if (r.getResultCode() == ResultCode.SERVER_DOWN) 3163 { 3164 numDefunct++; 3165 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 3166 ERR_POOL_HEALTH_CHECK_CONN_CLOSED.get(), null); 3167 poolStatistics.incrementNumConnectionsClosedDefunct(); 3168 Debug.debugConnectionPool(Level.WARNING, this, conn, 3169 "Closing existing connection discovered to be invalid " + 3170 "with result " + r + " during health check " + 3171 "processing", 3172 null); 3173 conn = handleDefunctConnection(conn); 3174 if (conn != null) 3175 { 3176 examinedConnections.add(conn); 3177 } 3178 continue; 3179 } 3180 } 3181 } 3182 catch (final LDAPException le) 3183 { 3184 if (le.getResultCode() == ResultCode.TIMEOUT) 3185 { 3186 Debug.debugException(Level.FINEST, le); 3187 } 3188 else 3189 { 3190 Debug.debugException(le); 3191 numDefunct++; 3192 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 3193 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get( 3194 StaticUtils.getExceptionMessage(le)), le); 3195 poolStatistics.incrementNumConnectionsClosedDefunct(); 3196 Debug.debugConnectionPool(Level.WARNING, this, conn, 3197 "Closing existing connection discovered to be invalid " + 3198 "during health check processing", 3199 le); 3200 conn = handleDefunctConnection(conn); 3201 if (conn != null) 3202 { 3203 examinedConnections.add(conn); 3204 } 3205 continue; 3206 } 3207 } 3208 catch (final Exception e) 3209 { 3210 Debug.debugException(e); 3211 numDefunct++; 3212 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 3213 ERR_POOL_HEALTH_CHECK_READ_FAILURE.get( 3214 StaticUtils.getExceptionMessage(e)), 3215 e); 3216 poolStatistics.incrementNumConnectionsClosedDefunct(); 3217 Debug.debugConnectionPool(Level.SEVERE, this, conn, 3218 "Closing existing connection discovered to be invalid " + 3219 "with an unexpected exception type during health check " + 3220 "processing", 3221 e); 3222 conn = handleDefunctConnection(conn); 3223 if (conn != null) 3224 { 3225 examinedConnections.add(conn); 3226 } 3227 continue; 3228 } 3229 finally 3230 { 3231 if (previousTimeout != Integer.MIN_VALUE) 3232 { 3233 try 3234 { 3235 if (s != null) 3236 { 3237 InternalSDKHelper.setSoTimeout(conn, previousTimeout); 3238 } 3239 } 3240 catch (final Exception e) 3241 { 3242 Debug.debugException(e); 3243 numDefunct++; 3244 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, 3245 null, e); 3246 poolStatistics.incrementNumConnectionsClosedDefunct(); 3247 Debug.debugConnectionPool(Level.SEVERE, this, conn, 3248 "Closing existing connection during health check " + 3249 "processing because an error occurred while " + 3250 "attempting to set the SO_TIMEOUT", 3251 e); 3252 conn = handleDefunctConnection(conn); 3253 if (conn != null) 3254 { 3255 examinedConnections.add(conn); 3256 } 3257 continue; 3258 } 3259 } 3260 } 3261 } 3262 3263 try 3264 { 3265 hc.ensureConnectionValidForContinuedUse(conn); 3266 if (availableConnections.offer(conn)) 3267 { 3268 examinedConnections.add(conn); 3269 } 3270 else 3271 { 3272 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 3273 null, null); 3274 poolStatistics.incrementNumConnectionsClosedUnneeded(); 3275 Debug.debugConnectionPool(Level.INFO, this, conn, 3276 "Closing existing connection that passed health check " + 3277 "processing because the pool is already full", 3278 null); 3279 conn.terminate(null); 3280 } 3281 } 3282 catch (final Exception e) 3283 { 3284 Debug.debugException(e); 3285 numDefunct++; 3286 poolStatistics.incrementNumConnectionsClosedDefunct(); 3287 Debug.debugConnectionPool(Level.WARNING, this, conn, 3288 "Closing existing connection that failed health check " + 3289 "processing", 3290 e); 3291 conn = handleDefunctConnection(conn); 3292 if (conn != null) 3293 { 3294 examinedConnections.add(conn); 3295 } 3296 } 3297 } 3298 } 3299 3300 if (checkMinConnectionGoal) 3301 { 3302 try 3303 { 3304 final int neededConnections = 3305 minConnectionGoal - availableConnections.size(); 3306 for (int i=0; i < neededConnections; i++) 3307 { 3308 final LDAPConnection conn = createConnection(hc); 3309 if (! availableConnections.offer(conn)) 3310 { 3311 conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, 3312 null, null); 3313 poolStatistics.incrementNumConnectionsClosedUnneeded(); 3314 Debug.debugConnectionPool(Level.INFO, this, conn, 3315 "Closing a new connection that was created during health " + 3316 "check processing in achieve the minimum connection " + 3317 "goal, but the pool had already become full after the " + 3318 "connection was created", 3319 null); 3320 conn.terminate(null); 3321 break; 3322 } 3323 } 3324 } 3325 catch (final Exception e) 3326 { 3327 Debug.debugException(e); 3328 } 3329 } 3330 3331 return new LDAPConnectionPoolHealthCheckResult(numExamined, numExpired, 3332 numDefunct); 3333 } 3334 3335 3336 3337 /** 3338 * {@inheritDoc} 3339 */ 3340 @Override() 3341 public int getCurrentAvailableConnections() 3342 { 3343 return availableConnections.size(); 3344 } 3345 3346 3347 3348 /** 3349 * {@inheritDoc} 3350 */ 3351 @Override() 3352 public int getMaximumAvailableConnections() 3353 { 3354 return numConnections; 3355 } 3356 3357 3358 3359 /** 3360 * Retrieves the goal for the minimum number of available connections that the 3361 * pool should try to maintain for immediate use. If this goal is greater 3362 * than zero, then the health checking process will attempt to create enough 3363 * new connections to achieve this goal. 3364 * 3365 * @return The goal for the minimum number of available connections that the 3366 * pool should try to maintain for immediate use, or zero if it will 3367 * not try to maintain a minimum number of available connections. 3368 */ 3369 public int getMinimumAvailableConnectionGoal() 3370 { 3371 return minConnectionGoal; 3372 } 3373 3374 3375 3376 /** 3377 * Specifies the goal for the minimum number of available connections that the 3378 * pool should try to maintain for immediate use. If this goal is greater 3379 * than zero, then the health checking process will attempt to create enough 3380 * new connections to achieve this goal. 3381 * 3382 * @param goal The goal for the minimum number of available connections that 3383 * the pool should try to maintain for immediate use. A value 3384 * less than or equal to zero indicates that the pool should not 3385 * try to maintain a minimum number of available connections. 3386 */ 3387 public void setMinimumAvailableConnectionGoal(final int goal) 3388 { 3389 if (goal > numConnections) 3390 { 3391 minConnectionGoal = numConnections; 3392 } 3393 else if (goal > 0) 3394 { 3395 minConnectionGoal = goal; 3396 } 3397 else 3398 { 3399 minConnectionGoal = 0; 3400 } 3401 } 3402 3403 3404 3405 /** 3406 * {@inheritDoc} 3407 */ 3408 @Override() 3409 @NotNull() 3410 public LDAPConnectionPoolStatistics getConnectionPoolStatistics() 3411 { 3412 return poolStatistics; 3413 } 3414 3415 3416 3417 /** 3418 * Attempts to reduce the number of connections available for use in the pool. 3419 * Note that this will be a best-effort attempt to reach the desired number 3420 * of connections, as other threads interacting with the connection pool may 3421 * check out and/or release connections that cause the number of available 3422 * connections to fluctuate. 3423 * 3424 * @param connectionsToRetain The number of connections that should be 3425 * retained for use in the connection pool. 3426 */ 3427 public void shrinkPool(final int connectionsToRetain) 3428 { 3429 while (availableConnections.size() > connectionsToRetain) 3430 { 3431 final LDAPConnection conn; 3432 try 3433 { 3434 conn = getConnection(); 3435 } 3436 catch (final LDAPException le) 3437 { 3438 return; 3439 } 3440 3441 if (availableConnections.size() >= connectionsToRetain) 3442 { 3443 discardConnection(conn); 3444 } 3445 else 3446 { 3447 releaseConnection(conn); 3448 return; 3449 } 3450 } 3451 } 3452 3453 3454 3455 /** 3456 * Closes this connection pool in the event that it becomes unreferenced. 3457 * 3458 * @throws Throwable If an unexpected problem occurs. 3459 */ 3460 @Override() 3461 protected void finalize() 3462 throws Throwable 3463 { 3464 super.finalize(); 3465 3466 close(); 3467 } 3468 3469 3470 3471 /** 3472 * {@inheritDoc} 3473 */ 3474 @Override() 3475 public void toString(@NotNull final StringBuilder buffer) 3476 { 3477 buffer.append("LDAPConnectionPool("); 3478 3479 final String name = connectionPoolName; 3480 if (name != null) 3481 { 3482 buffer.append("name='"); 3483 buffer.append(name); 3484 buffer.append("', "); 3485 } 3486 3487 buffer.append("serverSet="); 3488 serverSet.toString(buffer); 3489 buffer.append(", maxConnections="); 3490 buffer.append(numConnections); 3491 buffer.append(')'); 3492 } 3493}