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}