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