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