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