001/* 002 * Copyright 2009-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2018 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<>(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<>(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 throw new LDAPException(ResultCode.LOCAL_ERROR, 771 ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); 772 } 773 } 774 775 776 // If we've gotten here, then the bind operation should be re-tried on a 777 // newly-established connection. 778 conn = replaceDefunctConnection(conn); 779 780 try 781 { 782 final BindResult result = conn.bind(bindRequest); 783 releaseAndReAuthenticateConnection(conn); 784 return result; 785 } 786 catch (final Throwable t) 787 { 788 Debug.debugException(t); 789 790 if (t instanceof LDAPException) 791 { 792 final LDAPException le = (LDAPException) t; 793 794 try 795 { 796 healthCheck.ensureConnectionValidAfterException(conn, le); 797 releaseAndReAuthenticateConnection(conn); 798 } 799 catch (final Exception e) 800 { 801 Debug.debugException(e); 802 releaseDefunctConnection(conn); 803 } 804 805 throw le; 806 } 807 else 808 { 809 releaseDefunctConnection(conn); 810 throw new LDAPException(ResultCode.LOCAL_ERROR, 811 ERR_POOL_OP_EXCEPTION.get(StaticUtils.getExceptionMessage(t)), t); 812 } 813 } 814 } 815 816 817 818 /** 819 * {@inheritDoc} 820 */ 821 @Override() 822 public LDAPConnection getConnection() 823 throws LDAPException 824 { 825 final Thread t = Thread.currentThread(); 826 LDAPConnection conn = connections.get(t); 827 828 if (closed) 829 { 830 if (conn != null) 831 { 832 conn.terminate(null); 833 connections.remove(t); 834 } 835 836 poolStatistics.incrementNumFailedCheckouts(); 837 throw new LDAPException(ResultCode.CONNECT_ERROR, 838 ERR_POOL_CLOSED.get()); 839 } 840 841 boolean created = false; 842 if ((conn == null) || (! conn.isConnected())) 843 { 844 conn = createConnection(); 845 connections.put(t, conn); 846 created = true; 847 } 848 849 try 850 { 851 healthCheck.ensureConnectionValidForCheckout(conn); 852 if (created) 853 { 854 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 855 } 856 else 857 { 858 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 859 } 860 return conn; 861 } 862 catch (final LDAPException le) 863 { 864 Debug.debugException(le); 865 866 conn.setClosed(); 867 connections.remove(t); 868 869 if (created) 870 { 871 poolStatistics.incrementNumFailedCheckouts(); 872 throw le; 873 } 874 } 875 876 try 877 { 878 conn = createConnection(); 879 healthCheck.ensureConnectionValidForCheckout(conn); 880 connections.put(t, conn); 881 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 882 return conn; 883 } 884 catch (final LDAPException le) 885 { 886 Debug.debugException(le); 887 888 poolStatistics.incrementNumFailedCheckouts(); 889 890 if (conn != null) 891 { 892 conn.setClosed(); 893 } 894 895 throw le; 896 } 897 } 898 899 900 901 /** 902 * {@inheritDoc} 903 */ 904 @Override() 905 public void releaseConnection(final LDAPConnection connection) 906 { 907 if (connection == null) 908 { 909 return; 910 } 911 912 connection.setConnectionPoolName(connectionPoolName); 913 if (connectionIsExpired(connection)) 914 { 915 try 916 { 917 final LDAPConnection newConnection = createConnection(); 918 connections.put(Thread.currentThread(), newConnection); 919 920 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 921 null, null); 922 connection.terminate(null); 923 poolStatistics.incrementNumConnectionsClosedExpired(); 924 lastExpiredDisconnectTime = System.currentTimeMillis(); 925 } 926 catch (final LDAPException le) 927 { 928 Debug.debugException(le); 929 } 930 } 931 932 try 933 { 934 healthCheck.ensureConnectionValidForRelease(connection); 935 } 936 catch (final LDAPException le) 937 { 938 releaseDefunctConnection(connection); 939 return; 940 } 941 942 poolStatistics.incrementNumReleasedValid(); 943 944 if (closed) 945 { 946 close(); 947 } 948 } 949 950 951 952 /** 953 * Performs a bind on the provided connection before releasing it back to the 954 * pool, so that it will be authenticated as the same user as 955 * newly-established connections. If newly-established connections are 956 * unauthenticated, then this method will perform an anonymous simple bind to 957 * ensure that the resulting connection is unauthenticated. 958 * 959 * Releases the provided connection back to this pool. 960 * 961 * @param connection The connection to be released back to the pool after 962 * being re-authenticated. 963 */ 964 public void releaseAndReAuthenticateConnection( 965 final LDAPConnection connection) 966 { 967 if (connection == null) 968 { 969 return; 970 } 971 972 try 973 { 974 BindResult bindResult; 975 try 976 { 977 if (bindRequest == null) 978 { 979 bindResult = connection.bind("", ""); 980 } 981 else 982 { 983 bindResult = connection.bind(bindRequest.duplicate()); 984 } 985 } 986 catch (final LDAPBindException lbe) 987 { 988 Debug.debugException(lbe); 989 bindResult = lbe.getBindResult(); 990 } 991 992 try 993 { 994 healthCheck.ensureConnectionValidAfterAuthentication(connection, 995 bindResult); 996 if (bindResult.getResultCode() != ResultCode.SUCCESS) 997 { 998 throw new LDAPBindException(bindResult); 999 } 1000 } 1001 catch (final LDAPException le) 1002 { 1003 Debug.debugException(le); 1004 1005 try 1006 { 1007 connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le); 1008 connection.terminate(null); 1009 releaseDefunctConnection(connection); 1010 } 1011 catch (final Exception e) 1012 { 1013 Debug.debugException(e); 1014 } 1015 1016 throw le; 1017 } 1018 1019 releaseConnection(connection); 1020 } 1021 catch (final Exception e) 1022 { 1023 Debug.debugException(e); 1024 releaseDefunctConnection(connection); 1025 } 1026 } 1027 1028 1029 1030 /** 1031 * {@inheritDoc} 1032 */ 1033 @Override() 1034 public void releaseDefunctConnection(final LDAPConnection connection) 1035 { 1036 if (connection == null) 1037 { 1038 return; 1039 } 1040 1041 connection.setConnectionPoolName(connectionPoolName); 1042 poolStatistics.incrementNumConnectionsClosedDefunct(); 1043 handleDefunctConnection(connection); 1044 } 1045 1046 1047 1048 /** 1049 * Performs the real work of terminating a defunct connection and replacing it 1050 * with a new connection if possible. 1051 * 1052 * @param connection The defunct connection to be replaced. 1053 */ 1054 private void handleDefunctConnection(final LDAPConnection connection) 1055 { 1056 final Thread t = Thread.currentThread(); 1057 1058 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1059 null); 1060 connection.setClosed(); 1061 connections.remove(t); 1062 1063 if (closed) 1064 { 1065 return; 1066 } 1067 1068 try 1069 { 1070 final LDAPConnection conn = createConnection(); 1071 connections.put(t, conn); 1072 } 1073 catch (final LDAPException le) 1074 { 1075 Debug.debugException(le); 1076 } 1077 } 1078 1079 1080 1081 /** 1082 * {@inheritDoc} 1083 */ 1084 @Override() 1085 public LDAPConnection replaceDefunctConnection( 1086 final LDAPConnection connection) 1087 throws LDAPException 1088 { 1089 poolStatistics.incrementNumConnectionsClosedDefunct(); 1090 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 1091 null); 1092 connection.setClosed(); 1093 connections.remove(Thread.currentThread(), connection); 1094 1095 if (closed) 1096 { 1097 throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); 1098 } 1099 1100 final LDAPConnection newConnection = createConnection(); 1101 connections.put(Thread.currentThread(), newConnection); 1102 return newConnection; 1103 } 1104 1105 1106 1107 /** 1108 * {@inheritDoc} 1109 */ 1110 @Override() 1111 public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections() 1112 { 1113 return retryOperationTypes.get(); 1114 } 1115 1116 1117 1118 /** 1119 * {@inheritDoc} 1120 */ 1121 @Override() 1122 public void setRetryFailedOperationsDueToInvalidConnections( 1123 final Set<OperationType> operationTypes) 1124 { 1125 if ((operationTypes == null) || operationTypes.isEmpty()) 1126 { 1127 retryOperationTypes.set( 1128 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 1129 } 1130 else 1131 { 1132 final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class); 1133 s.addAll(operationTypes); 1134 retryOperationTypes.set(Collections.unmodifiableSet(s)); 1135 } 1136 } 1137 1138 1139 1140 /** 1141 * Indicates whether the provided connection should be considered expired. 1142 * 1143 * @param connection The connection for which to make the determination. 1144 * 1145 * @return {@code true} if the provided connection should be considered 1146 * expired, or {@code false} if not. 1147 */ 1148 private boolean connectionIsExpired(final LDAPConnection connection) 1149 { 1150 // If connection expiration is not enabled, then there is nothing to do. 1151 if (maxConnectionAge <= 0L) 1152 { 1153 return false; 1154 } 1155 1156 // If there is a minimum disconnect interval, then make sure that we have 1157 // not closed another expired connection too recently. 1158 final long currentTime = System.currentTimeMillis(); 1159 if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) 1160 { 1161 return false; 1162 } 1163 1164 // Get the age of the connection and see if it is expired. 1165 final long connectionAge = currentTime - connection.getConnectTime(); 1166 return (connectionAge > maxConnectionAge); 1167 } 1168 1169 1170 1171 /** 1172 * Specifies the bind request that will be used to authenticate subsequent new 1173 * connections that are established by this connection pool. The 1174 * authentication state for existing connections will not be altered unless 1175 * one of the {@code bindAndRevertAuthentication} or 1176 * {@code releaseAndReAuthenticateConnection} methods are invoked on those 1177 * connections. 1178 * 1179 * @param bindRequest The bind request that will be used to authenticate new 1180 * connections that are established by this pool, or 1181 * that will be applied to existing connections via the 1182 * {@code bindAndRevertAuthentication} or 1183 * {@code releaseAndReAuthenticateConnection} method. It 1184 * may be {@code null} if new connections should be 1185 * unauthenticated. 1186 */ 1187 public void setBindRequest(final BindRequest bindRequest) 1188 { 1189 this.bindRequest = bindRequest; 1190 } 1191 1192 1193 1194 /** 1195 * Specifies the server set that should be used to establish new connections 1196 * for use in this connection pool. Existing connections will not be 1197 * affected. 1198 * 1199 * @param serverSet The server set that should be used to establish new 1200 * connections for use in this connection pool. It must 1201 * not be {@code null}. 1202 */ 1203 public void setServerSet(final ServerSet serverSet) 1204 { 1205 Validator.ensureNotNull(serverSet); 1206 this.serverSet = serverSet; 1207 } 1208 1209 1210 1211 /** 1212 * {@inheritDoc} 1213 */ 1214 @Override() 1215 public String getConnectionPoolName() 1216 { 1217 return connectionPoolName; 1218 } 1219 1220 1221 1222 /** 1223 * {@inheritDoc} 1224 */ 1225 @Override() 1226 public void setConnectionPoolName(final String connectionPoolName) 1227 { 1228 this.connectionPoolName = connectionPoolName; 1229 } 1230 1231 1232 1233 /** 1234 * Retrieves the maximum length of time in milliseconds that a connection in 1235 * this pool may be established before it is closed and replaced with another 1236 * connection. 1237 * 1238 * @return The maximum length of time in milliseconds that a connection in 1239 * this pool may be established before it is closed and replaced with 1240 * another connection, or {@code 0L} if no maximum age should be 1241 * enforced. 1242 */ 1243 public long getMaxConnectionAgeMillis() 1244 { 1245 return maxConnectionAge; 1246 } 1247 1248 1249 1250 /** 1251 * Specifies the maximum length of time in milliseconds that a connection in 1252 * this pool may be established before it should be closed and replaced with 1253 * another connection. 1254 * 1255 * @param maxConnectionAge The maximum length of time in milliseconds that a 1256 * connection in this pool may be established before 1257 * it should be closed and replaced with another 1258 * connection. A value of zero indicates that no 1259 * maximum age should be enforced. 1260 */ 1261 public void setMaxConnectionAgeMillis(final long maxConnectionAge) 1262 { 1263 if (maxConnectionAge > 0L) 1264 { 1265 this.maxConnectionAge = maxConnectionAge; 1266 } 1267 else 1268 { 1269 this.maxConnectionAge = 0L; 1270 } 1271 } 1272 1273 1274 1275 /** 1276 * Retrieves the minimum length of time in milliseconds that should pass 1277 * between connections closed because they have been established for longer 1278 * than the maximum connection age. 1279 * 1280 * @return The minimum length of time in milliseconds that should pass 1281 * between connections closed because they have been established for 1282 * longer than the maximum connection age, or {@code 0L} if expired 1283 * connections may be closed as quickly as they are identified. 1284 */ 1285 public long getMinDisconnectIntervalMillis() 1286 { 1287 return minDisconnectInterval; 1288 } 1289 1290 1291 1292 /** 1293 * Specifies the minimum length of time in milliseconds that should pass 1294 * between connections closed because they have been established for longer 1295 * than the maximum connection age. 1296 * 1297 * @param minDisconnectInterval The minimum length of time in milliseconds 1298 * that should pass between connections closed 1299 * because they have been established for 1300 * longer than the maximum connection age. A 1301 * value less than or equal to zero indicates 1302 * that no minimum time should be enforced. 1303 */ 1304 public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) 1305 { 1306 if (minDisconnectInterval > 0) 1307 { 1308 this.minDisconnectInterval = minDisconnectInterval; 1309 } 1310 else 1311 { 1312 this.minDisconnectInterval = 0L; 1313 } 1314 } 1315 1316 1317 1318 /** 1319 * {@inheritDoc} 1320 */ 1321 @Override() 1322 public LDAPConnectionPoolHealthCheck getHealthCheck() 1323 { 1324 return healthCheck; 1325 } 1326 1327 1328 1329 /** 1330 * Sets the health check implementation for this connection pool. 1331 * 1332 * @param healthCheck The health check implementation for this connection 1333 * pool. It must not be {@code null}. 1334 */ 1335 public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck) 1336 { 1337 Validator.ensureNotNull(healthCheck); 1338 this.healthCheck = healthCheck; 1339 } 1340 1341 1342 1343 /** 1344 * {@inheritDoc} 1345 */ 1346 @Override() 1347 public long getHealthCheckIntervalMillis() 1348 { 1349 return healthCheckInterval; 1350 } 1351 1352 1353 1354 /** 1355 * {@inheritDoc} 1356 */ 1357 @Override() 1358 public void setHealthCheckIntervalMillis(final long healthCheckInterval) 1359 { 1360 Validator.ensureTrue(healthCheckInterval > 0L, 1361 "LDAPConnectionPool.healthCheckInterval must be greater than 0."); 1362 this.healthCheckInterval = healthCheckInterval; 1363 healthCheckThread.wakeUp(); 1364 } 1365 1366 1367 1368 /** 1369 * {@inheritDoc} 1370 */ 1371 @Override() 1372 protected void doHealthCheck() 1373 { 1374 final Iterator<Map.Entry<Thread,LDAPConnection>> iterator = 1375 connections.entrySet().iterator(); 1376 while (iterator.hasNext()) 1377 { 1378 final Map.Entry<Thread,LDAPConnection> e = iterator.next(); 1379 final Thread t = e.getKey(); 1380 final LDAPConnection c = e.getValue(); 1381 1382 if (! t.isAlive()) 1383 { 1384 c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null, 1385 null); 1386 c.terminate(null); 1387 iterator.remove(); 1388 } 1389 } 1390 } 1391 1392 1393 1394 /** 1395 * {@inheritDoc} 1396 */ 1397 @Override() 1398 public int getCurrentAvailableConnections() 1399 { 1400 return -1; 1401 } 1402 1403 1404 1405 /** 1406 * {@inheritDoc} 1407 */ 1408 @Override() 1409 public int getMaximumAvailableConnections() 1410 { 1411 return -1; 1412 } 1413 1414 1415 1416 /** 1417 * {@inheritDoc} 1418 */ 1419 @Override() 1420 public LDAPConnectionPoolStatistics getConnectionPoolStatistics() 1421 { 1422 return poolStatistics; 1423 } 1424 1425 1426 1427 /** 1428 * Closes this connection pool in the event that it becomes unreferenced. 1429 * 1430 * @throws Throwable If an unexpected problem occurs. 1431 */ 1432 @Override() 1433 protected void finalize() 1434 throws Throwable 1435 { 1436 super.finalize(); 1437 1438 close(); 1439 } 1440 1441 1442 1443 /** 1444 * {@inheritDoc} 1445 */ 1446 @Override() 1447 public void toString(final StringBuilder buffer) 1448 { 1449 buffer.append("LDAPThreadLocalConnectionPool("); 1450 1451 final String name = connectionPoolName; 1452 if (name != null) 1453 { 1454 buffer.append("name='"); 1455 buffer.append(name); 1456 buffer.append("', "); 1457 } 1458 1459 buffer.append("serverSet="); 1460 serverSet.toString(buffer); 1461 buffer.append(')'); 1462 } 1463}