001/*
002 * Copyright 2009-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2017 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  @SuppressWarnings("deprecation")
342  private LDAPConnection createConnection()
343          throws LDAPException
344  {
345    final LDAPConnection c;
346    try
347    {
348      c = serverSet.getConnection(healthCheck);
349    }
350    catch (final LDAPException le)
351    {
352      debugException(le);
353      poolStatistics.incrementNumFailedConnectionAttempts();
354      throw le;
355    }
356    c.setConnectionPool(this);
357
358
359    // Auto-reconnect must be disabled for pooled connections, so turn it off
360    // if the associated connection options have it enabled for some reason.
361    LDAPConnectionOptions opts = c.getConnectionOptions();
362    if (opts.autoReconnect())
363    {
364      opts = opts.duplicate();
365      opts.setAutoReconnect(false);
366      c.setConnectionOptions(opts);
367    }
368
369
370    // Invoke pre-authentication post-connect processing.
371    if (postConnectProcessor != null)
372    {
373      try
374      {
375        postConnectProcessor.processPreAuthenticatedConnection(c);
376      }
377      catch (Exception e)
378      {
379        debugException(e);
380
381        try
382        {
383          poolStatistics.incrementNumFailedConnectionAttempts();
384          c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
385          c.terminate(null);
386        }
387        catch (Exception e2)
388        {
389          debugException(e2);
390        }
391
392        if (e instanceof LDAPException)
393        {
394          throw ((LDAPException) e);
395        }
396        else
397        {
398          throw new LDAPException(ResultCode.CONNECT_ERROR,
399               ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
400        }
401      }
402    }
403
404
405    // Authenticate the connection if appropriate.
406    BindResult bindResult = null;
407    try
408    {
409      if (bindRequest != null)
410      {
411        bindResult = c.bind(bindRequest.duplicate());
412      }
413    }
414    catch (final LDAPBindException lbe)
415    {
416      debugException(lbe);
417      bindResult = lbe.getBindResult();
418    }
419    catch (final LDAPException le)
420    {
421      debugException(le);
422      bindResult = new BindResult(le);
423    }
424
425    if (bindResult != null)
426    {
427      try
428      {
429        healthCheck.ensureConnectionValidAfterAuthentication(c, bindResult);
430        if (bindResult.getResultCode() != ResultCode.SUCCESS)
431        {
432          throw new LDAPBindException(bindResult);
433        }
434      }
435      catch (final LDAPException le)
436      {
437        debugException(le);
438
439        try
440        {
441          poolStatistics.incrementNumFailedConnectionAttempts();
442          c.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
443          c.terminate(null);
444        }
445        catch (final Exception e)
446        {
447          debugException(e);
448        }
449
450        throw le;
451      }
452    }
453
454
455    // Invoke post-authentication post-connect processing.
456    if (postConnectProcessor != null)
457    {
458      try
459      {
460        postConnectProcessor.processPostAuthenticatedConnection(c);
461      }
462      catch (Exception e)
463      {
464        debugException(e);
465        try
466        {
467          poolStatistics.incrementNumFailedConnectionAttempts();
468          c.setDisconnectInfo(DisconnectType.POOL_CREATION_FAILURE, null, e);
469          c.terminate(null);
470        }
471        catch (Exception e2)
472        {
473          debugException(e2);
474        }
475
476        if (e instanceof LDAPException)
477        {
478          throw ((LDAPException) e);
479        }
480        else
481        {
482          throw new LDAPException(ResultCode.CONNECT_ERROR,
483               ERR_POOL_POST_CONNECT_ERROR.get(getExceptionMessage(e)), e);
484        }
485      }
486    }
487
488
489    // Get the pooled schema if appropriate.
490    if (opts.usePooledSchema())
491    {
492      final long currentTime = System.currentTimeMillis();
493      if ((pooledSchema == null) || (currentTime > pooledSchema.getFirst()))
494      {
495        try
496        {
497          final Schema schema = c.getSchema();
498          if (schema != null)
499          {
500            c.setCachedSchema(schema);
501
502            final long timeout = opts.getPooledSchemaTimeoutMillis();
503            if ((timeout <= 0L) || (currentTime + timeout <= 0L))
504            {
505              pooledSchema =
506                   new ObjectPair<Long,Schema>(Long.MAX_VALUE, schema);
507            }
508            else
509            {
510              pooledSchema =
511                   new ObjectPair<Long,Schema>((currentTime+timeout), schema);
512            }
513          }
514        }
515        catch (final Exception e)
516        {
517          debugException(e);
518
519          // There was a problem retrieving the schema from the server, but if
520          // we have an earlier copy then we can assume it's still valid.
521          if (pooledSchema != null)
522          {
523            c.setCachedSchema(pooledSchema.getSecond());
524          }
525        }
526      }
527      else
528      {
529        c.setCachedSchema(pooledSchema.getSecond());
530      }
531    }
532
533
534    // Finish setting up the connection.
535    c.setConnectionPoolName(connectionPoolName);
536    poolStatistics.incrementNumSuccessfulConnectionAttempts();
537
538    return c;
539  }
540
541
542
543  /**
544   * {@inheritDoc}
545   */
546  @Override()
547  public void close()
548  {
549    close(true, 1);
550  }
551
552
553
554  /**
555   * {@inheritDoc}
556   */
557  @Override()
558  public void close(final boolean unbind, final int numThreads)
559  {
560    closed = true;
561    healthCheckThread.stopRunning();
562
563    if (numThreads > 1)
564    {
565      final ArrayList<LDAPConnection> connList =
566           new ArrayList<LDAPConnection>(connections.size());
567      final Iterator<LDAPConnection> iterator = connections.values().iterator();
568      while (iterator.hasNext())
569      {
570        connList.add(iterator.next());
571        iterator.remove();
572      }
573
574      if (! connList.isEmpty())
575      {
576        final ParallelPoolCloser closer =
577             new ParallelPoolCloser(connList, unbind, numThreads);
578        closer.closeConnections();
579      }
580    }
581    else
582    {
583      final Iterator<Map.Entry<Thread,LDAPConnection>> iterator =
584           connections.entrySet().iterator();
585      while (iterator.hasNext())
586      {
587        final LDAPConnection conn = iterator.next().getValue();
588        iterator.remove();
589
590        poolStatistics.incrementNumConnectionsClosedUnneeded();
591        conn.setDisconnectInfo(DisconnectType.POOL_CLOSED, null, null);
592        if (unbind)
593        {
594          conn.terminate(null);
595        }
596        else
597        {
598          conn.setClosed();
599        }
600      }
601    }
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public boolean isClosed()
611  {
612    return closed;
613  }
614
615
616
617  /**
618   * Processes a simple bind using a connection from this connection pool, and
619   * then reverts that authentication by re-binding as the same user used to
620   * authenticate new connections.  If new connections are unauthenticated, then
621   * the subsequent bind will be an anonymous simple bind.  This method attempts
622   * to ensure that processing the provided bind operation does not have a
623   * lasting impact the authentication state of the connection used to process
624   * it.
625   * <BR><BR>
626   * If the second bind attempt (the one used to restore the authentication
627   * identity) fails, the connection will be closed as defunct so that a new
628   * connection will be created to take its place.
629   *
630   * @param  bindDN    The bind DN for the simple bind request.
631   * @param  password  The password for the simple bind request.
632   * @param  controls  The optional set of controls for the simple bind request.
633   *
634   * @return  The result of processing the provided bind operation.
635   *
636   * @throws  LDAPException  If the server rejects the bind request, or if a
637   *                         problem occurs while sending the request or reading
638   *                         the response.
639   */
640  public BindResult bindAndRevertAuthentication(final String bindDN,
641                                                final String password,
642                                                final Control... controls)
643         throws LDAPException
644  {
645    return bindAndRevertAuthentication(
646         new SimpleBindRequest(bindDN, password, controls));
647  }
648
649
650
651  /**
652   * Processes the provided bind request using a connection from this connection
653   * pool, and then reverts that authentication by re-binding as the same user
654   * used to authenticate new connections.  If new connections are
655   * unauthenticated, then the subsequent bind will be an anonymous simple bind.
656   * This method attempts to ensure that processing the provided bind operation
657   * does not have a lasting impact the authentication state of the connection
658   * used to process it.
659   * <BR><BR>
660   * If the second bind attempt (the one used to restore the authentication
661   * identity) fails, the connection will be closed as defunct so that a new
662   * connection will be created to take its place.
663   *
664   * @param  bindRequest  The bind request to be processed.  It must not be
665   *                      {@code null}.
666   *
667   * @return  The result of processing the provided bind operation.
668   *
669   * @throws  LDAPException  If the server rejects the bind request, or if a
670   *                         problem occurs while sending the request or reading
671   *                         the response.
672   */
673  public BindResult bindAndRevertAuthentication(final BindRequest bindRequest)
674         throws LDAPException
675  {
676    LDAPConnection conn = getConnection();
677
678    try
679    {
680      final BindResult result = conn.bind(bindRequest);
681      releaseAndReAuthenticateConnection(conn);
682      return result;
683    }
684    catch (final Throwable t)
685    {
686      debugException(t);
687
688      if (t instanceof LDAPException)
689      {
690        final LDAPException le = (LDAPException) t;
691
692        boolean shouldThrow;
693        try
694        {
695          healthCheck.ensureConnectionValidAfterException(conn, le);
696
697          // The above call will throw an exception if the connection doesn't
698          // seem to be valid, so if we've gotten here then we should assume
699          // that it is valid and we will pass the exception onto the client
700          // without retrying the operation.
701          releaseAndReAuthenticateConnection(conn);
702          shouldThrow = true;
703        }
704        catch (final Exception e)
705        {
706          debugException(e);
707
708          // This implies that the connection is not valid.  If the pool is
709          // configured to re-try bind operations on a newly-established
710          // connection, then that will be done later in this method.
711          // Otherwise, release the connection as defunct and pass the bind
712          // exception onto the client.
713          if (! getOperationTypesToRetryDueToInvalidConnections().contains(
714                     OperationType.BIND))
715          {
716            releaseDefunctConnection(conn);
717            shouldThrow = true;
718          }
719          else
720          {
721            shouldThrow = false;
722          }
723        }
724
725        if (shouldThrow)
726        {
727          throw le;
728        }
729      }
730      else
731      {
732        releaseDefunctConnection(conn);
733        throw new LDAPException(ResultCode.LOCAL_ERROR,
734             ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
735      }
736    }
737
738
739    // If we've gotten here, then the bind operation should be re-tried on a
740    // newly-established connection.
741    conn = replaceDefunctConnection(conn);
742
743    try
744    {
745      final BindResult result = conn.bind(bindRequest);
746      releaseAndReAuthenticateConnection(conn);
747      return result;
748    }
749    catch (final Throwable t)
750    {
751      debugException(t);
752
753      if (t instanceof LDAPException)
754      {
755        final LDAPException le = (LDAPException) t;
756
757        try
758        {
759          healthCheck.ensureConnectionValidAfterException(conn, le);
760          releaseAndReAuthenticateConnection(conn);
761        }
762        catch (final Exception e)
763        {
764          debugException(e);
765          releaseDefunctConnection(conn);
766        }
767
768        throw le;
769      }
770      else
771      {
772        releaseDefunctConnection(conn);
773        throw new LDAPException(ResultCode.LOCAL_ERROR,
774             ERR_POOL_OP_EXCEPTION.get(getExceptionMessage(t)), t);
775      }
776    }
777  }
778
779
780
781  /**
782   * {@inheritDoc}
783   */
784  @Override()
785  public LDAPConnection getConnection()
786         throws LDAPException
787  {
788    final Thread t = Thread.currentThread();
789    LDAPConnection conn = connections.get(t);
790
791    if (closed)
792    {
793      if (conn != null)
794      {
795        conn.terminate(null);
796        connections.remove(t);
797      }
798
799      poolStatistics.incrementNumFailedCheckouts();
800      throw new LDAPException(ResultCode.CONNECT_ERROR,
801                              ERR_POOL_CLOSED.get());
802    }
803
804    boolean created = false;
805    if ((conn == null) || (! conn.isConnected()))
806    {
807      conn = createConnection();
808      connections.put(t, conn);
809      created = true;
810    }
811
812    try
813    {
814      healthCheck.ensureConnectionValidForCheckout(conn);
815      if (created)
816      {
817        poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
818      }
819      else
820      {
821        poolStatistics.incrementNumSuccessfulCheckoutsWithoutWaiting();
822      }
823      return conn;
824    }
825    catch (LDAPException le)
826    {
827      debugException(le);
828
829      conn.terminate(null);
830      connections.remove(t);
831
832      if (created)
833      {
834        poolStatistics.incrementNumFailedCheckouts();
835        throw le;
836      }
837    }
838
839    try
840    {
841      conn = createConnection();
842      healthCheck.ensureConnectionValidForCheckout(conn);
843      connections.put(t, conn);
844      poolStatistics.incrementNumSuccessfulCheckoutsNewConnection();
845      return conn;
846    }
847    catch (LDAPException le)
848    {
849      debugException(le);
850
851      poolStatistics.incrementNumFailedCheckouts();
852
853      if (conn != null)
854      {
855        conn.terminate(null);
856      }
857
858      throw le;
859    }
860  }
861
862
863
864  /**
865   * {@inheritDoc}
866   */
867  @Override()
868  public void releaseConnection(final LDAPConnection connection)
869  {
870    if (connection == null)
871    {
872      return;
873    }
874
875    connection.setConnectionPoolName(connectionPoolName);
876    if (connectionIsExpired(connection))
877    {
878      try
879      {
880        final LDAPConnection newConnection = createConnection();
881        connections.put(Thread.currentThread(), newConnection);
882
883        connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_EXPIRED,
884             null, null);
885        connection.terminate(null);
886        poolStatistics.incrementNumConnectionsClosedExpired();
887        lastExpiredDisconnectTime = System.currentTimeMillis();
888      }
889      catch (final LDAPException le)
890      {
891        debugException(le);
892      }
893    }
894
895    try
896    {
897      healthCheck.ensureConnectionValidForRelease(connection);
898    }
899    catch (LDAPException le)
900    {
901      releaseDefunctConnection(connection);
902      return;
903    }
904
905    poolStatistics.incrementNumReleasedValid();
906
907    if (closed)
908    {
909      close();
910    }
911  }
912
913
914
915  /**
916   * Performs a bind on the provided connection before releasing it back to the
917   * pool, so that it will be authenticated as the same user as
918   * newly-established connections.  If newly-established connections are
919   * unauthenticated, then this method will perform an anonymous simple bind to
920   * ensure that the resulting connection is unauthenticated.
921   *
922   * Releases the provided connection back to this pool.
923   *
924   * @param  connection  The connection to be released back to the pool after
925   *                     being re-authenticated.
926   */
927  public void releaseAndReAuthenticateConnection(
928       final LDAPConnection connection)
929  {
930    if (connection == null)
931    {
932      return;
933    }
934
935    try
936    {
937      BindResult bindResult;
938      try
939      {
940        if (bindRequest == null)
941        {
942          bindResult = connection.bind("", "");
943        }
944        else
945        {
946          bindResult = connection.bind(bindRequest.duplicate());
947        }
948      }
949      catch (final LDAPBindException lbe)
950      {
951        debugException(lbe);
952        bindResult = lbe.getBindResult();
953      }
954
955      try
956      {
957        healthCheck.ensureConnectionValidAfterAuthentication(connection,
958             bindResult);
959        if (bindResult.getResultCode() != ResultCode.SUCCESS)
960        {
961          throw new LDAPBindException(bindResult);
962        }
963      }
964      catch (final LDAPException le)
965      {
966        debugException(le);
967
968        try
969        {
970          connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, le);
971          connection.terminate(null);
972          releaseDefunctConnection(connection);
973        }
974        catch (final Exception e)
975        {
976          debugException(e);
977        }
978
979        throw le;
980      }
981
982      releaseConnection(connection);
983    }
984    catch (final Exception e)
985    {
986      debugException(e);
987      releaseDefunctConnection(connection);
988    }
989  }
990
991
992
993  /**
994   * {@inheritDoc}
995   */
996  @Override()
997  public void releaseDefunctConnection(final LDAPConnection connection)
998  {
999    if (connection == null)
1000    {
1001      return;
1002    }
1003
1004    connection.setConnectionPoolName(connectionPoolName);
1005    poolStatistics.incrementNumConnectionsClosedDefunct();
1006    handleDefunctConnection(connection);
1007  }
1008
1009
1010
1011  /**
1012   * Performs the real work of terminating a defunct connection and replacing it
1013   * with a new connection if possible.
1014   *
1015   * @param  connection  The defunct connection to be replaced.
1016   */
1017  private void handleDefunctConnection(final LDAPConnection connection)
1018  {
1019    final Thread t = Thread.currentThread();
1020
1021    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1022                                 null);
1023    connection.terminate(null);
1024    connections.remove(t);
1025
1026    if (closed)
1027    {
1028      return;
1029    }
1030
1031    try
1032    {
1033      final LDAPConnection conn = createConnection();
1034      connections.put(t, conn);
1035    }
1036    catch (LDAPException le)
1037    {
1038      debugException(le);
1039    }
1040  }
1041
1042
1043
1044  /**
1045   * {@inheritDoc}
1046   */
1047  @Override()
1048  public LDAPConnection replaceDefunctConnection(
1049                             final LDAPConnection connection)
1050         throws LDAPException
1051  {
1052    poolStatistics.incrementNumConnectionsClosedDefunct();
1053    connection.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, null,
1054                                 null);
1055    connection.terminate(null);
1056    connections.remove(Thread.currentThread(), connection);
1057
1058    if (closed)
1059    {
1060      throw new LDAPException(ResultCode.CONNECT_ERROR, ERR_POOL_CLOSED.get());
1061    }
1062
1063    final LDAPConnection newConnection = createConnection();
1064    connections.put(Thread.currentThread(), newConnection);
1065    return newConnection;
1066  }
1067
1068
1069
1070  /**
1071   * {@inheritDoc}
1072   */
1073  @Override()
1074  public Set<OperationType> getOperationTypesToRetryDueToInvalidConnections()
1075  {
1076    return retryOperationTypes.get();
1077  }
1078
1079
1080
1081  /**
1082   * {@inheritDoc}
1083   */
1084  @Override()
1085  public void setRetryFailedOperationsDueToInvalidConnections(
1086                   final Set<OperationType> operationTypes)
1087  {
1088    if ((operationTypes == null) || operationTypes.isEmpty())
1089    {
1090      retryOperationTypes.set(
1091           Collections.unmodifiableSet(EnumSet.noneOf(OperationType.class)));
1092    }
1093    else
1094    {
1095      final EnumSet<OperationType> s = EnumSet.noneOf(OperationType.class);
1096      s.addAll(operationTypes);
1097      retryOperationTypes.set(Collections.unmodifiableSet(s));
1098    }
1099  }
1100
1101
1102
1103  /**
1104   * Indicates whether the provided connection should be considered expired.
1105   *
1106   * @param  connection  The connection for which to make the determination.
1107   *
1108   * @return  {@code true} if the provided connection should be considered
1109   *          expired, or {@code false} if not.
1110   */
1111  private boolean connectionIsExpired(final LDAPConnection connection)
1112  {
1113    // If connection expiration is not enabled, then there is nothing to do.
1114    if (maxConnectionAge <= 0L)
1115    {
1116      return false;
1117    }
1118
1119    // If there is a minimum disconnect interval, then make sure that we have
1120    // not closed another expired connection too recently.
1121    final long currentTime = System.currentTimeMillis();
1122    if ((currentTime - lastExpiredDisconnectTime) < minDisconnectInterval)
1123    {
1124      return false;
1125    }
1126
1127    // Get the age of the connection and see if it is expired.
1128    final long connectionAge = currentTime - connection.getConnectTime();
1129    return (connectionAge > maxConnectionAge);
1130  }
1131
1132
1133
1134  /**
1135   * {@inheritDoc}
1136   */
1137  @Override()
1138  public String getConnectionPoolName()
1139  {
1140    return connectionPoolName;
1141  }
1142
1143
1144
1145  /**
1146   * {@inheritDoc}
1147   */
1148  @Override()
1149  public void setConnectionPoolName(final String connectionPoolName)
1150  {
1151    this.connectionPoolName = connectionPoolName;
1152  }
1153
1154
1155
1156  /**
1157   * Retrieves the maximum length of time in milliseconds that a connection in
1158   * this pool may be established before it is closed and replaced with another
1159   * connection.
1160   *
1161   * @return  The maximum length of time in milliseconds that a connection in
1162   *          this pool may be established before it is closed and replaced with
1163   *          another connection, or {@code 0L} if no maximum age should be
1164   *          enforced.
1165   */
1166  public long getMaxConnectionAgeMillis()
1167  {
1168    return maxConnectionAge;
1169  }
1170
1171
1172
1173  /**
1174   * Specifies the maximum length of time in milliseconds that a connection in
1175   * this pool may be established before it should be closed and replaced with
1176   * another connection.
1177   *
1178   * @param  maxConnectionAge  The maximum length of time in milliseconds that a
1179   *                           connection in this pool may be established before
1180   *                           it should be closed and replaced with another
1181   *                           connection.  A value of zero indicates that no
1182   *                           maximum age should be enforced.
1183   */
1184  public void setMaxConnectionAgeMillis(final long maxConnectionAge)
1185  {
1186    if (maxConnectionAge > 0L)
1187    {
1188      this.maxConnectionAge = maxConnectionAge;
1189    }
1190    else
1191    {
1192      this.maxConnectionAge = 0L;
1193    }
1194  }
1195
1196
1197
1198  /**
1199   * Retrieves the minimum length of time in milliseconds that should pass
1200   * between connections closed because they have been established for longer
1201   * than the maximum connection age.
1202   *
1203   * @return  The minimum length of time in milliseconds that should pass
1204   *          between connections closed because they have been established for
1205   *          longer than the maximum connection age, or {@code 0L} if expired
1206   *          connections may be closed as quickly as they are identified.
1207   */
1208  public long getMinDisconnectIntervalMillis()
1209  {
1210    return minDisconnectInterval;
1211  }
1212
1213
1214
1215  /**
1216   * Specifies the minimum length of time in milliseconds that should pass
1217   * between connections closed because they have been established for longer
1218   * than the maximum connection age.
1219   *
1220   * @param  minDisconnectInterval  The minimum length of time in milliseconds
1221   *                                that should pass between connections closed
1222   *                                because they have been established for
1223   *                                longer than the maximum connection age.  A
1224   *                                value less than or equal to zero indicates
1225   *                                that no minimum time should be enforced.
1226   */
1227  public void setMinDisconnectIntervalMillis(final long minDisconnectInterval)
1228  {
1229    if (minDisconnectInterval > 0)
1230    {
1231      this.minDisconnectInterval = minDisconnectInterval;
1232    }
1233    else
1234    {
1235      this.minDisconnectInterval = 0L;
1236    }
1237  }
1238
1239
1240
1241  /**
1242   * {@inheritDoc}
1243   */
1244  @Override()
1245  public LDAPConnectionPoolHealthCheck getHealthCheck()
1246  {
1247    return healthCheck;
1248  }
1249
1250
1251
1252  /**
1253   * Sets the health check implementation for this connection pool.
1254   *
1255   * @param  healthCheck  The health check implementation for this connection
1256   *                      pool.  It must not be {@code null}.
1257   */
1258  public void setHealthCheck(final LDAPConnectionPoolHealthCheck healthCheck)
1259  {
1260    ensureNotNull(healthCheck);
1261    this.healthCheck = healthCheck;
1262  }
1263
1264
1265
1266  /**
1267   * {@inheritDoc}
1268   */
1269  @Override()
1270  public long getHealthCheckIntervalMillis()
1271  {
1272    return healthCheckInterval;
1273  }
1274
1275
1276
1277  /**
1278   * {@inheritDoc}
1279   */
1280  @Override()
1281  public void setHealthCheckIntervalMillis(final long healthCheckInterval)
1282  {
1283    ensureTrue(healthCheckInterval > 0L,
1284         "LDAPConnectionPool.healthCheckInterval must be greater than 0.");
1285    this.healthCheckInterval = healthCheckInterval;
1286    healthCheckThread.wakeUp();
1287  }
1288
1289
1290
1291  /**
1292   * {@inheritDoc}
1293   */
1294  @Override()
1295  protected void doHealthCheck()
1296  {
1297    final Iterator<Map.Entry<Thread,LDAPConnection>> iterator =
1298         connections.entrySet().iterator();
1299    while (iterator.hasNext())
1300    {
1301      final Map.Entry<Thread,LDAPConnection> e = iterator.next();
1302      final Thread                           t = e.getKey();
1303      final LDAPConnection                   c = e.getValue();
1304
1305      if (! t.isAlive())
1306      {
1307        c.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_UNNEEDED, null,
1308                            null);
1309        c.terminate(null);
1310        iterator.remove();
1311      }
1312    }
1313  }
1314
1315
1316
1317  /**
1318   * {@inheritDoc}
1319   */
1320  @Override()
1321  public int getCurrentAvailableConnections()
1322  {
1323    return -1;
1324  }
1325
1326
1327
1328  /**
1329   * {@inheritDoc}
1330   */
1331  @Override()
1332  public int getMaximumAvailableConnections()
1333  {
1334    return -1;
1335  }
1336
1337
1338
1339  /**
1340   * {@inheritDoc}
1341   */
1342  @Override()
1343  public LDAPConnectionPoolStatistics getConnectionPoolStatistics()
1344  {
1345    return poolStatistics;
1346  }
1347
1348
1349
1350  /**
1351   * Closes this connection pool in the event that it becomes unreferenced.
1352   *
1353   * @throws  Throwable  If an unexpected problem occurs.
1354   */
1355  @Override()
1356  protected void finalize()
1357            throws Throwable
1358  {
1359    super.finalize();
1360
1361    close();
1362  }
1363
1364
1365
1366  /**
1367   * {@inheritDoc}
1368   */
1369  @Override()
1370  public void toString(final StringBuilder buffer)
1371  {
1372    buffer.append("LDAPThreadLocalConnectionPool(");
1373
1374    final String name = connectionPoolName;
1375    if (name != null)
1376    {
1377      buffer.append("name='");
1378      buffer.append(name);
1379      buffer.append("', ");
1380    }
1381
1382    buffer.append("serverSet=");
1383    serverSet.toString(buffer);
1384    buffer.append(')');
1385  }
1386}