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