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      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    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
978                                 null);
979    connection.terminate(null);
980    connections.remove(Thread.currentThread(), connection);
981
982    if (closed)
983    {
984      throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
985    }
986
987    final LDAPConnection newConnection = createConnection();
988    connections.put(Thread.currentThread(), newConnection);
989    return newConnection;
990  }
991
992
993
994  /**
995   * {@inheritDoc}
996   */
997  @Override()
998  public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
999  {
1000    return retryOperationTypes.get();
1001  }
1002
1003
1004
1005  /**
1006   * {@inheritDoc}
1007   */
1008  @Override()
1009  public void setRetryFailedOperationsDueToInvalidConnections(
1010                   final Set<OperationType> operationTypes)
1011  {
1012    if ((operationTypes == null) || operationTypes.isEmpty())
1013    {
1014      retryOperationTypes.set(
1015           Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
1016    }
1017    else
1018    {
1019      final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
1020      s.addAll(operationTypes);
1021      retryOperationTypes.set(Collections.unmodifiableSet(s));
1022    }
1023  }
1024
1025
1026
1027  /**
1028   * Indicates whether the provided connection should be considered expired.
1029   *
1030   * @param  connection  The connection for which to make the determination.
1031   *
1032   * @return  {@code true} if the provided connection should be considered
1033   *          expired, or {@code false} if not.
1034   */
1035  private boolean connectionIsExpired(final LDAPConnection connection)
1036  {
1037    // If connection expiration is not enabled, then there is nothing to do.
1038    if (maxConnectionAge <= 0L)
1039    {
1040      return false;
1041    }
1042
1043    // If there is a minimum disconnect interval, then make sure that we have
1044    // not closed another expired connection too recently.
1045    final long currentTime = System.currentTimeMillis();
1046    if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
1047    {
1048      return false;
1049    }
1050
1051    // Get the age of the connection and see if it is expired.
1052    final long connectionAge = currentTime - connection.getConnectTime();
1053    return (connectionAge > maxConnectionAge);
1054  }
1055
1056
1057
1058  /**
1059   * {@inheritDoc}
1060   */
1061  @Override()
1062  public String getConnectionPoolName()
1063  {
1064    return connectionPoolName;
1065  }
1066
1067
1068
1069  /**
1070   * {@inheritDoc}
1071   */
1072  @Override()
1073  public void setConnectionPoolName(final String connectionPoolName)
1074  {
1075    this.connectionPoolName = connectionPoolName;
1076  }
1077
1078
1079
1080  /**
1081   * Retrieves the maximum length of time in milliseconds that a connection in
1082   * this pool may be established before it is closed and replaced with another
1083   * connection.
1084   *
1085   * @return  The maximum length of time in milliseconds that a connection in
1086   *          this pool may be established before it is closed and replaced with
1087   *          another connection, or {@code 0L} if no maximum age should be
1088   *          enforced.
1089   */
1090  public long getMaxConnectionAgeMillis()
1091  {
1092    return maxConnectionAge;
1093  }
1094
1095
1096
1097  /**
1098   * Specifies the maximum length of time in milliseconds that a connection in
1099   * this pool may be established before it should be closed and replaced with
1100   * another connection.
1101   *
1102   * @param  maxConnectionAge  The maximum length of time in milliseconds that a
1103   *                           connection in this pool may be established before
1104   *                           it should be closed and replaced with another
1105   *                           connection.  A value of zero indicates that no
1106   *                           maximum age should be enforced.
1107   */
1108  public void setMaxConnectionAgeMillis(final long maxConnectionAge)
1109  {
1110    if (maxConnectionAge > 0L)
1111    {
1112      this.maxConnectionAge = maxConnectionAge;
1113    }
1114    else
1115    {
1116      this.maxConnectionAge = 0L;
1117    }
1118  }
1119
1120
1121
1122  /**
1123   * Retrieves the minimum length of time in milliseconds that should pass
1124   * between connections closed because they have been established for longer
1125   * than the maximum connection age.
1126   *
1127   * @return  The minimum length of time in milliseconds that should pass
1128   *          between connections closed because they have been established for
1129   *          longer than the maximum connection age, or {@code 0L} if expired
1130   *          connections may be closed as quickly as they are identified.
1131   */
1132  public long getMinDisconnectIntervalMillis()
1133  {
1134    return minDisconnectInterval;
1135  }
1136
1137
1138
1139  /**
1140   * Specifies the minimum length of time in milliseconds that should pass
1141   * between connections closed because they have been established for longer
1142   * than the maximum connection age.
1143   *
1144   * @param  minDisconnectInterval  The minimum length of time in milliseconds
1145   *                                that should pass between connections closed
1146   *                                because they have been established for
1147   *                                longer than the maximum connection age.  A
1148   *                                value less than or equal to zero indicates
1149   *                                that no minimum time should be enforced.
1150   */
1151  public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
1152  {
1153    if (minDisconnectInterval > 0)
1154    {
1155      this.minDisconnectInterval = minDisconnectInterval;
1156    }
1157    else
1158    {
1159      this.minDisconnectInterval = 0L;
1160    }
1161  }
1162
1163
1164
1165  /**
1166   * {@inheritDoc}
1167   */
1168  @Override()
1169  public LDAPConnectionPoolHealthCheck getHealthCheck()
1170  {
1171    return healthCheck;
1172  }
1173
1174
1175
1176  /**
1177   * Sets the health check implementation for this connection pool.
1178   *
1179   * @param  healthCheck  The health check implementation for this connection
1180   *                      pool.  It must not be {@code null}.
1181   */
1182  public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
1183  {
1184    ensureNotNull(healthCheck);
1185    this.healthCheck = healthCheck;
1186  }
1187
1188
1189
1190  /**
1191   * {@inheritDoc}
1192   */
1193  @Override()
1194  public long getHealthCheckIntervalMillis()
1195  {
1196    return healthCheckInterval;
1197  }
1198
1199
1200
1201  /**
1202   * {@inheritDoc}
1203   */
1204  @Override()
1205  public void setHealthCheckIntervalMillis(final long healthCheckInterval)
1206  {
1207    ensureTrue(healthCheckInterval > 0L,
1208         "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
1209    this.healthCheckInterval = healthCheckInterval;
1210    healthCheckThread.wakeUp();
1211  }
1212
1213
1214
1215  /**
1216   * {@inheritDoc}
1217   */
1218  @Override()
1219  protected void doHealthCheck()
1220  {
1221    final Iterator<Map.Entry<Thread,LDAPConnection>> iterator =
1222         connections.entrySet().iterator();
1223    while (iterator.hasNext())
1224    {
1225      final Map.Entry<Thread,LDAPConnection> e = iterator.next();
1226      final Thread                           t = e.getKey();
1227      final LDAPConnection                   c = e.getValue();
1228
1229      if (! t.isAlive())
1230      {
1231        c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null,
1232                            null);
1233        c.terminate(null);
1234        iterator.remove();
1235      }
1236    }
1237  }
1238
1239
1240
1241  /**
1242   * {@inheritDoc}
1243   */
1244  @Override()
1245  public int getCurrentAvailableConnections()
1246  {
1247    return -1;
1248  }
1249
1250
1251
1252  /**
1253   * {@inheritDoc}
1254   */
1255  @Override()
1256  public int getMaximumAvailableConnections()
1257  {
1258    return -1;
1259  }
1260
1261
1262
1263  /**
1264   * {@inheritDoc}
1265   */
1266  @Override()
1267  public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
1268  {
1269    return poolStatistics;
1270  }
1271
1272
1273
1274  /**
1275   * Closes this connection pool in the event that it becomes unreferenced.
1276   *
1277   * @throws  Throwable  If an unexpected problem occurs.
1278   */
1279  @Override()
1280  protected void finalize()
1281            throws Throwable
1282  {
1283    super.finalize();
1284
1285    close();
1286  }
1287
1288
1289
1290  /**
1291   * {@inheritDoc}
1292   */
1293  @Override()
1294  public void toString(final StringBuilder buffer)
1295  {
1296    buffer.append("LDAPThreadLocalConnectionPool(");
1297
1298    final String name = connectionPoolName;
1299    if (name != null)
1300    {
1301      buffer.append("name='");
1302      buffer.append(name);
1303      buffer.append("', ");
1304    }
1305
1306    buffer.append("serverSet=");
1307    serverSet.toString(buffer);
1308    buffer.append(')');
1309  }
1310}