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 if (! connList.isEmpty()) 536 { 537 final ParallelPoolCloser closer = 538 new ParallelPoolCloser(connList, unbind, numThreads); 539 closer.closeConnections(); 540 } 541 } 542 else 543 { 544 final Iterator<Map.Entry<Thread,LDAPConnection>> iterator = 545 connections.entrySet().iterator(); 546 while (iterator.hasNext()) 547 { 548 final LDAPConnection conn = iterator.next().getValue(); 549 iterator.remove(); 550 551 poolStatistics.incrementNumConnectionsClosedUnneeded(); 552 conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null); 553 if (unbind) 554 { 555 conn.terminate(null); 556 } 557 else 558 { 559 conn.setClosed(); 560 } 561 } 562 } 563 } 564 565 566 567 /** 568 * {@inheritDoc} 569 */ 570 @Override() 571 public boolean isClosed() 572 { 573 return closed; 574 } 575 576 577 578 /** 579 * Processes a simple bind using a connection from this connection pool, and 580 * then reverts that authentication by re-binding as the same user used to 581 * authenticate new connections. If new connections are unauthenticated, then 582 * the subsequent bind will be an anonymous simple bind. This method attempts 583 * to ensure that processing the provided bind operation does not have a 584 * lasting impact the authentication state of the connection used to process 585 * it. 586 * <BR><BR> 587 * If the second bind attempt (the one used to restore the authentication 588 * identity) fails, the connection will be closed as defunct so that a new 589 * connection will be created to take its place. 590 * 591 * @param bindDN The bind DN for the simple bind request. 592 * @param password The password for the simple bind request. 593 * @param controls The optional set of controls for the simple bind request. 594 * 595 * @return The result of processing the provided bind operation. 596 * 597 * @throws LDAPException If the server rejects the bind request, or if a 598 * problem occurs while sending the request or reading 599 * the response. 600 */ 601 public BindResult bindAndRevertAuthentication(final String bindDN, 602 final String password, 603 final Control... controls) 604 throws LDAPException 605 { 606 return bindAndRevertAuthentication( 607 new SimpleBindRequest(bindDN, password, controls)); 608 } 609 610 611 612 /** 613 * Processes the provided bind request using a connection from this connection 614 * pool, and then reverts that authentication by re-binding as the same user 615 * used to authenticate new connections. If new connections are 616 * unauthenticated, then the subsequent bind will be an anonymous simple bind. 617 * This method attempts to ensure that processing the provided bind operation 618 * does not have a lasting impact the authentication state of the connection 619 * used to process it. 620 * <BR><BR> 621 * If the second bind attempt (the one used to restore the authentication 622 * identity) fails, the connection will be closed as defunct so that a new 623 * connection will be created to take its place. 624 * 625 * @param bindRequest The bind request to be processed. It must not be 626 * {@code null}. 627 * 628 * @return The result of processing the provided bind operation. 629 * 630 * @throws LDAPException If the server rejects the bind request, or if a 631 * problem occurs while sending the request or reading 632 * the response. 633 */ 634 public BindResult bindAndRevertAuthentication(final BindRequest bindRequest) 635 throws LDAPException 636 { 637 LDAPConnection conn = getConnection(); 638 639 try 640 { 641 final BindResult result = conn.bind(bindRequest); 642 releaseAndReAuthenticateConnection(conn); 643 return result; 644 } 645 catch (final Throwable t) 646 { 647 debugException(t); 648 649 if (t instanceof LDAPException) 650 { 651 final LDAPException le = (LDAPException) t; 652 653 boolean shouldThrow; 654 try 655 { 656 healthCheck.ensureConnectionValidAfterException(conn, le); 657 658 // The above call will throw an exception if the connection doesn't 659 // seem to be valid, so if we've gotten here then we should assume 660 // that it is valid and we will pass the exception onto the client 661 // without retrying the operation. 662 releaseAndReAuthenticateConnection(conn); 663 shouldThrow = true; 664 } 665 catch (final Exception e) 666 { 667 debugException(e); 668 669 // This implies that the connection is not valid. If the pool is 670 // configured to re-try bind operations on a newly-established 671 // connection, then that will be done later in this method. 672 // Otherwise, release the connection as defunct and pass the bind 673 // exception onto the client. 674 if (! getOperationTypesToRetryDueToInvalidConnections().contains( 675 OperationType.BIND)) 676 { 677 releaseDefunctConnection(conn); 678 shouldThrow = true; 679 } 680 else 681 { 682 shouldThrow = false; 683 } 684 } 685 686 if (shouldThrow) 687 { 688 throw le; 689 } 690 } 691 else 692 { 693 releaseDefunctConnection(conn); 694 throw new LDAPException(ResultCode.LOCAL_ERROR, 695 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t); 696 } 697 } 698 699 700 // If we've gotten here, then the bind operation should be re-tried on a 701 // newly-established connection. 702 conn = replaceDefunctConnection(conn); 703 704 try 705 { 706 final BindResult result = conn.bind(bindRequest); 707 releaseAndReAuthenticateConnection(conn); 708 return result; 709 } 710 catch (final Throwable t) 711 { 712 debugException(t); 713 714 if (t instanceof LDAPException) 715 { 716 final LDAPException le = (LDAPException) t; 717 718 try 719 { 720 healthCheck.ensureConnectionValidAfterException(conn, le); 721 releaseAndReAuthenticateConnection(conn); 722 } 723 catch (final Exception e) 724 { 725 debugException(e); 726 releaseDefunctConnection(conn); 727 } 728 729 throw le; 730 } 731 else 732 { 733 releaseDefunctConnection(conn); 734 throw new LDAPException(ResultCode.LOCAL_ERROR, 735 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t); 736 } 737 } 738 } 739 740 741 742 /** 743 * {@inheritDoc} 744 */ 745 @Override() 746 public LDAPConnection getConnection() 747 throws LDAPException 748 { 749 final Thread t = Thread.currentThread(); 750 LDAPConnection conn = connections.get(t); 751 752 if (closed) 753 { 754 if (conn != null) 755 { 756 conn.terminate(null); 757 connections.remove(t); 758 } 759 760 poolStatistics.incrementNumFailedCheckouts(); 761 throw new LDAPException(ResultCode.CONNECT_ERROR, 762 ERR_POOL_CLOSED.get()); 763 } 764 765 boolean created = false; 766 if ((conn == null) || (! conn.isConnected())) 767 { 768 conn = createConnection(); 769 connections.put(t, conn); 770 created = true; 771 } 772 773 try 774 { 775 healthCheck.ensureConnectionValidForCheckout(conn); 776 if (created) 777 { 778 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 779 } 780 else 781 { 782 poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting(); 783 } 784 return conn; 785 } 786 catch (LDAPException le) 787 { 788 debugException(le); 789 790 conn.terminate(null); 791 connections.remove(t); 792 793 if (created) 794 { 795 poolStatistics.incrementNumFailedCheckouts(); 796 throw le; 797 } 798 } 799 800 try 801 { 802 conn = createConnection(); 803 healthCheck.ensureConnectionValidForCheckout(conn); 804 connections.put(t, conn); 805 poolStatistics.incrementNumSuccessfulCheckoutsNewConnection(); 806 return conn; 807 } 808 catch (LDAPException le) 809 { 810 debugException(le); 811 812 poolStatistics.incrementNumFailedCheckouts(); 813 814 if (conn != null) 815 { 816 conn.terminate(null); 817 } 818 819 throw le; 820 } 821 } 822 823 824 825 /** 826 * {@inheritDoc} 827 */ 828 @Override() 829 public void releaseConnection(final LDAPConnection connection) 830 { 831 if (connection == null) 832 { 833 return; 834 } 835 836 connection.setConnectionPoolName(connectionPoolName); 837 if (connectionIsExpired(connection)) 838 { 839 try 840 { 841 final LDAPConnection newConnection = createConnection(); 842 connections.put(Thread.currentThread(), newConnection); 843 844 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED, 845 null, null); 846 connection.terminate(null); 847 poolStatistics.incrementNumConnectionsClosedExpired(); 848 lastExpiredDisconnectTime = System.currentTimeMillis(); 849 } 850 catch (final LDAPException le) 851 { 852 debugException(le); 853 } 854 } 855 856 try 857 { 858 healthCheck.ensureConnectionValidForRelease(connection); 859 } 860 catch (LDAPException le) 861 { 862 releaseDefunctConnection(connection); 863 return; 864 } 865 866 poolStatistics.incrementNumReleasedValid(); 867 868 if (closed) 869 { 870 close(); 871 } 872 } 873 874 875 876 /** 877 * Performs a bind on the provided connection before releasing it back to the 878 * pool, so that it will be authenticated as the same user as 879 * newly-established connections. If newly-established connections are 880 * unauthenticated, then this method will perform an anonymous simple bind to 881 * ensure that the resulting connection is unauthenticated. 882 * 883 * Releases the provided connection back to this pool. 884 * 885 * @param connection The connection to be released back to the pool after 886 * being re-authenticated. 887 */ 888 public void releaseAndReAuthenticateConnection( 889 final LDAPConnection connection) 890 { 891 if (connection == null) 892 { 893 return; 894 } 895 896 try 897 { 898 if (bindRequest == null) 899 { 900 connection.bind("", ""); 901 } 902 else 903 { 904 connection.bind(bindRequest); 905 } 906 907 releaseConnection(connection); 908 } 909 catch (final Exception e) 910 { 911 debugException(e); 912 releaseDefunctConnection(connection); 913 } 914 } 915 916 917 918 /** 919 * {@inheritDoc} 920 */ 921 @Override() 922 public void releaseDefunctConnection(final LDAPConnection connection) 923 { 924 if (connection == null) 925 { 926 return; 927 } 928 929 connection.setConnectionPoolName(connectionPoolName); 930 poolStatistics.incrementNumConnectionsClosedDefunct(); 931 handleDefunctConnection(connection); 932 } 933 934 935 936 /** 937 * Performs the real work of terminating a defunct connection and replacing it 938 * with a new connection if possible. 939 * 940 * @param connection The defunct connection to be replaced. 941 */ 942 private void handleDefunctConnection(final LDAPConnection connection) 943 { 944 final Thread t = Thread.currentThread(); 945 946 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 947 null); 948 connection.terminate(null); 949 connections.remove(t); 950 951 if (closed) 952 { 953 return; 954 } 955 956 try 957 { 958 final LDAPConnection conn = createConnection(); 959 connections.put(t, conn); 960 } 961 catch (LDAPException le) 962 { 963 debugException(le); 964 } 965 } 966 967 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override() 973 public LDAPConnection replaceDefunctConnection( 974 final LDAPConnection connection) 975 throws LDAPException 976 { 977 connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null, 978 null); 979 connection.terminate(null); 980 connections.remove(Thread.currentThread(), connection); 981 982 if (closed) 983 { 984 throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get()); 985 } 986 987 final LDAPConnection newConnection = createConnection(); 988 connections.put(Thread.currentThread(), newConnection); 989 return newConnection; 990 } 991 992 993 994 /** 995 * {@inheritDoc} 996 */ 997 @Override() 998 public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections() 999 { 1000 return retryOperationTypes.get(); 1001 } 1002 1003 1004 1005 /** 1006 * {@inheritDoc} 1007 */ 1008 @Override() 1009 public void setRetryFailedOperationsDueToInvalidConnections( 1010 final Set<OperationType> operationTypes) 1011 { 1012 if ((operationTypes == null) || operationTypes.isEmpty()) 1013 { 1014 retryOperationTypes.set( 1015 Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class))); 1016 } 1017 else 1018 { 1019 final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class); 1020 s.addAll(operationTypes); 1021 retryOperationTypes.set(Collections.unmodifiableSet(s)); 1022 } 1023 } 1024 1025 1026 1027 /** 1028 * Indicates whether the provided connection should be considered expired. 1029 * 1030 * @param connection The connection for which to make the determination. 1031 * 1032 * @return {@code true} if the provided connection should be considered 1033 * expired, or {@code false} if not. 1034 */ 1035 private boolean connectionIsExpired(final LDAPConnection connection) 1036 { 1037 // If connection expiration is not enabled, then there is nothing to do. 1038 if (maxConnectionAge <= 0L) 1039 { 1040 return false; 1041 } 1042 1043 // If there is a minimum disconnect interval, then make sure that we have 1044 // not closed another expired connection too recently. 1045 final long currentTime = System.currentTimeMillis(); 1046 if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval) 1047 { 1048 return false; 1049 } 1050 1051 // Get the age of the connection and see if it is expired. 1052 final long connectionAge = currentTime - connection.getConnectTime(); 1053 return (connectionAge > maxConnectionAge); 1054 } 1055 1056 1057 1058 /** 1059 * {@inheritDoc} 1060 */ 1061 @Override() 1062 public String getConnectionPoolName() 1063 { 1064 return connectionPoolName; 1065 } 1066 1067 1068 1069 /** 1070 * {@inheritDoc} 1071 */ 1072 @Override() 1073 public void setConnectionPoolName(final String connectionPoolName) 1074 { 1075 this.connectionPoolName = connectionPoolName; 1076 } 1077 1078 1079 1080 /** 1081 * Retrieves the maximum length of time in milliseconds that a connection in 1082 * this pool may be established before it is closed and replaced with another 1083 * connection. 1084 * 1085 * @return The maximum length of time in milliseconds that a connection in 1086 * this pool may be established before it is closed and replaced with 1087 * another connection, or {@code 0L} if no maximum age should be 1088 * enforced. 1089 */ 1090 public long getMaxConnectionAgeMillis() 1091 { 1092 return maxConnectionAge; 1093 } 1094 1095 1096 1097 /** 1098 * Specifies the maximum length of time in milliseconds that a connection in 1099 * this pool may be established before it should be closed and replaced with 1100 * another connection. 1101 * 1102 * @param maxConnectionAge The maximum length of time in milliseconds that a 1103 * connection in this pool may be established before 1104 * it should be closed and replaced with another 1105 * connection. A value of zero indicates that no 1106 * maximum age should be enforced. 1107 */ 1108 public void setMaxConnectionAgeMillis(final long maxConnectionAge) 1109 { 1110 if (maxConnectionAge > 0L) 1111 { 1112 this.maxConnectionAge = maxConnectionAge; 1113 } 1114 else 1115 { 1116 this.maxConnectionAge = 0L; 1117 } 1118 } 1119 1120 1121 1122 /** 1123 * Retrieves the minimum length of time in milliseconds that should pass 1124 * between connections closed because they have been established for longer 1125 * than the maximum connection age. 1126 * 1127 * @return The minimum length of time in milliseconds that should pass 1128 * between connections closed because they have been established for 1129 * longer than the maximum connection age, or {@code 0L} if expired 1130 * connections may be closed as quickly as they are identified. 1131 */ 1132 public long getMinDisconnectIntervalMillis() 1133 { 1134 return minDisconnectInterval; 1135 } 1136 1137 1138 1139 /** 1140 * Specifies the minimum length of time in milliseconds that should pass 1141 * between connections closed because they have been established for longer 1142 * than the maximum connection age. 1143 * 1144 * @param minDisconnectInterval The minimum length of time in milliseconds 1145 * that should pass between connections closed 1146 * because they have been established for 1147 * longer than the maximum connection age. A 1148 * value less than or equal to zero indicates 1149 * that no minimum time should be enforced. 1150 */ 1151 public void setMinDisconnectIntervalMillis(final long minDisconnectInterval) 1152 { 1153 if (minDisconnectInterval > 0) 1154 { 1155 this.minDisconnectInterval = minDisconnectInterval; 1156 } 1157 else 1158 { 1159 this.minDisconnectInterval = 0L; 1160 } 1161 } 1162 1163 1164 1165 /** 1166 * {@inheritDoc} 1167 */ 1168 @Override() 1169 public LDAPConnectionPoolHealthCheck getHealthCheck() 1170 { 1171 return healthCheck; 1172 } 1173 1174 1175 1176 /** 1177 * Sets the health check implementation for this connection pool. 1178 * 1179 * @param healthCheck The health check implementation for this connection 1180 * pool. It must not be {@code null}. 1181 */ 1182 public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck) 1183 { 1184 ensureNotNull(healthCheck); 1185 this.healthCheck = healthCheck; 1186 } 1187 1188 1189 1190 /** 1191 * {@inheritDoc} 1192 */ 1193 @Override() 1194 public long getHealthCheckIntervalMillis() 1195 { 1196 return healthCheckInterval; 1197 } 1198 1199 1200 1201 /** 1202 * {@inheritDoc} 1203 */ 1204 @Override() 1205 public void setHealthCheckIntervalMillis(final long healthCheckInterval) 1206 { 1207 ensureTrue(healthCheckInterval > 0L, 1208 "LDAPConnectionPool.healthCheckInterval must be greater than 0."); 1209 this.healthCheckInterval = healthCheckInterval; 1210 healthCheckThread.wakeUp(); 1211 } 1212 1213 1214 1215 /** 1216 * {@inheritDoc} 1217 */ 1218 @Override() 1219 protected void doHealthCheck() 1220 { 1221 final Iterator<Map.Entry<Thread,LDAPConnection>> iterator = 1222 connections.entrySet().iterator(); 1223 while (iterator.hasNext()) 1224 { 1225 final Map.Entry<Thread,LDAPConnection> e = iterator.next(); 1226 final Thread t = e.getKey(); 1227 final LDAPConnection c = e.getValue(); 1228 1229 if (! t.isAlive()) 1230 { 1231 c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null, 1232 null); 1233 c.terminate(null); 1234 iterator.remove(); 1235 } 1236 } 1237 } 1238 1239 1240 1241 /** 1242 * {@inheritDoc} 1243 */ 1244 @Override() 1245 public int getCurrentAvailableConnections() 1246 { 1247 return -1; 1248 } 1249 1250 1251 1252 /** 1253 * {@inheritDoc} 1254 */ 1255 @Override() 1256 public int getMaximumAvailableConnections() 1257 { 1258 return -1; 1259 } 1260 1261 1262 1263 /** 1264 * {@inheritDoc} 1265 */ 1266 @Override() 1267 public LDAPConnectionPoolStatistics getConnectionPoolStatistics() 1268 { 1269 return poolStatistics; 1270 } 1271 1272 1273 1274 /** 1275 * Closes this connection pool in the event that it becomes unreferenced. 1276 * 1277 * @throws Throwable If an unexpected problem occurs. 1278 */ 1279 @Override() 1280 protected void finalize() 1281 throws Throwable 1282 { 1283 super.finalize(); 1284 1285 close(); 1286 } 1287 1288 1289 1290 /** 1291 * {@inheritDoc} 1292 */ 1293 @Override() 1294 public void toString(final StringBuilder buffer) 1295 { 1296 buffer.append("LDAPThreadLocalConnectionPool("); 1297 1298 final String name = connectionPoolName; 1299 if (name != null) 1300 { 1301 buffer.append("name='"); 1302 buffer.append(name); 1303 buffer.append("', "); 1304 } 1305 1306 buffer.append("serverSet="); 1307 serverSet.toString(buffer); 1308 buffer.append(')'); 1309 } 1310}