001/* 002 * Copyright 2009-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2019 Ping Identity Corporation 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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.EnumSet; 028import java.util.Iterator; 029import java.util.Map; 030import java.util.Set; 031import java.util.concurrent.ConcurrentHashMap; 032import java.util.concurrent.atomic.AtomicReference; 033 034import com.unboundid.ldap.sdk.schema.Schema; 035import com.unboundid.util.Debug; 036import com.unboundid.util.ObjectPair; 037import com.unboundid.util.StaticUtils; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040import com.unboundid.util.Validator; 041 042import static com.unboundid.ldap.sdk.LDAPMessages.*; 043 044 045 046/** 047 * This class provides an implementation of an LDAP connection pool which 048 * maintains a dedicated connection for each thread using the connection pool. 049 * Connections will be created on an on-demand basis, so that if a thread 050 * attempts to use this connection pool for the first time then a new connection 051 * will be created by that thread. This implementation eliminates the need to 052 * determine how best to size the connection pool, and it can eliminate 053 * contention among threads when trying to access a shared set of connections. 054 * All connections will be properly closed when the connection pool itself is 055 * closed, but if any thread which had previously used the connection pool stops 056 * running before the connection pool is closed, then the connection associated 057 * with that thread will also be closed by the Java finalizer. 058 * <BR><BR> 059 * If a thread obtains a connection to this connection pool, then that 060 * connection should not be made available to any other thread. Similarly, if 061 * a thread attempts to check out multiple connections from the pool, then the 062 * same connection instance will be returned each time. 063 * <BR><BR> 064 * The capabilities offered by this class are generally the same as those 065 * provided by the {@link LDAPConnectionPool} class, as is the manner in which 066 * applications should interact with it. See the class-level documentation for 067 * the {@code LDAPConnectionPool} class for additional information and examples. 068 * <BR><BR> 069 * One difference between this connection pool implementation and that provided 070 * by the {@link LDAPConnectionPool} class is that this implementation does not 071 * currently support periodic background health checks. You can define health 072 * checks that will be invoked when a new connection is created, just before it 073 * is checked out for use, just after it is released, and if an error occurs 074 * while using the connection, but it will not maintain a separate background 075 * thread 076 */ 077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 078public final class LDAPThreadLocalConnectionPool 079 extends AbstractConnectionPool 080{ 081 /** 082 * The default health check interval for this connection pool, which is set to 083 * 60000 milliseconds (60 seconds). 084 */ 085 private static final long DEFAULT_HEALTH_CHECK_INTERVAL = 60_000L; 086 087 088 089 // The types of operations that should be retried if they fail in a manner 090 // that may be the result of a connection that is no longer valid. 091 private final AtomicReference<Set<OperationType>> retryOperationTypes; 092 093 // Indicates whether this connection pool has been closed. 094 private volatile boolean closed; 095 096 // The bind request to use to perform authentication whenever a new connection 097 // is established. 098 private volatile BindRequest bindRequest; 099 100 // The map of connections maintained for this connection pool. 101 private final ConcurrentHashMap<Thread,LDAPConnection> connections; 102 103 // The health check implementation that should be used for this connection 104 // pool. 105 private LDAPConnectionPoolHealthCheck healthCheck; 106 107 // The thread that will be used to perform periodic background health checks 108 // for this connection pool. 109 private final LDAPConnectionPoolHealthCheckThread healthCheckThread; 110 111 // The statistics for this connection pool. 112 private final LDAPConnectionPoolStatistics poolStatistics; 113 114 // The length of time in milliseconds between periodic health checks against 115 // the available connections in this pool. 116 private volatile long healthCheckInterval; 117 118 // The time that the last expired connection was closed. 119 private volatile long lastExpiredDisconnectTime; 120 121 // The maximum length of time in milliseconds that a connection should be 122 // allowed to be established before terminating and re-establishing the 123 // connection. 124 private volatile long maxConnectionAge; 125 126 // The minimum length of time in milliseconds that must pass between 127 // disconnects of connections that have exceeded the maximum connection age. 128 private volatile long minDisconnectInterval; 129 130 // The schema that should be shared for connections in this pool, along with 131 // its expiration time. 132 private volatile ObjectPair<Long,Schema> pooledSchema; 133 134 // The post-connect processor for this connection pool, if any. 135 private final PostConnectProcessor postConnectProcessor; 136 137 // The server set to use for establishing connections for use by this pool. 138 private volatile ServerSet serverSet; 139 140 // The user-friendly name assigned to this connection pool. 141 private String connectionPoolName; 142 143 144 145 /** 146 * Creates a new LDAP thread-local connection pool in which all connections 147 * will be clones of the provided connection. 148 * 149 * @param connection The connection to use to provide the template for the 150 * other connections to be created. This connection will 151 * be included in the pool. It must not be {@code null}, 152 * and it must be established to the target server. It 153 * does not necessarily need to be authenticated if all 154 * connections in the pool are to be unauthenticated. 155 * 156 * @throws LDAPException If the provided connection cannot be used to 157 * initialize the pool. If this is thrown, then all 158 * connections associated with the pool (including the 159 * one provided as an argument) will be closed. 160 */ 161 public LDAPThreadLocalConnectionPool(final LDAPConnection connection) 162 throws LDAPException 163 { 164 this(connection, null); 165 } 166 167 168 169 /** 170 * Creates a new LDAP thread-local connection pool in which all connections 171 * will be clones of the provided connection. 172 * 173 * @param connection The connection to use to provide the template 174 * for the other connections to be created. 175 * This connection will be included in the pool. 176 * It must not be {@code null}, and it must be 177 * established to the target server. It does 178 * not necessarily need to be authenticated if 179 * all connections in the pool are to be 180 * unauthenticated. 181 * @param postConnectProcessor A processor that should be used to perform 182 * any post-connect processing for connections 183 * in this pool. It may be {@code null} if no 184 * special processing is needed. Note that this 185 * processing will not be invoked on the 186 * provided connection that will be used as the 187 * first connection in the pool. 188 * 189 * @throws LDAPException If the provided connection cannot be used to 190 * initialize the pool. If this is thrown, then all 191 * connections associated with the pool (including the 192 * one provided as an argument) will be closed. 193 */ 194 public LDAPThreadLocalConnectionPool(final LDAPConnection connection, 195 final PostConnectProcessor postConnectProcessor) 196 throws LDAPException 197 { 198 Validator.ensureNotNull(connection); 199 200 // NOTE: The post-connect processor (if any) will be used in the server 201 // set that we create rather than in the connection pool itself. 202 this.postConnectProcessor = null; 203 204 healthCheck = new LDAPConnectionPoolHealthCheck(); 205 healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; 206 poolStatistics = new LDAPConnectionPoolStatistics(this); 207 connectionPoolName = null; 208 retryOperationTypes = new AtomicReference<>( 209 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 210 211 if (! connection.isConnected()) 212 { 213 throw new LDAPException(ResultCode.PARAM_ERROR, 214 ERR_POOL_CONN_NOT_ESTABLISHED.get()); 215 } 216 217 218 bindRequest = connection.getLastBindRequest(); 219 serverSet = new SingleServerSet(connection.getConnectedAddress(), 220 connection.getConnectedPort(), 221 connection.getLastUsedSocketFactory(), 222 connection.getConnectionOptions(), null, 223 postConnectProcessor); 224 225 connections = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 226 connections.put(Thread.currentThread(), connection); 227 228 lastExpiredDisconnectTime = 0L; 229 maxConnectionAge = 0L; 230 closed = false; 231 minDisconnectInterval = 0L; 232 233 healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); 234 healthCheckThread.start(); 235 236 final LDAPConnectionOptions opts = connection.getConnectionOptions(); 237 if (opts.usePooledSchema()) 238 { 239 try 240 { 241 final Schema schema = connection.getSchema(); 242 if (schema != null) 243 { 244 connection.setCachedSchema(schema); 245 246 final long currentTime = System.currentTimeMillis(); 247 final long timeout = opts.getPooledSchemaTimeoutMillis(); 248 if ((timeout <= 0L) || (timeout+currentTime <= 0L)) 249 { 250 pooledSchema = new ObjectPair<>(Long.MAX_VALUE, schema); 251 } 252 else 253 { 254 pooledSchema = new ObjectPair<>(timeout+currentTime, schema); 255 } 256 } 257 } 258 catch (final Exception e) 259 { 260 Debug.debugException(e); 261 } 262 } 263 } 264 265 266 267 /** 268 * Creates a new LDAP thread-local connection pool which will use the provided 269 * server set and bind request for creating new connections. 270 * 271 * @param serverSet The server set to use to create the connections. 272 * It is acceptable for the server set to create the 273 * connections across multiple servers. 274 * @param bindRequest The bind request to use to authenticate the 275 * connections that are established. It may be 276 * {@code null} if no authentication should be 277 * performed on the connections. Note that if the 278 * server set is configured to perform 279 * authentication, this bind request should be the 280 * same bind request used by the server set. This 281 * is important because even though the server set 282 * may be used to perform the initial authentication 283 * on a newly established connection, this connection 284 * pool may still need to re-authenticate the 285 * connection. 286 */ 287 public LDAPThreadLocalConnectionPool(final ServerSet serverSet, 288 final BindRequest bindRequest) 289 { 290 this(serverSet, bindRequest, null); 291 } 292 293 294 295 /** 296 * Creates a new LDAP thread-local connection pool which will use the provided 297 * server set and bind request for creating new connections. 298 * 299 * @param serverSet The server set to use to create the 300 * connections. It is acceptable for the server 301 * set to create the connections across multiple 302 * servers. 303 * @param bindRequest The bind request to use to authenticate the 304 * connections that are established. It may be 305 * {@code null} if no authentication should be 306 * performed on the connections. Note that if 307 * the server set is configured to perform 308 * authentication, this bind request should be 309 * the same bind request used by the server set. 310 * This is important because even though the 311 * server set may be used to perform the 312 * initial authentication on a newly 313 * established connection, this connection 314 * pool may still need to re-authenticate the 315 * connection. 316 * @param postConnectProcessor A processor that should be used to perform 317 * any post-connect processing for connections 318 * in this pool. It may be {@code null} if no 319 * special processing is needed. Note that if 320 * the server set is configured with a 321 * non-{@code null} post-connect processor, then 322 * the post-connect processor provided to the 323 * pool must be {@code null}. 324 */ 325 public LDAPThreadLocalConnectionPool(final ServerSet serverSet, 326 final BindRequest bindRequest, 327 final PostConnectProcessor postConnectProcessor) 328 { 329 Validator.ensureNotNull(serverSet); 330 331 this.serverSet = serverSet; 332 this.bindRequest = bindRequest; 333 this.postConnectProcessor = postConnectProcessor; 334 335 if (serverSet.includesAuthentication()) 336 { 337 Validator.ensureTrue((bindRequest != null), 338 "LDAPThreadLocalConnectionPool.bindRequest must not be null if " + 339 "serverSet.includesAuthentication returns true"); 340 } 341 342 if (serverSet.includesPostConnectProcessing()) 343 { 344 Validator.ensureTrue((postConnectProcessor == null), 345 "LDAPThreadLocalConnectionPool.postConnectProcessor must be null " + 346 "if serverSet.includesPostConnectProcessing returns true."); 347 } 348 349 healthCheck = new LDAPConnectionPoolHealthCheck(); 350 healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL; 351 poolStatistics = new LDAPConnectionPoolStatistics(this); 352 connectionPoolName = null; 353 retryOperationTypes = new AtomicReference<>( 354 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 355 356 connections = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 357 358 lastExpiredDisconnectTime = 0L; 359 maxConnectionAge = 0L; 360 minDisconnectInterval = 0L; 361 closed = false; 362 363 healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this); 364 healthCheckThread.start(); 365 } 366 367 368 369 /** 370 * Creates a new LDAP connection for use in this pool. 371 * 372 * @return A new connection created for use in this pool. 373 * 374 * @throws LDAPException If a problem occurs while attempting to establish 375 * the connection. If a connection had been created, 376 * it will be closed. 377 */ 378 @SuppressWarnings("deprecation") 379 private LDAPConnection createConnection() 380 throws LDAPException 381 { 382 final LDAPConnection c; 383 try 384 { 385 c = serverSet.getConnection(healthCheck); 386 } 387 catch (final LDAPException le) 388 { 389 Debug.debugException(le); 390 poolStatistics.incrementNumFailedConnectionAttempts(); 391 throw le; 392 } 393 c.setConnectionPool(this); 394 395 396 // Auto-reconnect must be disabled for pooled connections, so turn it off 397 // if the associated connection options have it enabled for some reason. 398 LDAPConnectionOptions opts = c.getConnectionOptions(); 399 if (opts.autoReconnect()) 400 { 401 opts = opts.duplicate(); 402 opts.setAutoReconnect(false); 403 c.setConnectionOptions(opts); 404 } 405 406 407 // Invoke pre-authentication post-connect processing. 408 if (postConnectProcessor != null) 409 { 410 try 411 { 412 postConnectProcessor.processPreAuthenticatedConnection(c); 413 } 414 catch (final Exception e) 415 { 416 Debug.debugException(e); 417 418 try 419 { 420 poolStatistics.incrementNumFailedConnectionAttempts(); 421 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); 422 c.setClosed(); 423 } 424 catch (final Exception e2) 425 { 426 Debug.debugException(e2); 427 } 428 429 if (e instanceof LDAPException) 430 { 431 throw ((LDAPException) e); 432 } 433 else 434 { 435 throw new LDAPException(ResultCode.CONNECT_ERROR, 436 ERR_POOL_POST_CONNECT_ERROR.get( 437 StaticUtils.getExceptionMessage(e)), 438 e); 439 } 440 } 441 } 442 443 444 // Authenticate the connection if appropriate. 445 if ((bindRequest != null) && (! serverSet.includesAuthentication())) 446 { 447 BindResult bindResult; 448 try 449 { 450 bindResult = c.bind(bindRequest.duplicate()); 451 } 452 catch (final LDAPBindException lbe) 453 { 454 Debug.debugException(lbe); 455 bindResult = lbe.getBindResult(); 456 } 457 catch (final LDAPException le) 458 { 459 Debug.debugException(le); 460 bindResult = new BindResult(le); 461 } 462 463 try 464 { 465 healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult); 466 if (bindResult.getResultCode() != ResultCode.SUCCESS) 467 { 468 throw new LDAPBindException(bindResult); 469 } 470 } 471 catch (final LDAPException le) 472 { 473 Debug.debugException(le); 474 475 try 476 { 477 poolStatistics.incrementNumFailedConnectionAttempts(); 478 c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); 479 c.setClosed(); 480 } 481 catch (final Exception e) 482 { 483 Debug.debugException(e); 484 } 485 486 throw le; 487 } 488 } 489 490 491 // Invoke post-authentication post-connect processing. 492 if (postConnectProcessor != null) 493 { 494 try 495 { 496 postConnectProcessor.processPostAuthenticatedConnection(c); 497 } 498 catch (final Exception e) 499 { 500 Debug.debugException(e); 501 try 502 { 503 poolStatistics.incrementNumFailedConnectionAttempts(); 504 c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e); 505 c.setClosed(); 506 } 507 catch (final Exception e2) 508 { 509 Debug.debugException(e2); 510 } 511 512 if (e instanceof LDAPException) 513 { 514 throw ((LDAPException) e); 515 } 516 else 517 { 518 throw new LDAPException(ResultCode.CONNECT_ERROR, 519 ERR_POOL_POST_CONNECT_ERROR.get( 520 StaticUtils.getExceptionMessage(e)), 521 e); 522 } 523 } 524 } 525 526 527 // Get the pooled schema if appropriate. 528 if (opts.usePooledSchema()) 529 { 530 final long currentTime = System.currentTimeMillis(); 531 if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst())) 532 { 533 try 534 { 535 final Schema schema = c.getSchema(); 536 if (schema != null) 537 { 538 c.setCachedSchema(schema); 539 540 final long timeout = opts.getPooledSchemaTimeoutMillis(); 541 if ((timeout <= 0L) || (currentTime + timeout <= 0L)) 542 { 543 pooledSchema = new ObjectPair<>(Long.MAX_VALUE, schema); 544 } 545 else 546 { 547 pooledSchema = new ObjectPair<>((currentTime+timeout), schema); 548 } 549 } 550 } 551 catch (final Exception e) 552 { 553 Debug.debugException(e); 554 555 // There was a problem retrieving the schema from the server, but if 556 // we have an earlier copy then we can assume it's still valid. 557 if (pooledSchema != null) 558 { 559 c.setCachedSchema(pooledSchema.getSecond()); 560 } 561 } 562 } 563 else 564 { 565 c.setCachedSchema(pooledSchema.getSecond()); 566 } 567 } 568 569 570 // Finish setting up the connection. 571 c.setConnectionPoolName(connectionPoolName); 572 poolStatistics.incrementNumSuccessfulConnectionAttempts(); 573 574 return c; 575 } 576 577 578 579 /** 580 * {@inheritDoc} 581 */ 582 @Override() 583 public void close() 584 { 585 close(true, 1); 586 } 587 588 589 590 /** 591 * {@inheritDoc} 592 */ 593 @Override() 594 public void close(final boolean unbind, final int numThreads) 595 { 596 final boolean healthCheckThreadAlreadySignaled = closed; 597 closed = true; 598 healthCheckThread.stopRunning(! healthCheckThreadAlreadySignaled); 599 600 if (numThreads > 1) 601 { 602 final ArrayList<LDAPConnection> connList = 603 new ArrayList<>(connections.size()); 604 final Iterator<LDAPConnection> iterator = connections.values().iterator(); 605 while (iterator.hasNext()) 606 { 607 connList.add(iterator.next()); 608 iterator.remove(); 609 } 610 611 if (! connList.isEmpty()) 612 { 613 final ParallelPoolCloser closer = 614 new ParallelPoolCloser(connList, unbind, numThreads); 615 closer.closeConnections(); 616 } 617 } 618 else 619 { 620 final Iterator<Map.Entry<Thread,LDAPConnection>> iterator = 621 connections.entrySet().iterator(); 622 while (iterator.hasNext()) 623 { 624 final LDAPConnection conn = iterator.next().getValue(); 625 iterator.remove(); 626 627 poolStatistics.incrementNumConnectionsClosedUnneeded(); 628 conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null); 629 if (unbind) 630 { 631 conn.terminate(null); 632 } 633 else 634 { 635 conn.setClosed(); 636 } 637 } 638 } 639 } 640 641 642 643 /** 644 * {@inheritDoc} 645 */ 646 @Override() 647 public boolean isClosed() 648 { 649 return closed; 650 } 651 652 653 654 /** 655 * Processes a simple bind using a connection from this connection pool, and 656 * then reverts that authentication by re-binding as the same user used to 657 * authenticate new connections. If new connections are unauthenticated, then 658 * the subsequent bind will be an anonymous simple bind. This method attempts 659 * to ensure that processing the provided bind operation does not have a 660 * lasting impact the authentication state of the connection used to process 661 * it. 662 * <BR><BR> 663 * If the second bind attempt (the one used to restore the authentication 664 * identity) fails, the connection will be closed as defunct so that a new 665 * connection will be created to take its place. 666 * 667 * @param bindDN The bind DN for the simple bind request. 668 * @param password The password for the simple bind request. 669 * @param controls The optional set of controls for the simple bind request. 670 * 671 * @return The result of processing the provided bind operation. 672 * 673 * @throws LDAPException If the server rejects the bind request, or if a 674 * problem occurs while sending the request or reading 675 * the response. 676 */ 677 public BindResult bindAndRevertAuthentication(final String bindDN, 678 final String password, 679 final Control... controls) 680 throws LDAPException 681 { 682 return bindAndRevertAuthentication( 683 new SimpleBindRequest(bindDN, password, controls)); 684 } 685 686 687 688 /** 689 * Processes the provided bind request using a connection from this connection 690 * pool, and then reverts that authentication by re-binding as the same user 691 * used to authenticate new connections. If new connections are 692 * unauthenticated, then the subsequent bind will be an anonymous simple bind. 693 * This method attempts to ensure that processing the provided bind operation 694 * does not have a lasting impact the authentication state of the connection 695 * used to process it. 696 * <BR><BR> 697 * If the second bind attempt (the one used to restore the authentication 698 * identity) fails, the connection will be closed as defunct so that a new 699 * connection will be created to take its place. 700 * 701 * @param bindRequest The bind request to be processed. It must not be 702 * {@code null}. 703 * 704 * @return The result of processing the provided bind operation. 705 * 706 * @throws LDAPException If the server rejects the bind request, or if a 707 * problem occurs while sending the request or reading 708 * the response. 709 */ 710 public BindResult bindAndRevertAuthentication(final BindRequest bindRequest) 711 throws LDAPException 712 { 713 LDAPConnection conn = getConnection(); 714 715 try 716 { 717 final BindResult result = conn.bind(bindRequest); 718 releaseAndReAuthenticateConnection(conn); 719 return result; 720 } 721 catch (final Throwable t) 722 { 723 Debug.debugException(t); 724 725 if (t instanceof LDAPException) 726 { 727 final LDAPException le = (LDAPException) t; 728 729 boolean shouldThrow; 730 try 731 { 732 healthCheck.ensureConnectionValidAfterException(conn, le); 733 734 // The above call will throw an exception if the connection doesn't 735 // seem to be valid, so if we've gotten here then we should assume 736 // that it is valid and we will pass the exception onto the client 737 // without retrying the operation. 738 releaseAndReAuthenticateConnection(conn); 739 shouldThrow = true; 740 } 741 catch (final Exception e) 742 { 743 Debug.debugException(e); 744 745 // This implies that the connection is not valid. If the pool is 746 // configured to re-try bind operations on a newly-established 747 // connection, then that will be done later in this method. 748 // Otherwise, release the connection as defunct and pass the bind 749 // exception onto the client. 750 if (! getOperationTypesToRetryDueToInvalidConnections().contains( 751 OperationType.BIND)) 752 { 753 releaseDefunctConnection(conn); 754 shouldThrow = true; 755 } 756 else 757 { 758 shouldThrow = false; 759 } 760 } 761 762 if (shouldThrow) 763 { 764 throw le; 765 } 766 } 767 else 768 { 769 releaseDefunctConnection(conn); 770 StaticUtils.rethrowIfError(t); 771 throw new LDAPException(ResultCode.LOCAL_ERROR, 772 ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); 773 } 774 } 775 776 777 // If we've gotten here, then the bind operation should be re-tried on a 778 // newly-established connection. 779 conn = replaceDefunctConnection(conn); 780 781 try 782 { 783 final BindResult result = conn.bind(bindRequest); 784 releaseAndReAuthenticateConnection(conn); 785 return result; 786 } 787 catch (final Throwable t) 788 { 789 Debug.debugException(t); 790 791 if (t instanceof LDAPException) 792 { 793 final LDAPException le = (LDAPException) t; 794 795 try 796 { 797 healthCheck.ensureConnectionValidAfterException(conn, le); 798 releaseAndReAuthenticateConnection(conn); 799 } 800 catch (final Exception e) 801 { 802 Debug.debugException(e); 803 releaseDefunctConnection(conn); 804 } 805 806 throw le; 807 } 808 else 809 { 810 releaseDefunctConnection(conn); 811 StaticUtils.rethrowIfError(t); 812 throw new LDAPException(ResultCode.LOCAL_ERROR, 813 ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); 814 } 815 } 816 } 817 818 819 820 /** 821 * {@inheritDoc} 822 */ 823 @Override() 824 public LDAPConnection getConnection() 825 throws LDAPException 826 { 827 final Thread t = Thread.currentThread(); 828 LDAPConnection conn = connections.get(t); 829 830 if (closed) 831 { 832 if (conn != null) 833 { 834 conn.terminate(null); 835 connections.remove(t); 836 } 837 838 poolStatistics.incrementNumFailedCheckouts(); 839 throw new LDAPException(ResultCode.CONNECT_ERROR, 840 ERR_POOL_CLOSED.get()); 841 } 842 843 boolean created = false; 844 if ((conn == null) || (! conn.isConnected())) 845 { 846 conn = createConnection(); 847 connections.put(t, conn); 848 created = true; 849 } 850 851 try 852 { 853 healthCheck.ensureConnectionValidForCheckout(conn); 854 if (created) 855 { 856 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 857 } 858 else 859 { 860 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 861 } 862 return conn; 863 } 864 catch (final LDAPException le) 865 { 866 Debug.debugException(le); 867 868 conn.setClosed(); 869 connections.remove(t); 870 871 if (created) 872 { 873 poolStatistics.incrementNumFailedCheckouts(); 874 throw le; 875 } 876 } 877 878 try 879 { 880 conn = createConnection(); 881 healthCheck.ensureConnectionValidForCheckout(conn); 882 connections.put(t, conn); 883 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 884 return conn; 885 } 886 catch (final LDAPException le) 887 { 888 Debug.debugException(le); 889 890 poolStatistics.incrementNumFailedCheckouts(); 891 892 if (conn != null) 893 { 894 conn.setClosed(); 895 } 896 897 throw le; 898 } 899 } 900 901 902 903 /** 904 * {@inheritDoc} 905 */ 906 @Override() 907 public void releaseConnection(final LDAPConnection connection) 908 { 909 if (connection == null) 910 { 911 return; 912 } 913 914 connection.setConnectionPoolName(connectionPoolName); 915 if (connectionIsExpired(connection)) 916 { 917 try 918 { 919 final LDAPConnection newConnection = createConnection(); 920 connections.put(Thread.currentThread(), newConnection); 921 922 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 923 null, null); 924 connection.terminate(null); 925 poolStatistics.incrementNumConnectionsClosedExpired(); 926 lastExpiredDisconnectTime = System.currentTimeMillis(); 927 } 928 catch (final LDAPException le) 929 { 930 Debug.debugException(le); 931 } 932 } 933 934 try 935 { 936 healthCheck.ensureConnectionValidForRelease(connection); 937 } 938 catch (final LDAPException le) 939 { 940 releaseDefunctConnection(connection); 941 return; 942 } 943 944 poolStatistics.incrementNumReleasedValid(); 945 946 if (closed) 947 { 948 close(); 949 } 950 } 951 952 953 954 /** 955 * Performs a bind on the provided connection before releasing it back to the 956 * pool, so that it will be authenticated as the same user as 957 * newly-established connections. If newly-established connections are 958 * unauthenticated, then this method will perform an anonymous simple bind to 959 * ensure that the resulting connection is unauthenticated. 960 * 961 * Releases the provided connection back to this pool. 962 * 963 * @param connection The connection to be released back to the pool after 964 * being re-authenticated. 965 */ 966 public void releaseAndReAuthenticateConnection( 967 final LDAPConnection connection) 968 { 969 if (connection == null) 970 { 971 return; 972 } 973 974 try 975 { 976 BindResult bindResult; 977 try 978 { 979 if (bindRequest == null) 980 { 981 bindResult = connection.bind("", ""); 982 } 983 else 984 { 985 bindResult = connection.bind(bindRequest.duplicate()); 986 } 987 } 988 catch (final LDAPBindException lbe) 989 { 990 Debug.debugException(lbe); 991 bindResult = lbe.getBindResult(); 992 } 993 994 try 995 { 996 healthCheck.ensureConnectionValidAfterAuthentication(connection, 997 bindResult); 998 if (bindResult.getResultCode() != ResultCode.SUCCESS) 999 { 1000 throw new LDAPBindException(bindResult); 1001 } 1002 } 1003 catch (final LDAPException le) 1004 { 1005 Debug.debugException(le); 1006 1007 try 1008 { 1009 connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); 1010 connection.terminate(null); 1011 releaseDefunctConnection(connection); 1012 } 1013 catch (final Exception e) 1014 { 1015 Debug.debugException(e); 1016 } 1017 1018 throw le; 1019 } 1020 1021 releaseConnection(connection); 1022 } 1023 catch (final Exception e) 1024 { 1025 Debug.debugException(e); 1026 releaseDefunctConnection(connection); 1027 } 1028 } 1029 1030 1031 1032 /** 1033 * {@inheritDoc} 1034 */ 1035 @Override() 1036 public void releaseDefunctConnection(final LDAPConnection connection) 1037 { 1038 if (connection == null) 1039 { 1040 return; 1041 } 1042 1043 connection.setConnectionPoolName(connectionPoolName); 1044 poolStatistics.incrementNumConnectionsClosedDefunct(); 1045 handleDefunctConnection(connection); 1046 } 1047 1048 1049 1050 /** 1051 * Performs the real work of terminating a defunct connection and replacing it 1052 * with a new connection if possible. 1053 * 1054 * @param connection The defunct connection to be replaced. 1055 */ 1056 private void handleDefunctConnection(final LDAPConnection connection) 1057 { 1058 final Thread t = Thread.currentThread(); 1059 1060 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1061 null); 1062 connection.setClosed(); 1063 connections.remove(t); 1064 1065 if (closed) 1066 { 1067 return; 1068 } 1069 1070 try 1071 { 1072 final LDAPConnection conn = createConnection(); 1073 connections.put(t, conn); 1074 } 1075 catch (final LDAPException le) 1076 { 1077 Debug.debugException(le); 1078 } 1079 } 1080 1081 1082 1083 /** 1084 * {@inheritDoc} 1085 */ 1086 @Override() 1087 public LDAPConnection replaceDefunctConnection( 1088 final LDAPConnection connection) 1089 throws LDAPException 1090 { 1091 poolStatistics.incrementNumConnectionsClosedDefunct(); 1092 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1093 null); 1094 connection.setClosed(); 1095 connections.remove(Thread.currentThread(), connection); 1096 1097 if (closed) 1098 { 1099 throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); 1100 } 1101 1102 final LDAPConnection newConnection = createConnection(); 1103 connections.put(Thread.currentThread(), newConnection); 1104 return newConnection; 1105 } 1106 1107 1108 1109 /** 1110 * {@inheritDoc} 1111 */ 1112 @Override() 1113 public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections() 1114 { 1115 return retryOperationTypes.get(); 1116 } 1117 1118 1119 1120 /** 1121 * {@inheritDoc} 1122 */ 1123 @Override() 1124 public void setRetryFailedOperationsDueToInvalidConnections( 1125 final Set<OperationType> operationTypes) 1126 { 1127 if ((operationTypes == null) || operationTypes.isEmpty()) 1128 { 1129 retryOperationTypes.set( 1130 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 1131 } 1132 else 1133 { 1134 final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class); 1135 s.addAll(operationTypes); 1136 retryOperationTypes.set(Collections.unmodifiableSet(s)); 1137 } 1138 } 1139 1140 1141 1142 /** 1143 * Indicates whether the provided connection should be considered expired. 1144 * 1145 * @param connection The connection for which to make the determination. 1146 * 1147 * @return {@code true} if the provided connection should be considered 1148 * expired, or {@code false} if not. 1149 */ 1150 private boolean connectionIsExpired(final LDAPConnection connection) 1151 { 1152 // If connection expiration is not enabled, then there is nothing to do. 1153 if (maxConnectionAge <= 0L) 1154 { 1155 return false; 1156 } 1157 1158 // If there is a minimum disconnect interval, then make sure that we have 1159 // not closed another expired connection too recently. 1160 final long currentTime = System.currentTimeMillis(); 1161 if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) 1162 { 1163 return false; 1164 } 1165 1166 // Get the age of the connection and see if it is expired. 1167 final long connectionAge = currentTime - connection.getConnectTime(); 1168 return (connectionAge > maxConnectionAge); 1169 } 1170 1171 1172 1173 /** 1174 * Specifies the bind request that will be used to authenticate subsequent new 1175 * connections that are established by this connection pool. The 1176 * authentication state for existing connections will not be altered unless 1177 * one of the {@code bindAndRevertAuthentication} or 1178 * {@code releaseAndReAuthenticateConnection} methods are invoked on those 1179 * connections. 1180 * 1181 * @param bindRequest The bind request that will be used to authenticate new 1182 * connections that are established by this pool, or 1183 * that will be applied to existing connections via the 1184 * {@code bindAndRevertAuthentication} or 1185 * {@code releaseAndReAuthenticateConnection} method. It 1186 * may be {@code null} if new connections should be 1187 * unauthenticated. 1188 */ 1189 public void setBindRequest(final BindRequest bindRequest) 1190 { 1191 this.bindRequest = bindRequest; 1192 } 1193 1194 1195 1196 /** 1197 * Specifies the server set that should be used to establish new connections 1198 * for use in this connection pool. Existing connections will not be 1199 * affected. 1200 * 1201 * @param serverSet The server set that should be used to establish new 1202 * connections for use in this connection pool. It must 1203 * not be {@code null}. 1204 */ 1205 public void setServerSet(final ServerSet serverSet) 1206 { 1207 Validator.ensureNotNull(serverSet); 1208 this.serverSet = serverSet; 1209 } 1210 1211 1212 1213 /** 1214 * {@inheritDoc} 1215 */ 1216 @Override() 1217 public String getConnectionPoolName() 1218 { 1219 return connectionPoolName; 1220 } 1221 1222 1223 1224 /** 1225 * {@inheritDoc} 1226 */ 1227 @Override() 1228 public void setConnectionPoolName(final String connectionPoolName) 1229 { 1230 this.connectionPoolName = connectionPoolName; 1231 } 1232 1233 1234 1235 /** 1236 * Retrieves the maximum length of time in milliseconds that a connection in 1237 * this pool may be established before it is closed and replaced with another 1238 * connection. 1239 * 1240 * @return The maximum length of time in milliseconds that a connection in 1241 * this pool may be established before it is closed and replaced with 1242 * another connection, or {@code 0L} if no maximum age should be 1243 * enforced. 1244 */ 1245 public long getMaxConnectionAgeMillis() 1246 { 1247 return maxConnectionAge; 1248 } 1249 1250 1251 1252 /** 1253 * Specifies the maximum length of time in milliseconds that a connection in 1254 * this pool may be established before it should be closed and replaced with 1255 * another connection. 1256 * 1257 * @param maxConnectionAge The maximum length of time in milliseconds that a 1258 * connection in this pool may be established before 1259 * it should be closed and replaced with another 1260 * connection. A value of zero indicates that no 1261 * maximum age should be enforced. 1262 */ 1263 public void setMaxConnectionAgeMillis(final long maxConnectionAge) 1264 { 1265 if (maxConnectionAge > 0L) 1266 { 1267 this.maxConnectionAge = maxConnectionAge; 1268 } 1269 else 1270 { 1271 this.maxConnectionAge = 0L; 1272 } 1273 } 1274 1275 1276 1277 /** 1278 * Retrieves the minimum length of time in milliseconds that should pass 1279 * between connections closed because they have been established for longer 1280 * than the maximum connection age. 1281 * 1282 * @return The minimum length of time in milliseconds that should pass 1283 * between connections closed because they have been established for 1284 * longer than the maximum connection age, or {@code 0L} if expired 1285 * connections may be closed as quickly as they are identified. 1286 */ 1287 public long getMinDisconnectIntervalMillis() 1288 { 1289 return minDisconnectInterval; 1290 } 1291 1292 1293 1294 /** 1295 * Specifies the minimum length of time in milliseconds that should pass 1296 * between connections closed because they have been established for longer 1297 * than the maximum connection age. 1298 * 1299 * @param minDisconnectInterval The minimum length of time in milliseconds 1300 * that should pass between connections closed 1301 * because they have been established for 1302 * longer than the maximum connection age. A 1303 * value less than or equal to zero indicates 1304 * that no minimum time should be enforced. 1305 */ 1306 public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) 1307 { 1308 if (minDisconnectInterval > 0) 1309 { 1310 this.minDisconnectInterval = minDisconnectInterval; 1311 } 1312 else 1313 { 1314 this.minDisconnectInterval = 0L; 1315 } 1316 } 1317 1318 1319 1320 /** 1321 * {@inheritDoc} 1322 */ 1323 @Override() 1324 public LDAPConnectionPoolHealthCheck getHealthCheck() 1325 { 1326 return healthCheck; 1327 } 1328 1329 1330 1331 /** 1332 * Sets the health check implementation for this connection pool. 1333 * 1334 * @param healthCheck The health check implementation for this connection 1335 * pool. It must not be {@code null}. 1336 */ 1337 public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck) 1338 { 1339 Validator.ensureNotNull(healthCheck); 1340 this.healthCheck = healthCheck; 1341 } 1342 1343 1344 1345 /** 1346 * {@inheritDoc} 1347 */ 1348 @Override() 1349 public long getHealthCheckIntervalMillis() 1350 { 1351 return healthCheckInterval; 1352 } 1353 1354 1355 1356 /** 1357 * {@inheritDoc} 1358 */ 1359 @Override() 1360 public void setHealthCheckIntervalMillis(final long healthCheckInterval) 1361 { 1362 Validator.ensureTrue(healthCheckInterval > 0L, 1363 "LDAPConnectionPool.healthCheckInterval must be greater than 0."); 1364 this.healthCheckInterval = healthCheckInterval; 1365 healthCheckThread.wakeUp(); 1366 } 1367 1368 1369 1370 /** 1371 * {@inheritDoc} 1372 */ 1373 @Override() 1374 protected void doHealthCheck() 1375 { 1376 final Iterator<Map.Entry<Thread,LDAPConnection>> iterator = 1377 connections.entrySet().iterator(); 1378 while (iterator.hasNext()) 1379 { 1380 final Map.Entry<Thread,LDAPConnection> e = iterator.next(); 1381 final Thread t = e.getKey(); 1382 final LDAPConnection c = e.getValue(); 1383 1384 if (! t.isAlive()) 1385 { 1386 c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null, 1387 null); 1388 c.terminate(null); 1389 iterator.remove(); 1390 } 1391 } 1392 } 1393 1394 1395 1396 /** 1397 * {@inheritDoc} 1398 */ 1399 @Override() 1400 public int getCurrentAvailableConnections() 1401 { 1402 return -1; 1403 } 1404 1405 1406 1407 /** 1408 * {@inheritDoc} 1409 */ 1410 @Override() 1411 public int getMaximumAvailableConnections() 1412 { 1413 return -1; 1414 } 1415 1416 1417 1418 /** 1419 * {@inheritDoc} 1420 */ 1421 @Override() 1422 public LDAPConnectionPoolStatistics getConnectionPoolStatistics() 1423 { 1424 return poolStatistics; 1425 } 1426 1427 1428 1429 /** 1430 * Closes this connection pool in the event that it becomes unreferenced. 1431 * 1432 * @throws Throwable If an unexpected problem occurs. 1433 */ 1434 @Override() 1435 protected void finalize() 1436 throws Throwable 1437 { 1438 super.finalize(); 1439 1440 close(); 1441 } 1442 1443 1444 1445 /** 1446 * {@inheritDoc} 1447 */ 1448 @Override() 1449 public void toString(final StringBuilder buffer) 1450 { 1451 buffer.append("LDAPThreadLocalConnectionPool("); 1452 1453 final String name = connectionPoolName; 1454 if (name != null) 1455 { 1456 buffer.append("name='"); 1457 buffer.append(name); 1458 buffer.append("', "); 1459 } 1460 1461 buffer.append("serverSet="); 1462 serverSet.toString(buffer); 1463 buffer.append(')'); 1464 } 1465}