001 /*
002 * Copyright 2009-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2013 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 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)
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 }