001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-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) 2012-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.util.concurrent.ArrayBlockingQueue; 041import java.util.concurrent.TimeUnit; 042import java.util.concurrent.atomic.AtomicBoolean; 043import javax.net.SocketFactory; 044 045import com.unboundid.util.Debug; 046import com.unboundid.util.NotMutable; 047import com.unboundid.util.NotNull; 048import com.unboundid.util.Nullable; 049import com.unboundid.util.StaticUtils; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.Validator; 053 054import static com.unboundid.ldap.sdk.LDAPMessages.*; 055 056 057 058/** 059 * This class provides a server set implementation that will attempt to 060 * establish connections to all associated servers in parallel, keeping the one 061 * that was first to be successfully established and closing all others. 062 * <BR><BR> 063 * Note that this server set implementation may only be used in conjunction with 064 * connection options that allow the associated socket factory to create 065 * multiple connections in parallel. If the 066 * {@link LDAPConnectionOptions#allowConcurrentSocketFactoryUse} method returns 067 * false for the associated connection options, then the {@code getConnection} 068 * methods will throw an exception. 069 * <BR><BR> 070 * <H2>Example</H2> 071 * The following example demonstrates the process for creating a fastest connect 072 * server set that may be used to establish connections to either of two 073 * servers. When using the server set to attempt to create a connection, it 074 * will try both in parallel and will return the first connection that it is 075 * able to establish: 076 * <PRE> 077 * // Create arrays with the addresses and ports of the directory server 078 * // instances. 079 * String[] addresses = 080 * { 081 * server1Address, 082 * server2Address 083 * }; 084 * int[] ports = 085 * { 086 * server1Port, 087 * server2Port 088 * }; 089 * 090 * // Create the server set using the address and port arrays. 091 * FastestConnectServerSet fastestConnectSet = 092 * new FastestConnectServerSet(addresses, ports); 093 * 094 * // Verify that we can establish a single connection using the server set. 095 * LDAPConnection connection = fastestConnectSet.getConnection(); 096 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 097 * connection.close(); 098 * 099 * // Verify that we can establish a connection pool using the server set. 100 * SimpleBindRequest bindRequest = 101 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 102 * LDAPConnectionPool pool = 103 * new LDAPConnectionPool(fastestConnectSet, bindRequest, 10); 104 * RootDSE rootDSEFromPool = pool.getRootDSE(); 105 * pool.close(); 106 * </PRE> 107 */ 108@NotMutable() 109@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 110public final class FastestConnectServerSet 111 extends ServerSet 112{ 113 // The bind request to use to authenticate connections created by this 114 // server set. 115 @Nullable private final BindRequest bindRequest; 116 117 // The port numbers of the target servers. 118 @NotNull private final int[] ports; 119 120 // The set of connection options to use for new connections. 121 @NotNull private final LDAPConnectionOptions connectionOptions; 122 123 // The post-connect processor to invoke against connections created by this 124 // server set. 125 @Nullable private final PostConnectProcessor postConnectProcessor; 126 127 // The socket factory to use to establish connections. 128 @NotNull private final SocketFactory socketFactory; 129 130 // The addresses of the target servers. 131 @NotNull private final String[] addresses; 132 133 134 135 /** 136 * Creates a new fastest connect server set with the specified set of 137 * directory server addresses and port numbers. It will use the default 138 * socket factory provided by the JVM to create the underlying sockets. 139 * 140 * @param addresses The addresses of the directory servers to which the 141 * connections should be established. It must not be 142 * {@code null} or empty. 143 * @param ports The ports of the directory servers to which the 144 * connections should be established. It must not be 145 * {@code null}, and it must have the same number of 146 * elements as the {@code addresses} array. The order of 147 * elements in the {@code addresses} array must correspond 148 * to the order of elements in the {@code ports} array. 149 */ 150 public FastestConnectServerSet(@NotNull final String[] addresses, 151 @NotNull final int[] ports) 152 { 153 this(addresses, ports, null, null); 154 } 155 156 157 158 /** 159 * Creates a new fastest connect server set with the specified set of 160 * directory server addresses and port numbers. It will use the default 161 * socket factory provided by the JVM to create the underlying sockets. 162 * 163 * @param addresses The addresses of the directory servers to which 164 * the connections should be established. It must 165 * not be {@code null} or empty. 166 * @param ports The ports of the directory servers to which the 167 * connections should be established. It must not 168 * be {@code null}, and it must have the same 169 * number of elements as the {@code addresses} 170 * array. The order of elements in the 171 * {@code addresses} array must correspond to the 172 * order of elements in the {@code ports} array. 173 * @param connectionOptions The set of connection options to use for the 174 * underlying connections. 175 */ 176 public FastestConnectServerSet(@NotNull final String[] addresses, 177 @NotNull final int[] ports, 178 @Nullable final LDAPConnectionOptions connectionOptions) 179 { 180 this(addresses, ports, null, connectionOptions); 181 } 182 183 184 185 /** 186 * Creates a new fastest connect server set with the specified set of 187 * directory server addresses and port numbers. It will use the provided 188 * socket factory to create the underlying sockets. 189 * 190 * @param addresses The addresses of the directory servers to which the 191 * connections should be established. It must not be 192 * {@code null} or empty. 193 * @param ports The ports of the directory servers to which the 194 * connections should be established. It must not be 195 * {@code null}, and it must have the same number of 196 * elements as the {@code addresses} array. The order 197 * of elements in the {@code addresses} array must 198 * correspond to the order of elements in the 199 * {@code ports} array. 200 * @param socketFactory The socket factory to use to create the underlying 201 * connections. 202 */ 203 public FastestConnectServerSet(@NotNull final String[] addresses, 204 @NotNull final int[] ports, 205 @Nullable final SocketFactory socketFactory) 206 { 207 this(addresses, ports, socketFactory, null); 208 } 209 210 211 212 /** 213 * Creates a new fastest connect server set with the specified set of 214 * directory server addresses and port numbers. It will use the provided 215 * socket factory to create the underlying sockets. 216 * 217 * @param addresses The addresses of the directory servers to which 218 * the connections should be established. It must 219 * not be {@code null} or empty. 220 * @param ports The ports of the directory servers to which the 221 * connections should be established. It must not 222 * be {@code null}, and it must have the same 223 * number of elements as the {@code addresses} 224 * array. The order of elements in the 225 * {@code addresses} array must correspond to the 226 * order of elements in the {@code ports} array. 227 * @param socketFactory The socket factory to use to create the 228 * underlying connections. 229 * @param connectionOptions The set of connection options to use for the 230 * underlying connections. 231 */ 232 public FastestConnectServerSet(@NotNull final String[] addresses, 233 @NotNull final int[] ports, 234 @Nullable final SocketFactory socketFactory, 235 @Nullable final LDAPConnectionOptions connectionOptions) 236 { 237 this(addresses, ports, socketFactory, connectionOptions, null, null); 238 } 239 240 241 242 /** 243 * Creates a new fastest connect server set with the specified set of 244 * directory server addresses and port numbers. It will use the provided 245 * socket factory to create the underlying sockets. 246 * 247 * @param addresses The addresses of the directory servers to 248 * which the connections should be established. 249 * It must not be {@code null} or empty. 250 * @param ports The ports of the directory servers to which 251 * the connections should be established. It 252 * must not be {@code null}, and it must have 253 * the same number of elements as the 254 * {@code addresses} array. The order of 255 * elements in the {@code addresses} array must 256 * correspond to the order of elements in the 257 * {@code ports} array. 258 * @param socketFactory The socket factory to use to create the 259 * underlying connections. 260 * @param connectionOptions The set of connection options to use for the 261 * underlying connections. 262 * @param bindRequest The bind request that should be used to 263 * authenticate newly-established connections. 264 * It may be {@code null} if this server set 265 * should not perform any authentication. 266 * @param postConnectProcessor The post-connect processor that should be 267 * invoked on newly-established connections. It 268 * may be {@code null} if this server set should 269 * not perform any post-connect processing. 270 */ 271 public FastestConnectServerSet(@NotNull final String[] addresses, 272 @NotNull final int[] ports, 273 @Nullable final SocketFactory socketFactory, 274 @Nullable final LDAPConnectionOptions connectionOptions, 275 @Nullable final BindRequest bindRequest, 276 @Nullable final PostConnectProcessor postConnectProcessor) 277 { 278 Validator.ensureNotNull(addresses, ports); 279 Validator.ensureTrue(addresses.length > 0, 280 "RoundRobinServerSet.addresses must not be empty."); 281 Validator.ensureTrue(addresses.length == ports.length, 282 "RoundRobinServerSet addresses and ports arrays must be the same " + 283 "size."); 284 285 this.addresses = addresses; 286 this.ports = ports; 287 this.bindRequest = bindRequest; 288 this.postConnectProcessor = postConnectProcessor; 289 290 if (socketFactory == null) 291 { 292 this.socketFactory = SocketFactory.getDefault(); 293 } 294 else 295 { 296 this.socketFactory = socketFactory; 297 } 298 299 if (connectionOptions == null) 300 { 301 this.connectionOptions = new LDAPConnectionOptions(); 302 } 303 else 304 { 305 this.connectionOptions = connectionOptions; 306 } 307 } 308 309 310 311 /** 312 * Retrieves the addresses of the directory servers to which the connections 313 * should be established. 314 * 315 * @return The addresses of the directory servers to which the connections 316 * should be established. 317 */ 318 @NotNull() 319 public String[] getAddresses() 320 { 321 return addresses; 322 } 323 324 325 326 /** 327 * Retrieves the ports of the directory servers to which the connections 328 * should be established. 329 * 330 * @return The ports of the directory servers to which the connections should 331 * be established. 332 */ 333 @NotNull() 334 public int[] getPorts() 335 { 336 return ports; 337 } 338 339 340 341 /** 342 * Retrieves the socket factory that will be used to establish connections. 343 * 344 * @return The socket factory that will be used to establish connections. 345 */ 346 @NotNull() 347 public SocketFactory getSocketFactory() 348 { 349 return socketFactory; 350 } 351 352 353 354 /** 355 * Retrieves the set of connection options that will be used for underlying 356 * connections. 357 * 358 * @return The set of connection options that will be used for underlying 359 * connections. 360 */ 361 @NotNull() 362 public LDAPConnectionOptions getConnectionOptions() 363 { 364 return connectionOptions; 365 } 366 367 368 369 /** 370 * {@inheritDoc} 371 */ 372 @Override() 373 public boolean includesAuthentication() 374 { 375 return (bindRequest != null); 376 } 377 378 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override() 384 public boolean includesPostConnectProcessing() 385 { 386 return (postConnectProcessor != null); 387 } 388 389 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override() 395 @NotNull() 396 public LDAPConnection getConnection() 397 throws LDAPException 398 { 399 return getConnection(null); 400 } 401 402 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override() 408 @NotNull() 409 public LDAPConnection getConnection( 410 @Nullable final LDAPConnectionPoolHealthCheck healthCheck) 411 throws LDAPException 412 { 413 if (! connectionOptions.allowConcurrentSocketFactoryUse()) 414 { 415 throw new LDAPException(ResultCode.CONNECT_ERROR, 416 ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get()); 417 } 418 419 final ArrayBlockingQueue<Object> resultQueue = 420 new ArrayBlockingQueue<>(addresses.length, false); 421 final AtomicBoolean connectionSelected = new AtomicBoolean(false); 422 423 final FastestConnectThread[] connectThreads = 424 new FastestConnectThread[addresses.length]; 425 for (int i=0; i < connectThreads.length; i++) 426 { 427 connectThreads[i] = new FastestConnectThread(addresses[i], ports[i], 428 socketFactory, connectionOptions, bindRequest, postConnectProcessor, 429 healthCheck, resultQueue, connectionSelected); 430 } 431 432 for (final FastestConnectThread t : connectThreads) 433 { 434 t.start(); 435 } 436 437 try 438 { 439 final long effectiveConnectTimeout; 440 final long connectTimeout = 441 connectionOptions.getConnectTimeoutMillis(); 442 if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE)) 443 { 444 effectiveConnectTimeout = connectTimeout; 445 } 446 else 447 { 448 effectiveConnectTimeout = Integer.MAX_VALUE; 449 } 450 451 int connectFailures = 0; 452 final long stopWaitingTime = 453 System.currentTimeMillis() + effectiveConnectTimeout; 454 while (true) 455 { 456 final Object o; 457 final long waitTime = stopWaitingTime - System.currentTimeMillis(); 458 if (waitTime > 0L) 459 { 460 o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS); 461 } 462 else 463 { 464 o = resultQueue.poll(); 465 } 466 467 if (o == null) 468 { 469 throw new LDAPException(ResultCode.CONNECT_ERROR, 470 ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get( 471 effectiveConnectTimeout)); 472 } 473 else if (o instanceof LDAPConnection) 474 { 475 final LDAPConnection conn = (LDAPConnection) o; 476 associateConnectionWithThisServerSet(conn); 477 return conn; 478 } 479 else 480 { 481 connectFailures++; 482 if (connectFailures >= addresses.length) 483 { 484 throw new LDAPException(ResultCode.CONNECT_ERROR, 485 ERR_FASTEST_CONNECT_SET_ALL_FAILED.get()); 486 } 487 } 488 } 489 } 490 catch (final LDAPException le) 491 { 492 Debug.debugException(le); 493 throw le; 494 } 495 catch (final Exception e) 496 { 497 Debug.debugException(e); 498 499 if (e instanceof InterruptedException) 500 { 501 Thread.currentThread().interrupt(); 502 } 503 504 throw new LDAPException(ResultCode.CONNECT_ERROR, 505 ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get( 506 StaticUtils.getExceptionMessage(e)), 507 e); 508 } 509 } 510 511 512 513 /** 514 * {@inheritDoc} 515 */ 516 @Override() 517 public void toString(@NotNull final StringBuilder buffer) 518 { 519 buffer.append("FastestConnectServerSet(servers={"); 520 521 for (int i=0; i < addresses.length; i++) 522 { 523 if (i > 0) 524 { 525 buffer.append(", "); 526 } 527 528 buffer.append(addresses[i]); 529 buffer.append(':'); 530 buffer.append(ports[i]); 531 } 532 533 buffer.append("}, includesAuthentication="); 534 buffer.append(bindRequest != null); 535 buffer.append(", includesPostConnectProcessing="); 536 buffer.append(postConnectProcessor != null); 537 buffer.append(')'); 538 } 539}