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