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