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