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