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