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