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