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 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.EnumSet;
028 import java.util.Iterator;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.concurrent.ConcurrentHashMap;
032 import java.util.concurrent.atomic.AtomicReference;
033
034 import com.unboundid.ldap.sdk.schema.Schema;
035 import com.unboundid.util.ObjectPair;
036 import com.unboundid.util.ThreadSafety;
037 import com.unboundid.util.ThreadSafetyLevel;
038
039 import static com.unboundid.ldap.sdk.LDAPMessages.*;
040 import static com.unboundid.util.Debug.*;
041 import static com.unboundid.util.StaticUtils.*;
042 import 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)
078 public 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 }