001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.EnumSet;
028    import java.util.Iterator;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.concurrent.ConcurrentHashMap;
032    import java.util.concurrent.atomic.AtomicReference;
033    
034    import com.unboundid.ldap.sdk.schema.Schema;
035    import com.unboundid.util.ObjectPair;
036    import com.unboundid.util.ThreadSafety;
037    import com.unboundid.util.ThreadSafetyLevel;
038    
039    import static com.unboundid.ldap.sdk.LDAPMessages.*;
040    import static com.unboundid.util.Debug.*;
041    import static com.unboundid.util.StaticUtils.*;
042    import static com.unboundid.util.Validator.*;
043    
044    
045    
046    /**
047     * This class provides an implementation of an LDAP connection pool which
048     * maintains a dedicated connection for each thread using the connection pool.
049     * Connections will be created on an on-demand basis, so that if a thread
050     * attempts to use this connection pool for the first time then a new connection
051     * will be created by that thread.  This implementation eliminates the need to
052     * determine how best to size the connection pool, and it can eliminate
053     * contention among threads when trying to access a shared set of connections.
054     * All connections will be properly closed when the connection pool itself is
055     * closed, but if any thread which had previously used the connection pool stops
056     * running before the connection pool is closed, then the connection associated
057     * with that thread will also be closed by the Java finalizer.
058     * <BR><BR>
059     * If a thread obtains a connection to this connection pool, then that
060     * connection should not be made available to any other thread.  Similarly, if
061     * a thread attempts to check out multiple connections from the pool, then the
062     * same connection instance will be returned each time.
063     * <BR><BR>
064     * The capabilities offered by this class are generally the same as those
065     * provided by the {@link LDAPConnectionPool} class, as is the manner in which
066     * applications should interact with it.  See the class-level documentation for
067     * the {@code LDAPConnectionPool} class for additional information and examples.
068     * <BR><BR>
069     * One difference between this connection pool implementation and that provided
070     * by the {@link LDAPConnectionPool} class is that this implementation does not
071     * currently support periodic background health checks.  You can define health
072     * checks that will be invoked when a new connection is created, just before it
073     * is checked out for use, just after it is released, and if an error occurs
074     * while using the connection, but it will not maintain a separate background
075     * thread
076     */
077    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078    public final class LDAPThreadLocalConnectionPool
079           extends AbstractConnectionPool
080    {
081      /**
082       * The default health check interval for this connection pool, which is set to
083       * 60000 milliseconds (60 seconds).
084       */
085      private static final long DEFAULT_HEALTH_CHECK_INTERVAL = 60000L;
086    
087    
088    
089      // The types of operations that should be retried if they fail in a manner
090      // that may be the result of a connection that is no longer valid.
091      private final AtomicReference<Set<OperationType>> retryOperationTypes;
092    
093      // Indicates whether this connection pool has been closed.
094      private volatile boolean closed;
095    
096      // The bind request to use to perform authentication whenever a new connection
097      // is established.
098      private final BindRequest bindRequest;
099    
100      // The map of connections maintained for this connection pool.
101      private final ConcurrentHashMap<Thread,LDAPConnection> connections;
102    
103      // The health check implementation that should be used for this connection
104      // pool.
105      private LDAPConnectionPoolHealthCheck healthCheck;
106    
107      // The thread that will be used to perform periodic background health checks
108      // for this connection pool.
109      private final LDAPConnectionPoolHealthCheckThread healthCheckThread;
110    
111      // The statistics for this connection pool.
112      private final LDAPConnectionPoolStatistics poolStatistics;
113    
114      // The length of time in milliseconds between periodic health checks against
115      // the available connections in this pool.
116      private volatile long healthCheckInterval;
117    
118      // The time that the last expired connection was closed.
119      private volatile long lastExpiredDisconnectTime;
120    
121      // The maximum length of time in milliseconds that a connection should be
122      // allowed to be established before terminating and re-establishing the
123      // connection.
124      private volatile long maxConnectionAge;
125    
126      // The minimum length of time in milliseconds that must pass between
127      // disconnects of connections that have exceeded the maximum connection age.
128      private volatile long minDisconnectInterval;
129    
130      // The schema that should be shared for connections in this pool, along with
131      // its expiration time.
132      private volatile ObjectPair<Long,Schema> pooledSchema;
133    
134      // The post-connect processor for this connection pool, if any.
135      private final PostConnectProcessor postConnectProcessor;
136    
137      // The server set to use for establishing connections for use by this pool.
138      private final ServerSet serverSet;
139    
140      // The user-friendly name assigned to this connection pool.
141      private String connectionPoolName;
142    
143    
144    
145      /**
146       * Creates a new LDAP thread-local connection pool in which all connections
147       * will be clones of the provided connection.
148       *
149       * @param  connection  The connection to use to provide the template for the
150       *                     other connections to be created.  This connection will
151       *                     be included in the pool.  It must not be {@code null},
152       *                     and it must be established to the target server.  It
153       *                     does not necessarily need to be authenticated if all
154       *                     connections in the pool are to be unauthenticated.
155       *
156       * @throws  LDAPException  If the provided connection cannot be used to
157       *                         initialize the pool.  If this is thrown, then all
158       *                         connections associated with the pool (including the
159       *                         one provided as an argument) will be closed.
160       */
161      public LDAPThreadLocalConnectionPool(final LDAPConnection connection)
162             throws LDAPException
163      {
164        this(connection, null);
165      }
166    
167    
168    
169      /**
170       * Creates a new LDAP thread-local connection pool in which all connections
171       * will be clones of the provided connection.
172       *
173       * @param  connection            The connection to use to provide the template
174       *                               for the other connections to be created.
175       *                               This connection will be included in the pool.
176       *                               It must not be {@code null}, and it must be
177       *                               established to the target server.  It does
178       *                               not necessarily need to be authenticated if
179       *                               all connections in the pool are to be
180       *                               unauthenticated.
181       * @param  postConnectProcessor  A processor that should be used to perform
182       *                               any post-connect processing for connections
183       *                               in this pool.  It may be {@code null} if no
184       *                               special processing is needed.  Note that this
185       *                               processing will not be invoked on the
186       *                               provided connection that will be used as the
187       *                               first connection in the pool.
188       *
189       * @throws  LDAPException  If the provided connection cannot be used to
190       *                         initialize the pool.  If this is thrown, then all
191       *                         connections associated with the pool (including the
192       *                         one provided as an argument) will be closed.
193       */
194      public LDAPThreadLocalConnectionPool(final LDAPConnection connection,
195                  final PostConnectProcessor postConnectProcessor)
196             throws LDAPException
197      {
198        ensureNotNull(connection);
199    
200        this.postConnectProcessor = postConnectProcessor;
201    
202        healthCheck               = new LDAPConnectionPoolHealthCheck();
203        healthCheckInterval       = DEFAULT_HEALTH_CHECK_INTERVAL;
204        poolStatistics            = new LDAPConnectionPoolStatistics(this);
205        connectionPoolName        = null;
206        retryOperationTypes       = new AtomicReference<Set<OperationType>>(
207             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
208    
209        if (! connection.isConnected())
210        {
211          throw new LDAPException(ResultCode.PARAM_ERROR,
212                                  ERR_POOL_CONN_NOT_ESTABLISHED.get());
213        }
214    
215    
216        serverSet = new SingleServerSet(connection.getConnectedAddress(),
217                                        connection.getConnectedPort(),
218                                        connection.getLastUsedSocketFactory(),
219                                        connection.getConnectionOptions());
220        bindRequest = connection.getLastBindRequest();
221    
222        connections = new ConcurrentHashMap<Thread,LDAPConnection>();
223        connections.put(Thread.currentThread(), connection);
224    
225        lastExpiredDisconnectTime = 0L;
226        maxConnectionAge          = 0L;
227        closed                    = false;
228        minDisconnectInterval     = 0L;
229    
230        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
231        healthCheckThread.start();
232    
233        final LDAPConnectionOptions opts = connection.getConnectionOptions();
234        if (opts.usePooledSchema())
235        {
236          try
237          {
238            final Schema schema = connection.getSchema();
239            if (schema != null)
240            {
241              connection.setCachedSchema(schema);
242    
243              final long currentTime = System.currentTimeMillis();
244              final long timeout = opts.getPooledSchemaTimeoutMillis();
245              if ((timeout <= 0L) || (timeout+currentTime <= 0L))
246              {
247                pooledSchema = new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
248              }
249              else
250              {
251                pooledSchema =
252                     new ObjectPair<Long,Schema>(timeout+currentTime, schema);
253              }
254            }
255          }
256          catch (final Exception e)
257          {
258            debugException(e);
259          }
260        }
261      }
262    
263    
264    
265      /**
266       * Creates a new LDAP thread-local connection pool which will use the provided
267       * server set and bind request for creating new connections.
268       *
269       * @param  serverSet       The server set to use to create the connections.
270       *                         It is acceptable for the server set to create the
271       *                         connections across multiple servers.
272       * @param  bindRequest     The bind request to use to authenticate the
273       *                         connections that are established.  It may be
274       *                         {@code null} if no authentication should be
275       *                         performed on the connections.
276       */
277      public LDAPThreadLocalConnectionPool(final ServerSet serverSet,
278                                           final BindRequest bindRequest)
279      {
280        this(serverSet, bindRequest, null);
281      }
282    
283    
284    
285      /**
286       * Creates a new LDAP thread-local connection pool which will use the provided
287       * server set and bind request for creating new connections.
288       *
289       * @param  serverSet             The server set to use to create the
290       *                               connections.  It is acceptable for the server
291       *                               set to create the connections across multiple
292       *                               servers.
293       * @param  bindRequest           The bind request to use to authenticate the
294       *                               connections that are established.  It may be
295       *                               {@code null} if no authentication should be
296       *                               performed on the connections.
297       * @param  postConnectProcessor  A processor that should be used to perform
298       *                               any post-connect processing for connections
299       *                               in this pool.  It may be {@code null} if no
300       *                               special processing is needed.
301       */
302      public LDAPThreadLocalConnectionPool(final ServerSet serverSet,
303                  final BindRequest bindRequest,
304                  final PostConnectProcessor postConnectProcessor)
305      {
306        ensureNotNull(serverSet);
307    
308        this.serverSet            = serverSet;
309        this.bindRequest          = bindRequest;
310        this.postConnectProcessor = postConnectProcessor;
311    
312        healthCheck               = new LDAPConnectionPoolHealthCheck();
313        healthCheckInterval       = DEFAULT_HEALTH_CHECK_INTERVAL;
314        poolStatistics            = new LDAPConnectionPoolStatistics(this);
315        connectionPoolName        = null;
316        retryOperationTypes       = new AtomicReference<Set<OperationType>>(
317             Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
318    
319        connections = new ConcurrentHashMap<Thread,LDAPConnection>();
320    
321        lastExpiredDisconnectTime = 0L;
322        maxConnectionAge          = 0L;
323        minDisconnectInterval     = 0L;
324        closed                    = false;
325    
326        healthCheckThread = new LDAPConnectionPoolHealthCheckThread(this);
327        healthCheckThread.start();
328      }
329    
330    
331    
332      /**
333       * Creates a new LDAP connection for use in this pool.
334       *
335       * @return  A new connection created for use in this pool.
336       *
337       * @throws  LDAPException  If a problem occurs while attempting to establish
338       *                         the connection.  If a connection had been created,
339       *                         it will be closed.
340       */
341      private LDAPConnection createConnection()
342              throws LDAPException
343      {
344        final LDAPConnection c = serverSet.getConnection(healthCheck);
345        c.setConnectionPool(this);
346    
347        // Auto-reconnect must be disabled for pooled connections, so turn it off
348        // if the associated connection options have it enabled for some reason.
349        LDAPConnectionOptions opts = c.getConnectionOptions();
350        if (opts.autoReconnect())
351        {
352          opts = opts.duplicate();
353          opts.setAutoReconnect(false);
354          c.setConnectionOptions(opts);
355        }
356    
357        if (postConnectProcessor != null)
358        {
359          try
360          {
361            postConnectProcessor.processPreAuthenticatedConnection(c);
362          }
363          catch (Exception e)
364          {
365            debugException(e);
366    
367            try
368            {
369              poolStatistics.incrementNumFailedConnectionAttempts();
370              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
371              c.terminate(null);
372            }
373            catch (Exception e2)
374            {
375              debugException(e2);
376            }
377    
378            if (e instanceof LDAPException)
379            {
380              throw ((LDAPException) e);
381            }
382            else
383            {
384              throw new LDAPException(ResultCode.CONNECT_ERROR,
385                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
386            }
387          }
388        }
389    
390        try
391        {
392          if (bindRequest != null)
393          {
394            c.bind(bindRequest.duplicate());
395          }
396        }
397        catch (Exception e)
398        {
399          debugException(e);
400          try
401          {
402            poolStatistics.incrementNumFailedConnectionAttempts();
403            c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e);
404            c.terminate(null);
405          }
406          catch (Exception e2)
407          {
408            debugException(e2);
409          }
410    
411          if (e instanceof LDAPException)
412          {
413            throw ((LDAPException) e);
414          }
415          else
416          {
417            throw new LDAPException(ResultCode.CONNECT_ERROR,
418                 ERR_POOL_CONNECT_ERROR.get(getExceptionMessage(e)), e);
419          }
420        }
421    
422        if (postConnectProcessor != null)
423        {
424          try
425          {
426            postConnectProcessor.processPostAuthenticatedConnection(c);
427          }
428          catch (Exception e)
429          {
430            debugException(e);
431            try
432            {
433              poolStatistics.incrementNumFailedConnectionAttempts();
434              c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
435              c.terminate(null);
436            }
437            catch (Exception e2)
438            {
439              debugException(e2);
440            }
441    
442            if (e instanceof LDAPException)
443            {
444              throw ((LDAPException) e);
445            }
446            else
447            {
448              throw new LDAPException(ResultCode.CONNECT_ERROR,
449                   ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
450            }
451          }
452        }
453    
454        if (opts.usePooledSchema())
455        {
456          final long currentTime = System.currentTimeMillis();
457          if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst()))
458          {
459            try
460            {
461              final Schema schema = c.getSchema();
462              if (schema != null)
463              {
464                c.setCachedSchema(schema);
465    
466                final long timeout = opts.getPooledSchemaTimeoutMillis();
467                if ((timeout <= 0L) || (currentTime + timeout <= 0L))
468                {
469                  pooledSchema =
470                       new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
471                }
472                else
473                {
474                  pooledSchema =
475                       new ObjectPair<Long,Schema>((currentTime+timeout), schema);
476                }
477              }
478            }
479            catch (final Exception e)
480            {
481              debugException(e);
482    
483              // There was a problem retrieving the schema from the server, but if
484              // we have an earlier copy then we can assume it's still valid.
485              if (pooledSchema != null)
486              {
487                c.setCachedSchema(pooledSchema.getSecond());
488              }
489            }
490          }
491          else
492          {
493            c.setCachedSchema(pooledSchema.getSecond());
494          }
495        }
496    
497        c.setConnectionPoolName(connectionPoolName);
498        poolStatistics.incrementNumSuccessfulConnectionAttempts();
499        return c;
500      }
501    
502    
503    
504      /**
505       * {@inheritDoc}
506       */
507      @Override()
508      public void close()
509      {
510        close(true, 1);
511      }
512    
513    
514    
515      /**
516       * {@inheritDoc}
517       */
518      @Override()
519      public void close(final boolean unbind, final int numThreads)
520      {
521        closed = true;
522        healthCheckThread.stopRunning();
523    
524        if (numThreads > 1)
525        {
526          final ArrayList<LDAPConnection> connList =
527               new ArrayList<LDAPConnection>(connections.size());
528          final Iterator<LDAPConnection> iterator = connections.values().iterator();
529          while (iterator.hasNext())
530          {
531            connList.add(iterator.next());
532            iterator.remove();
533          }
534    
535          if (! connList.isEmpty())
536          {
537            final ParallelPoolCloser closer =
538                 new ParallelPoolCloser(connList, unbind, numThreads);
539            closer.closeConnections();
540          }
541        }
542        else
543        {
544          final Iterator<Map.Entry<Thread,LDAPConnection>> iterator =
545               connections.entrySet().iterator();
546          while (iterator.hasNext())
547          {
548            final LDAPConnection conn = iterator.next().getValue();
549            iterator.remove();
550    
551            poolStatistics.incrementNumConnectionsClosedUnneeded();
552            conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
553            if (unbind)
554            {
555              conn.terminate(null);
556            }
557            else
558            {
559              conn.setClosed();
560            }
561          }
562        }
563      }
564    
565    
566    
567      /**
568       * {@inheritDoc}
569       */
570      @Override()
571      public boolean isClosed()
572      {
573        return closed;
574      }
575    
576    
577    
578      /**
579       * Processes a simple bind using a connection from this connection pool, and
580       * then reverts that authentication by re-binding as the same user used to
581       * authenticate new connections.  If new connections are unauthenticated, then
582       * the subsequent bind will be an anonymous simple bind.  This method attempts
583       * to ensure that processing the provided bind operation does not have a
584       * lasting impact the authentication state of the connection used to process
585       * it.
586       * <BR><BR>
587       * If the second bind attempt (the one used to restore the authentication
588       * identity) fails, the connection will be closed as defunct so that a new
589       * connection will be created to take its place.
590       *
591       * @param  bindDN    The bind DN for the simple bind request.
592       * @param  password  The password for the simple bind request.
593       * @param  controls  The optional set of controls for the simple bind request.
594       *
595       * @return  The result of processing the provided bind operation.
596       *
597       * @throws  LDAPException  If the server rejects the bind request, or if a
598       *                         problem occurs while sending the request or reading
599       *                         the response.
600       */
601      public BindResult bindAndRevertAuthentication(final String bindDN,
602                                                    final String password,
603                                                    final Control... controls)
604             throws LDAPException
605      {
606        return bindAndRevertAuthentication(
607             new SimpleBindRequest(bindDN, password, controls));
608      }
609    
610    
611    
612      /**
613       * Processes the provided bind request using a connection from this connection
614       * pool, and then reverts that authentication by re-binding as the same user
615       * used to authenticate new connections.  If new connections are
616       * unauthenticated, then the subsequent bind will be an anonymous simple bind.
617       * This method attempts to ensure that processing the provided bind operation
618       * does not have a lasting impact the authentication state of the connection
619       * used to process it.
620       * <BR><BR>
621       * If the second bind attempt (the one used to restore the authentication
622       * identity) fails, the connection will be closed as defunct so that a new
623       * connection will be created to take its place.
624       *
625       * @param  bindRequest  The bind request to be processed.  It must not be
626       *                      {@code null}.
627       *
628       * @return  The result of processing the provided bind operation.
629       *
630       * @throws  LDAPException  If the server rejects the bind request, or if a
631       *                         problem occurs while sending the request or reading
632       *                         the response.
633       */
634      public BindResult bindAndRevertAuthentication(final BindRequest bindRequest)
635             throws LDAPException
636      {
637        LDAPConnection conn = getConnection();
638    
639        try
640        {
641          final BindResult result = conn.bind(bindRequest);
642          releaseAndReAuthenticateConnection(conn);
643          return result;
644        }
645        catch (final Throwable t)
646        {
647          debugException(t);
648    
649          if (t instanceof LDAPException)
650          {
651            final LDAPException le = (LDAPException) t;
652    
653            boolean shouldThrow;
654            try
655            {
656              healthCheck.ensureConnectionValidAfterException(conn, le);
657    
658              // The above call will throw an exception if the connection doesn't
659              // seem to be valid, so if we've gotten here then we should assume
660              // that it is valid and we will pass the exception onto the client
661              // without retrying the operation.
662              releaseAndReAuthenticateConnection(conn);
663              shouldThrow = true;
664            }
665            catch (final Exception e)
666            {
667              debugException(e);
668    
669              // This implies that the connection is not valid.  If the pool is
670              // configured to re-try bind operations on a newly-established
671              // connection, then that will be done later in this method.
672              // Otherwise, release the connection as defunct and pass the bind
673              // exception onto the client.
674              if (! getOperationTypesToRetryDueToInvalidConnections().contains(
675                         OperationType.BIND))
676              {
677                releaseDefunctConnection(conn);
678                shouldThrow = true;
679              }
680              else
681              {
682                shouldThrow = false;
683              }
684            }
685    
686            if (shouldThrow)
687            {
688              throw le;
689            }
690          }
691          else
692          {
693            releaseDefunctConnection(conn);
694            throw new LDAPException(ResultCode.LOCAL_ERROR,
695                 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
696          }
697        }
698    
699    
700        // If we've gotten here, then the bind operation should be re-tried on a
701        // newly-established connection.
702        conn = replaceDefunctConnection(conn);
703    
704        try
705        {
706          final BindResult result = conn.bind(bindRequest);
707          releaseAndReAuthenticateConnection(conn);
708          return result;
709        }
710        catch (final Throwable t)
711        {
712          debugException(t);
713    
714          if (t instanceof LDAPException)
715          {
716            final LDAPException le = (LDAPException) t;
717    
718            try
719            {
720              healthCheck.ensureConnectionValidAfterException(conn, le);
721              releaseAndReAuthenticateConnection(conn);
722            }
723            catch (final Exception e)
724            {
725              debugException(e);
726              releaseDefunctConnection(conn);
727            }
728    
729            throw le;
730          }
731          else
732          {
733            releaseDefunctConnection(conn);
734            throw new LDAPException(ResultCode.LOCAL_ERROR,
735                 ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
736          }
737        }
738      }
739    
740    
741    
742      /**
743       * {@inheritDoc}
744       */
745      @Override()
746      public LDAPConnection getConnection()
747             throws LDAPException
748      {
749        final Thread t = Thread.currentThread();
750        LDAPConnection conn = connections.get(t);
751    
752        if (closed)
753        {
754          if (conn != null)
755          {
756            conn.terminate(null);
757            connections.remove(t);
758          }
759    
760          poolStatistics.incrementNumFailedCheckouts();
761          throw new LDAPException(ResultCode.CONNECT_ERROR,
762                                  ERR_POOL_CLOSED.get());
763        }
764    
765        boolean created = false;
766        if ((conn == null) || (! conn.isConnected()))
767        {
768          conn = createConnection();
769          connections.put(t, conn);
770          created = true;
771        }
772    
773        try
774        {
775          healthCheck.ensureConnectionValidForCheckout(conn);
776          if (created)
777          {
778            poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
779          }
780          else
781          {
782            poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
783          }
784          return conn;
785        }
786        catch (LDAPException le)
787        {
788          debugException(le);
789    
790          conn.terminate(null);
791          connections.remove(t);
792    
793          if (created)
794          {
795            poolStatistics.incrementNumFailedCheckouts();
796            throw le;
797          }
798        }
799    
800        try
801        {
802          conn = createConnection();
803          healthCheck.ensureConnectionValidForCheckout(conn);
804          connections.put(t, conn);
805          poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
806          return conn;
807        }
808        catch (LDAPException le)
809        {
810          debugException(le);
811    
812          poolStatistics.incrementNumFailedCheckouts();
813    
814          if (conn != null)
815          {
816            conn.terminate(null);
817          }
818    
819          throw le;
820        }
821      }
822    
823    
824    
825      /**
826       * {@inheritDoc}
827       */
828      @Override()
829      public void releaseConnection(final LDAPConnection connection)
830      {
831        if (connection == null)
832        {
833          return;
834        }
835    
836        connection.setConnectionPoolName(connectionPoolName);
837        if (connectionIsExpired(connection))
838        {
839          try
840          {
841            final LDAPConnection newConnection = createConnection();
842            connections.put(Thread.currentThread(), newConnection);
843    
844            connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
845                 null, null);
846            connection.terminate(null);
847            poolStatistics.incrementNumConnectionsClosedExpired();
848            lastExpiredDisconnectTime = System.currentTimeMillis();
849          }
850          catch (final LDAPException le)
851          {
852            debugException(le);
853          }
854        }
855    
856        try
857        {
858          healthCheck.ensureConnectionValidForRelease(connection);
859        }
860        catch (LDAPException le)
861        {
862          releaseDefunctConnection(connection);
863          return;
864        }
865    
866        poolStatistics.incrementNumReleasedValid();
867    
868        if (closed)
869        {
870          close();
871        }
872      }
873    
874    
875    
876      /**
877       * Performs a bind on the provided connection before releasing it back to the
878       * pool, so that it will be authenticated as the same user as
879       * newly-established connections.  If newly-established connections are
880       * unauthenticated, then this method will perform an anonymous simple bind to
881       * ensure that the resulting connection is unauthenticated.
882       *
883       * Releases the provided connection back to this pool.
884       *
885       * @param  connection  The connection to be released back to the pool after
886       *                     being re-authenticated.
887       */
888      public void releaseAndReAuthenticateConnection(
889           final LDAPConnection connection)
890      {
891        if (connection == null)
892        {
893          return;
894        }
895    
896        try
897        {
898          if (bindRequest == null)
899          {
900            connection.bind("", "");
901          }
902          else
903          {
904            connection.bind(bindRequest);
905          }
906    
907          releaseConnection(connection);
908        }
909        catch (final Exception e)
910        {
911          debugException(e);
912          releaseDefunctConnection(connection);
913        }
914      }
915    
916    
917    
918      /**
919       * {@inheritDoc}
920       */
921      @Override()
922      public void releaseDefunctConnection(final LDAPConnection connection)
923      {
924        if (connection == null)
925        {
926          return;
927        }
928    
929        connection.setConnectionPoolName(connectionPoolName);
930        poolStatistics.incrementNumConnectionsClosedDefunct();
931        handleDefunctConnection(connection);
932      }
933    
934    
935    
936      /**
937       * Performs the real work of terminating a defunct connection and replacing it
938       * with a new connection if possible.
939       *
940       * @param  connection  The defunct connection to be replaced.
941       */
942      private void handleDefunctConnection(final LDAPConnection connection)
943      {
944        final Thread t = Thread.currentThread();
945    
946        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
947                                     null);
948        connection.terminate(null);
949        connections.remove(t);
950    
951        if (closed)
952        {
953          return;
954        }
955    
956        try
957        {
958          final LDAPConnection conn = createConnection();
959          connections.put(t, conn);
960        }
961        catch (LDAPException le)
962        {
963          debugException(le);
964        }
965      }
966    
967    
968    
969      /**
970       * {@inheritDoc}
971       */
972      @Override()
973      public LDAPConnection replaceDefunctConnection(
974                                 final LDAPConnection connection)
975             throws LDAPException
976      {
977        poolStatistics.incrementNumConnectionsClosedDefunct();
978        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
979                                     null);
980        connection.terminate(null);
981        connections.remove(Thread.currentThread(), connection);
982    
983        if (closed)
984        {
985          throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
986        }
987    
988        final LDAPConnection newConnection = createConnection();
989        connections.put(Thread.currentThread(), newConnection);
990        return newConnection;
991      }
992    
993    
994    
995      /**
996       * {@inheritDoc}
997       */
998      @Override()
999      public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
1000      {
1001        return retryOperationTypes.get();
1002      }
1003    
1004    
1005    
1006      /**
1007       * {@inheritDoc}
1008       */
1009      @Override()
1010      public void setRetryFailedOperationsDueToInvalidConnections(
1011                       final Set<OperationType> operationTypes)
1012      {
1013        if ((operationTypes == null) || operationTypes.isEmpty())
1014        {
1015          retryOperationTypes.set(
1016               Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
1017        }
1018        else
1019        {
1020          final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
1021          s.addAll(operationTypes);
1022          retryOperationTypes.set(Collections.unmodifiableSet(s));
1023        }
1024      }
1025    
1026    
1027    
1028      /**
1029       * Indicates whether the provided connection should be considered expired.
1030       *
1031       * @param  connection  The connection for which to make the determination.
1032       *
1033       * @return  {@code true} if the provided connection should be considered
1034       *          expired, or {@code false} if not.
1035       */
1036      private boolean connectionIsExpired(final LDAPConnection connection)
1037      {
1038        // If connection expiration is not enabled, then there is nothing to do.
1039        if (maxConnectionAge <= 0L)
1040        {
1041          return false;
1042        }
1043    
1044        // If there is a minimum disconnect interval, then make sure that we have
1045        // not closed another expired connection too recently.
1046        final long currentTime = System.currentTimeMillis();
1047        if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
1048        {
1049          return false;
1050        }
1051    
1052        // Get the age of the connection and see if it is expired.
1053        final long connectionAge = currentTime - connection.getConnectTime();
1054        return (connectionAge > maxConnectionAge);
1055      }
1056    
1057    
1058    
1059      /**
1060       * {@inheritDoc}
1061       */
1062      @Override()
1063      public String getConnectionPoolName()
1064      {
1065        return connectionPoolName;
1066      }
1067    
1068    
1069    
1070      /**
1071       * {@inheritDoc}
1072       */
1073      @Override()
1074      public void setConnectionPoolName(final String connectionPoolName)
1075      {
1076        this.connectionPoolName = connectionPoolName;
1077      }
1078    
1079    
1080    
1081      /**
1082       * Retrieves the maximum length of time in milliseconds that a connection in
1083       * this pool may be established before it is closed and replaced with another
1084       * connection.
1085       *
1086       * @return  The maximum length of time in milliseconds that a connection in
1087       *          this pool may be established before it is closed and replaced with
1088       *          another connection, or {@code 0L} if no maximum age should be
1089       *          enforced.
1090       */
1091      public long getMaxConnectionAgeMillis()
1092      {
1093        return maxConnectionAge;
1094      }
1095    
1096    
1097    
1098      /**
1099       * Specifies the maximum length of time in milliseconds that a connection in
1100       * this pool may be established before it should be closed and replaced with
1101       * another connection.
1102       *
1103       * @param  maxConnectionAge  The maximum length of time in milliseconds that a
1104       *                           connection in this pool may be established before
1105       *                           it should be closed and replaced with another
1106       *                           connection.  A value of zero indicates that no
1107       *                           maximum age should be enforced.
1108       */
1109      public void setMaxConnectionAgeMillis(final long maxConnectionAge)
1110      {
1111        if (maxConnectionAge > 0L)
1112        {
1113          this.maxConnectionAge = maxConnectionAge;
1114        }
1115        else
1116        {
1117          this.maxConnectionAge = 0L;
1118        }
1119      }
1120    
1121    
1122    
1123      /**
1124       * Retrieves the minimum length of time in milliseconds that should pass
1125       * between connections closed because they have been established for longer
1126       * than the maximum connection age.
1127       *
1128       * @return  The minimum length of time in milliseconds that should pass
1129       *          between connections closed because they have been established for
1130       *          longer than the maximum connection age, or {@code 0L} if expired
1131       *          connections may be closed as quickly as they are identified.
1132       */
1133      public long getMinDisconnectIntervalMillis()
1134      {
1135        return minDisconnectInterval;
1136      }
1137    
1138    
1139    
1140      /**
1141       * Specifies the minimum length of time in milliseconds that should pass
1142       * between connections closed because they have been established for longer
1143       * than the maximum connection age.
1144       *
1145       * @param  minDisconnectInterval  The minimum length of time in milliseconds
1146       *                                that should pass between connections closed
1147       *                                because they have been established for
1148       *                                longer than the maximum connection age.  A
1149       *                                value less than or equal to zero indicates
1150       *                                that no minimum time should be enforced.
1151       */
1152      public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
1153      {
1154        if (minDisconnectInterval > 0)
1155        {
1156          this.minDisconnectInterval = minDisconnectInterval;
1157        }
1158        else
1159        {
1160          this.minDisconnectInterval = 0L;
1161        }
1162      }
1163    
1164    
1165    
1166      /**
1167       * {@inheritDoc}
1168       */
1169      @Override()
1170      public LDAPConnectionPoolHealthCheck getHealthCheck()
1171      {
1172        return healthCheck;
1173      }
1174    
1175    
1176    
1177      /**
1178       * Sets the health check implementation for this connection pool.
1179       *
1180       * @param  healthCheck  The health check implementation for this connection
1181       *                      pool.  It must not be {@code null}.
1182       */
1183      public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
1184      {
1185        ensureNotNull(healthCheck);
1186        this.healthCheck = healthCheck;
1187      }
1188    
1189    
1190    
1191      /**
1192       * {@inheritDoc}
1193       */
1194      @Override()
1195      public long getHealthCheckIntervalMillis()
1196      {
1197        return healthCheckInterval;
1198      }
1199    
1200    
1201    
1202      /**
1203       * {@inheritDoc}
1204       */
1205      @Override()
1206      public void setHealthCheckIntervalMillis(final long healthCheckInterval)
1207      {
1208        ensureTrue(healthCheckInterval > 0L,
1209             "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
1210        this.healthCheckInterval = healthCheckInterval;
1211        healthCheckThread.wakeUp();
1212      }
1213    
1214    
1215    
1216      /**
1217       * {@inheritDoc}
1218       */
1219      @Override()
1220      protected void doHealthCheck()
1221      {
1222        final Iterator<Map.Entry<Thread,LDAPConnection>> iterator =
1223             connections.entrySet().iterator();
1224        while (iterator.hasNext())
1225        {
1226          final Map.Entry<Thread,LDAPConnection> e = iterator.next();
1227          final Thread                           t = e.getKey();
1228          final LDAPConnection                   c = e.getValue();
1229    
1230          if (! t.isAlive())
1231          {
1232            c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null,
1233                                null);
1234            c.terminate(null);
1235            iterator.remove();
1236          }
1237        }
1238      }
1239    
1240    
1241    
1242      /**
1243       * {@inheritDoc}
1244       */
1245      @Override()
1246      public int getCurrentAvailableConnections()
1247      {
1248        return -1;
1249      }
1250    
1251    
1252    
1253      /**
1254       * {@inheritDoc}
1255       */
1256      @Override()
1257      public int getMaximumAvailableConnections()
1258      {
1259        return -1;
1260      }
1261    
1262    
1263    
1264      /**
1265       * {@inheritDoc}
1266       */
1267      @Override()
1268      public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
1269      {
1270        return poolStatistics;
1271      }
1272    
1273    
1274    
1275      /**
1276       * Closes this connection pool in the event that it becomes unreferenced.
1277       *
1278       * @throws  Throwable  If an unexpected problem occurs.
1279       */
1280      @Override()
1281      protected void finalize()
1282                throws Throwable
1283      {
1284        super.finalize();
1285    
1286        close();
1287      }
1288    
1289    
1290    
1291      /**
1292       * {@inheritDoc}
1293       */
1294      @Override()
1295      public void toString(final StringBuilder buffer)
1296      {
1297        buffer.append("LDAPThreadLocalConnectionPool(");
1298    
1299        final String name = connectionPoolName;
1300        if (name != null)
1301        {
1302          buffer.append("name='");
1303          buffer.append(name);
1304          buffer.append("', ");
1305        }
1306    
1307        buffer.append("serverSet=");
1308        serverSet.toString(buffer);
1309        buffer.append(')');
1310      }
1311    }