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