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