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