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.io.Serializable;
026
027import com.unboundid.util.NotMutable;
028import com.unboundid.util.ThreadSafety;
029import com.unboundid.util.ThreadSafetyLevel;
030
031import static com.unboundid.ldap.sdk.LDAPMessages.*;
032import static com.unboundid.util.Debug.*;
033import static com.unboundid.util.StaticUtils.*;
034
035
036
037/**
038 * This class provides an LDAP connection pool health check implementation that
039 * may be used to check the health of the associated server by verifying that a
040 * specified entry can be retrieved in an acceptable period of time.  If the
041 * entry cannot be retrieved (either because it does not exist, or because an
042 * error occurs while attempting to retrieve it), or if it takes too long to
043 * retrieve the entry, then the associated connection will be classified as
044 * unavailable.
045 * <BR><BR>
046 * It is possible to control under which conditions an attempt should be made to
047 * retrieve the target entry, and also to specify a maximum acceptable response
048 * time.  For best results, the target entry should be available to be retrieved
049 * by a client with any authentication state.
050 */
051@NotMutable()
052@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
053public final class GetEntryLDAPConnectionPoolHealthCheck
054       extends LDAPConnectionPoolHealthCheck
055       implements Serializable
056{
057  /**
058   * The default maximum response time value in milliseconds, which is set to
059   * 30,000 milliseconds or 30 seconds.
060   */
061  private static final long DEFAULT_MAX_RESPONSE_TIME = 30000L;
062
063
064
065  /**
066   * The serial version UID for this serializable class.
067   */
068  private static final long serialVersionUID = -3400259782503254645L;
069
070
071
072  // Indicates whether to invoke the test after a connection has been
073  // authenticated.
074  private final boolean invokeAfterAuthentication;
075
076  // Indicates whether to invoke the test during background health checks.
077  private final boolean invokeForBackgroundChecks;
078
079  // Indicates whether to invoke the test when checking out a connection.
080  private final boolean invokeOnCheckout;
081
082  // Indicates whether to invoke the test when creating a new connection.
083  private final boolean invokeOnCreate;
084
085  // Indicates whether to invoke the test whenever an exception is encountered
086  // when using the connection.
087  private final boolean invokeOnException;
088
089  // Indicates whether to invoke the test when releasing a connection.
090  private final boolean invokeOnRelease;
091
092  // The maximum response time value in milliseconds.
093  private final long maxResponseTime;
094
095  // The search request to send to the server.
096  private final SearchRequest searchRequest;
097
098  // The DN of the entry to retrieve.
099  private final String entryDN;
100
101
102
103  /**
104   * Creates a new instance of this get entry LDAP connection pool health check.
105   *
106   * @param  entryDN                    The DN of the entry to retrieve from
107   *                                    the target server.  If this is
108   *                                    {@code null}, then the server's root DSE
109   *                                    will be used.
110   * @param  maxResponseTime            The maximum length of time in
111   *                                    milliseconds that should be allowed when
112   *                                    attempting to retrieve the entry.  If
113   *                                    the provided value is less than or equal
114   *                                    to zero, then the default value of 30000
115   *                                    milliseconds (30 seconds) will be used.
116   * @param  invokeOnCreate             Indicates whether to test for the
117   *                                    existence of the target entry whenever a
118   *                                    new connection is created for use in the
119   *                                    pool.  Note that this check will be
120   *                                    performed immediately after the
121   *                                    connection has been established and
122   *                                    before any attempt has been made to
123   *                                    authenticate that connection.
124   * @param  invokeOnCheckout           Indicates whether to test for the
125   *                                    existence of the target entry
126   *                                    immediately before a connection is
127   *                                    checked out of the pool.
128   * @param  invokeOnRelease            Indicates whether to test for the
129   *                                    existence of the target entry
130   *                                    immediately after a connection has been
131   *                                    released back to the pool.
132   * @param  invokeForBackgroundChecks  Indicates whether to test for the
133   *                                    existence of the target entry during
134   *                                    periodic background health checks.
135   * @param  invokeOnException          Indicates whether to test for the
136   *                                    existence of the target entry if an
137   *                                    exception is encountered when using the
138   *                                    connection.
139   */
140  public GetEntryLDAPConnectionPoolHealthCheck(final String entryDN,
141              final long maxResponseTime, final boolean invokeOnCreate,
142              final boolean invokeOnCheckout, final boolean invokeOnRelease,
143              final boolean invokeForBackgroundChecks,
144              final boolean invokeOnException)
145  {
146    this(entryDN, maxResponseTime, invokeOnCreate, false, invokeOnCheckout,
147         invokeOnRelease, invokeForBackgroundChecks, invokeOnException);
148  }
149
150
151
152  /**
153   * Creates a new instance of this get entry LDAP connection pool health check.
154   *
155   * @param  entryDN
156   *              The DN of the entry to retrieve from the target server.  If
157   *              this is {@code null}, then the server's root DSE will be used.
158   * @param  maxResponseTime
159   *              The maximum length of time in milliseconds that should be
160   *              allowed when attempting to retrieve the entry.  If the
161   *              provided value is less than or equal to zero, then the
162   *              default value of 30000 milliseconds (30 seconds) will be used.
163   * @param  invokeOnCreate
164   *              Indicates whether to test for the existence of the target
165   *              entry whenever a new connection is created for use in the
166   *              pool.  Note that this check will be performed immediately
167   *              after the connection has been established and before any
168   *              attempt has been made to authenticate that connection.
169   * @param  invokeAfterAuthentication
170   *              Indicates whether to test for the existence of the target
171   *              entry immediately after a connection has been authenticated.
172   *              This includes immediately after a newly-created connection
173   *              has been authenticated, after a call to the connection pool's
174   *              {@code bindAndRevertAuthentication} method, and after a call
175   *              to the connection pool's
176   *              {@code releaseAndReAuthenticateConnection} method.  Note that
177   *              even if this is {@code true}, the health check will only be
178   *              performed if the provided bind result indicates that the bind
179   *              was successful.
180   * @param  invokeOnCheckout
181   *              Indicates whether to test for the existence of the target
182   *              entry immediately before a connection is checked out of the
183   *              pool.
184   * @param  invokeOnRelease
185   *              Indicates whether to test for the existence of the target
186   *              entry immediately after a connection has been released back
187   *              to the pool.
188   * @param  invokeForBackgroundChecks
189   *              Indicates whether to test for the existence of the target
190   *              entry during periodic background health checks.
191   * @param  invokeOnException
192   *              Indicates whether to test for the existence of the target
193   *              entry if an exception is encountered when using the
194   *              connection.
195   */
196  public GetEntryLDAPConnectionPoolHealthCheck(final String entryDN,
197              final long maxResponseTime, final boolean invokeOnCreate,
198              final boolean invokeAfterAuthentication,
199              final boolean invokeOnCheckout, final boolean invokeOnRelease,
200              final boolean invokeForBackgroundChecks,
201              final boolean invokeOnException)
202  {
203    this.invokeOnCreate            = invokeOnCreate;
204    this.invokeAfterAuthentication = invokeAfterAuthentication;
205    this.invokeOnCheckout          = invokeOnCheckout;
206    this.invokeOnRelease           = invokeOnRelease;
207    this.invokeForBackgroundChecks = invokeForBackgroundChecks;
208    this.invokeOnException         = invokeOnException;
209
210    if (entryDN == null)
211    {
212      this.entryDN = "";
213    }
214    else
215    {
216      this.entryDN = entryDN;
217    }
218
219    if (maxResponseTime > 0L)
220    {
221      this.maxResponseTime = maxResponseTime;
222    }
223    else
224    {
225      this.maxResponseTime = DEFAULT_MAX_RESPONSE_TIME;
226    }
227
228    searchRequest = new SearchRequest(this.entryDN, SearchScope.BASE,
229         Filter.createPresenceFilter("objectClass"), "1.1");
230    searchRequest.setResponseTimeoutMillis(this.maxResponseTime);
231  }
232
233
234
235  /**
236   * {@inheritDoc}
237   */
238  @Override()
239  public void ensureNewConnectionValid(final LDAPConnection connection)
240         throws LDAPException
241  {
242    if (invokeOnCreate)
243    {
244      getEntry(connection);
245    }
246  }
247
248
249
250  /**
251   * {@inheritDoc}
252   */
253  @Override()
254  public void ensureConnectionValidAfterAuthentication(
255                   final LDAPConnection connection, final BindResult bindResult)
256         throws LDAPException
257  {
258    if (invokeAfterAuthentication &&
259         (bindResult.getResultCode() == ResultCode.SUCCESS))
260    {
261      getEntry(connection);
262    }
263  }
264
265
266
267  /**
268   * {@inheritDoc}
269   */
270  @Override()
271  public void ensureConnectionValidForCheckout(final LDAPConnection connection)
272         throws LDAPException
273  {
274    if (invokeOnCheckout)
275    {
276      getEntry(connection);
277    }
278  }
279
280
281
282  /**
283   * {@inheritDoc}
284   */
285  @Override()
286  public void ensureConnectionValidForRelease(final LDAPConnection connection)
287         throws LDAPException
288  {
289    if (invokeOnRelease)
290    {
291      getEntry(connection);
292    }
293  }
294
295
296
297  /**
298   * {@inheritDoc}
299   */
300  @Override()
301  public void ensureConnectionValidForContinuedUse(
302                   final LDAPConnection connection)
303         throws LDAPException
304  {
305    if (invokeForBackgroundChecks)
306    {
307      getEntry(connection);
308    }
309  }
310
311
312
313  /**
314   * {@inheritDoc}
315   */
316  @Override()
317  public void ensureConnectionValidAfterException(
318                   final LDAPConnection connection,
319                   final LDAPException exception)
320         throws LDAPException
321  {
322    if (invokeOnException &&
323         (! ResultCode.isConnectionUsable(exception.getResultCode())))
324    {
325      getEntry(connection);
326    }
327  }
328
329
330
331  /**
332   * Retrieves the DN of the entry that will be retrieved when performing the
333   * health checks.
334   *
335   * @return  The DN of the entry that will be retrieved when performing the
336   *          health checks.
337   */
338  public String getEntryDN()
339  {
340    return entryDN;
341  }
342
343
344
345  /**
346   * Retrieves the maximum length of time in milliseconds that this health
347   * check should wait for the entry to be returned.
348   *
349   * @return  The maximum length of time in milliseconds that this health check
350   *          should wait for the entry to be returned.
351   */
352  public long getMaxResponseTimeMillis()
353  {
354    return maxResponseTime;
355  }
356
357
358
359  /**
360   * Indicates whether this health check will test for the existence of the
361   * target entry whenever a new connection is created.
362   *
363   * @return  {@code true} if this health check will test for the existence of
364   *          the target entry whenever a new connection is created, or
365   *          {@code false} if not.
366   */
367  public boolean invokeOnCreate()
368  {
369    return invokeOnCreate;
370  }
371
372
373
374  /**
375   * Indicates whether this health check will test for the existence of the
376   * target entry after a connection has been authenticated, including after
377   * authenticating a newly-created connection, as well as after calls to the
378   * connection pool's {@code bindAndRevertAuthentication} and
379   * {@code releaseAndReAuthenticateConnection} methods.
380   *
381   * @return  {@code true} if this health check will test for the existence of
382   *          the target entry whenever a connection has been authenticated, or
383   *          {@code false} if not.
384   */
385  public boolean invokeAfterAuthentication()
386  {
387    return invokeAfterAuthentication;
388  }
389
390
391
392  /**
393   * Indicates whether this health check will test for the existence of the
394   * target entry whenever a connection is to be checked out for use.
395   *
396   * @return  {@code true} if this health check will test for the existence of
397   *          the target entry whenever a connection is to be checked out, or
398   *          {@code false} if not.
399   */
400  public boolean invokeOnCheckout()
401  {
402    return invokeOnCheckout;
403  }
404
405
406
407  /**
408   * Indicates whether this health check will test for the existence of the
409   * target entry whenever a connection is to be released back to the pool.
410   *
411   * @return  {@code true} if this health check will test for the existence of
412   *          the target entry whenever a connection is to be released, or
413   *          {@code false} if not.
414   */
415  public boolean invokeOnRelease()
416  {
417    return invokeOnRelease;
418  }
419
420
421
422  /**
423   * Indicates whether this health check will test for the existence of the
424   * target entry during periodic background health checks.
425   *
426   * @return  {@code true} if this health check will test for the existence of
427   *          the target entry during periodic background health checks, or
428   *          {@code false} if not.
429   */
430  public boolean invokeForBackgroundChecks()
431  {
432    return invokeForBackgroundChecks;
433  }
434
435
436
437  /**
438   * Indicates whether this health check will test for the existence of the
439   * target entry if an exception is caught while processing an operation on a
440   * connection.
441   *
442   * @return  {@code true} if this health check will test for the existence of
443   *          the target entry whenever an exception is caught, or {@code false}
444   *          if not.
445   */
446  public boolean invokeOnException()
447  {
448    return invokeOnException;
449  }
450
451
452
453  /**
454   * Attempts to retrieve the target entry.  If the attempt fails, or if the
455   * connection takes too long then an exception will be thrown.
456   *
457   * @param  conn  The connection to be checked.
458   *
459   * @throws  LDAPException  If a problem occurs while trying to retrieve the
460   *                         entry, or if it cannot be retrieved in an
461   *                         acceptable length of time.
462   */
463  private void getEntry(final LDAPConnection conn)
464          throws LDAPException
465  {
466    try
467    {
468      final SearchResult result = conn.search(searchRequest.duplicate());
469      if (result.getEntryCount() != 1)
470      {
471        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
472             ERR_GET_ENTRY_HEALTH_CHECK_NO_ENTRY_RETURNED.get());
473      }
474    }
475    catch (final Exception e)
476    {
477      debugException(e);
478
479      final String msg = ERR_GET_ENTRY_HEALTH_CHECK_FAILURE.get(entryDN,
480           getExceptionMessage(e));
481
482      conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, msg, e);
483      throw new LDAPException(ResultCode.SERVER_DOWN, msg, e);
484    }
485  }
486
487
488
489  /**
490   * {@inheritDoc}
491   */
492  @Override()
493  public void toString(final StringBuilder buffer)
494  {
495    buffer.append("GetEntryLDAPConnectionPoolHealthCheck(entryDN='");
496    buffer.append(entryDN);
497    buffer.append("', maxResponseTimeMillis=");
498    buffer.append(maxResponseTime);
499    buffer.append(", invokeOnCreate=");
500    buffer.append(invokeOnCreate);
501    buffer.append(", invokeAfterAuthentication=");
502    buffer.append(invokeAfterAuthentication);
503    buffer.append(", invokeOnCheckout=");
504    buffer.append(invokeOnCheckout);
505    buffer.append(", invokeOnRelease=");
506    buffer.append(invokeOnRelease);
507    buffer.append(", invokeForBackgroundChecks=");
508    buffer.append(invokeForBackgroundChecks);
509    buffer.append(", invokeOnException=");
510    buffer.append(invokeOnException);
511    buffer.append(')');
512  }
513}