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    }