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