001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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 com.unboundid.util.Debug; 041import com.unboundid.util.Extensible; 042import com.unboundid.util.NotNull; 043import com.unboundid.util.Nullable; 044import com.unboundid.util.ThreadSafety; 045import com.unboundid.util.ThreadSafetyLevel; 046 047 048 049/** 050 * This class defines an API that can be used to select between multiple 051 * directory servers when establishing a connection. Implementations are free 052 * to use any kind of logic that they desire when selecting the server to which 053 * the connection is to be established. They may also support the use of 054 * health checks to determine whether the created connections are suitable for 055 * use. 056 * <BR><BR> 057 * Implementations MUST be threadsafe to allow for multiple concurrent attempts 058 * to establish new connections. 059 */ 060@Extensible() 061@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 062public abstract class ServerSet 063{ 064 /** 065 * Creates a new instance of this server set. 066 */ 067 protected ServerSet() 068 { 069 // No implementation is required. 070 } 071 072 073 074 /** 075 * Indicates whether connections created by this server set will be 076 * authenticated. 077 * 078 * @return {@code true} if connections created by this server set will be 079 * authenticated, or {@code false} if not. 080 */ 081 public boolean includesAuthentication() 082 { 083 return false; 084 } 085 086 087 088 /** 089 * Indicates whether connections created by this server set will have 090 * post-connect processing performed. 091 * 092 * @return {@code true} if connections created by this server set will have 093 * post-connect processing performed, or {@code false} if not. 094 */ 095 public boolean includesPostConnectProcessing() 096 { 097 return false; 098 } 099 100 101 102 /** 103 * Attempts to establish a connection to one of the directory servers in this 104 * server set. The connection that is returned must be established. The 105 * {@link #includesAuthentication()} must return true if and only if the 106 * connection will also be authenticated, and the 107 * {@link #includesPostConnectProcessing()} method must return true if and 108 * only if pre-authentication and post-authentication post-connect processing 109 * will have been performed. The caller may determine the server to which the 110 * connection is established using the 111 * {@link LDAPConnection#getConnectedAddress} and 112 * {@link LDAPConnection#getConnectedPort} methods. 113 * 114 * @return An {@code LDAPConnection} object that is established to one of the 115 * servers in this server set. 116 * 117 * @throws LDAPException If it is not possible to establish a connection to 118 * any of the servers in this server set. 119 */ 120 @NotNull() 121 public abstract LDAPConnection getConnection() 122 throws LDAPException; 123 124 125 126 /** 127 * Attempts to establish a connection to one of the directory servers in this 128 * server set, using the provided health check to further validate the 129 * connection. The connection that is returned must be established. The 130 * {@link #includesAuthentication()} must return true if and only if the 131 * connection will also be authenticated, and the 132 * {@link #includesPostConnectProcessing()} method must return true if and 133 * only if pre-authentication and post-authentication post-connect processing 134 * will have been performed. The caller may determine the server to which the 135 * connection is established using the 136 * {@link LDAPConnection#getConnectedAddress} and 137 * {@link LDAPConnection#getConnectedPort} methods. 138 * 139 * @param healthCheck The health check to use to verify the health of the 140 * newly-created connection. It may be {@code null} if 141 * no additional health check should be performed. If it 142 * is non-{@code null} and this server set performs 143 * authentication, then the health check's 144 * {@code ensureConnectionValidAfterAuthentication} 145 * method will be invoked immediately after the bind 146 * operation is processed (regardless of whether the bind 147 * was successful or not). And regardless of whether 148 * this server set performs authentication, the 149 * health check's {@code ensureNewConnectionValid} 150 * method must be invoked on the connection to ensure 151 * that it is valid immediately before it is returned. 152 * 153 * @return An {@code LDAPConnection} object that is established to one of the 154 * servers in this server set. 155 * 156 * @throws LDAPException If it is not possible to establish a connection to 157 * any of the servers in this server set. 158 */ 159 @NotNull() 160 public LDAPConnection getConnection( 161 @Nullable final LDAPConnectionPoolHealthCheck healthCheck) 162 throws LDAPException 163 { 164 final LDAPConnection c = getConnection(); 165 166 if (healthCheck != null) 167 { 168 try 169 { 170 healthCheck.ensureNewConnectionValid(c); 171 } 172 catch (final LDAPException le) 173 { 174 Debug.debugException(le); 175 c.close(); 176 throw le; 177 } 178 } 179 180 return c; 181 } 182 183 184 185 /** 186 * Performs the appropriate bind, post-connect, and health check processing 187 * for the provided connection, in the provided order. The processing 188 * performed will include: 189 * <OL> 190 * <LI> 191 * If the provided {@code postConnectProcessor} is not {@code null}, then 192 * invoke its {@code processPreAuthenticatedConnection} method on the 193 * provided connection. If this method throws an {@code LDAPException}, 194 * then it will propagated up to the caller of this method. 195 * </LI> 196 * <LI> 197 * If the provided {@code bindRequest} is not {@code null}, then 198 * authenticate the connection using that request. If the provided 199 * {@code healthCheck} is also not {@code null}, then invoke its 200 * {@code ensureConnectionValidAfterAuthentication} method on the 201 * connection, even if the bind was not successful. If the health check 202 * throws an {@code LDAPException}, then it will be propagated up to the 203 * caller of this method. If there is no health check or if it did not 204 * throw an exception but the bind attempt did throw an exception, then 205 * propagate that exception instead. 206 * </LI> 207 * <LI> 208 * If the provided {@code postConnectProcessor} is not {@code null}, then 209 * invoke its {@code processPostAuthenticatedConnection} method on the 210 * provided connection. If this method throws an {@code LDAPException}, 211 * then it will propagated up to the caller of this method. 212 * </LI> 213 * <LI> 214 * If the provided {@code healthCheck} is not {@code null}, then invoke 215 * its {@code ensureNewConnectionValid} method on the provided connection. 216 * If this method throws an {@code LDAPException}, then it will be 217 * propagated up to the caller of this method. 218 * </LI> 219 * </OL> 220 * 221 * @param connection The connection to be processed. It must not 222 * be {@code null}, and it must be established. 223 * Note that if an {@code LDAPException} is 224 * thrown by this method or anything that it 225 * calls, then the connection will have been 226 * closed before that exception has been 227 * propagated up to the caller of this method. 228 * @param bindRequest The bind request to use to authenticate the 229 * connection. It may be {@code null} if no 230 * authentication should be performed. 231 * @param postConnectProcessor The post-connect processor to invoke on the 232 * provided connection. It may be {@code null} 233 * if no post-connect processing should be 234 * performed. 235 * @param healthCheck The health check to use to verify the health 236 * of connection. It may be {@code null} if no 237 * health check processing should be performed. 238 * 239 * @throws LDAPException If a problem is encountered during any of the 240 * processing performed by this method. If an 241 * exception is thrown, then the provided connection 242 * will have been closed. 243 */ 244 protected static void doBindPostConnectAndHealthCheckProcessing( 245 @NotNull final LDAPConnection connection, 246 @Nullable final BindRequest bindRequest, 247 @Nullable final PostConnectProcessor postConnectProcessor, 248 @Nullable final LDAPConnectionPoolHealthCheck healthCheck) 249 throws LDAPException 250 { 251 try 252 { 253 if (postConnectProcessor != null) 254 { 255 postConnectProcessor.processPreAuthenticatedConnection(connection); 256 } 257 258 if (bindRequest != null) 259 { 260 BindResult bindResult; 261 LDAPException bindException = null; 262 try 263 { 264 bindResult = connection.bind(bindRequest.duplicate()); 265 } 266 catch (final LDAPException le) 267 { 268 Debug.debugException(le); 269 bindException = le; 270 bindResult = new BindResult(le); 271 } 272 273 if (healthCheck != null) 274 { 275 healthCheck.ensureConnectionValidAfterAuthentication(connection, 276 bindResult); 277 } 278 279 if (bindException != null) 280 { 281 throw bindException; 282 } 283 } 284 285 if (postConnectProcessor != null) 286 { 287 postConnectProcessor.processPostAuthenticatedConnection(connection); 288 } 289 290 if (healthCheck != null) 291 { 292 healthCheck.ensureNewConnectionValid(connection); 293 } 294 } 295 catch (final LDAPException le) 296 { 297 Debug.debugException(le); 298 connection.closeWithoutUnbind(); 299 throw le; 300 } 301 } 302 303 304 305 /** 306 * Updates the provided connection to indicate that it was created by this 307 * server set. 308 * 309 * @param connection The connection to be updated to indicate it was created 310 * by this server set. 311 */ 312 protected final void associateConnectionWithThisServerSet( 313 @NotNull final LDAPConnection connection) 314 { 315 if (connection != null) 316 { 317 connection.setServerSet(this); 318 } 319 } 320 321 322 323 /** 324 * Performs any processing that may be required when the provided connection 325 * is closed. This will only be invoked for connections created by this 326 * server set, and only if the {@link #associateConnectionWithThisServerSet} 327 * method was called on the connection when it was created by this server set. 328 * 329 * @param connection The connection that has been closed. 330 * @param host The address of the server to which the connection 331 * had been established. 332 * @param port The port of the server to which the connection had 333 * been established. 334 * @param disconnectType The disconnect type, which provides general 335 * information about the nature of the disconnect. 336 * @param message A message that may be associated with the 337 * disconnect. It may be {@code null} if no message 338 * is available. 339 * @param cause A {@code Throwable} that was caught and triggered 340 * the disconnect. It may be {@code null} if the 341 * disconnect was not triggered by a client-side 342 * exception or error. 343 */ 344 protected void handleConnectionClosed( 345 @NotNull final LDAPConnection connection, 346 @NotNull final String host, final int port, 347 @NotNull final DisconnectType disconnectType, 348 @Nullable final String message, 349 @Nullable final Throwable cause) 350 { 351 // No action is taken by default. 352 } 353 354 355 356 /** 357 * Shuts down this server set and frees any resources associated with it. 358 */ 359 public void shutDown() 360 { 361 // No implementation required by default. 362 } 363 364 365 366 /** 367 * Retrieves a string representation of this server set. 368 * 369 * @return A string representation of this server set. 370 */ 371 @Override() 372 @NotNull() 373 public String toString() 374 { 375 final StringBuilder buffer = new StringBuilder(); 376 toString(buffer); 377 return buffer.toString(); 378 } 379 380 381 382 /** 383 * Appends a string representation of this server set to the provided buffer. 384 * 385 * @param buffer The buffer to which the string representation should be 386 * appended. 387 */ 388 public void toString(@NotNull final StringBuilder buffer) 389 { 390 buffer.append("ServerSet(className="); 391 buffer.append(getClass().getName()); 392 buffer.append(')'); 393 } 394}