001/* 002 * Copyright 2008-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-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 javax.net.SocketFactory; 026 027import com.unboundid.util.NotMutable; 028import com.unboundid.util.ThreadSafety; 029import com.unboundid.util.ThreadSafetyLevel; 030 031import static com.unboundid.util.Debug.*; 032import static com.unboundid.util.Validator.*; 033 034 035 036/** 037 * This class provides a server set implementation that will use a round-robin 038 * algorithm to select the server to which the connection should be established. 039 * Any number of servers may be included in this server set, and each request 040 * will attempt to retrieve a connection to the next server in the list, 041 * circling back to the beginning of the list as necessary. If a server is 042 * unavailable when an attempt is made to establish a connection to it, then 043 * the connection will be established to the next available server in the set. 044 * <BR><BR> 045 * <H2>Example</H2> 046 * The following example demonstrates the process for creating a round-robin 047 * server set that may be used to establish connections to either of two 048 * servers. When using the server set to attempt to create a connection, it 049 * will first try one of the servers, but will fail over to the other if the 050 * first one attempted is not available: 051 * <PRE> 052 * // Create arrays with the addresses and ports of the directory server 053 * // instances. 054 * String[] addresses = 055 * { 056 * server1Address, 057 * server2Address 058 * }; 059 * int[] ports = 060 * { 061 * server1Port, 062 * server2Port 063 * }; 064 * 065 * // Create the server set using the address and port arrays. 066 * RoundRobinServerSet roundRobinSet = 067 * new RoundRobinServerSet(addresses, ports); 068 * 069 * // Verify that we can establish a single connection using the server set. 070 * LDAPConnection connection = roundRobinSet.getConnection(); 071 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 072 * connection.close(); 073 * 074 * // Verify that we can establish a connection pool using the server set. 075 * SimpleBindRequest bindRequest = 076 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 077 * LDAPConnectionPool pool = 078 * new LDAPConnectionPool(roundRobinSet, bindRequest, 10); 079 * RootDSE rootDSEFromPool = pool.getRootDSE(); 080 * pool.close(); 081 * </PRE> 082 */ 083@NotMutable() 084@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 085public final class RoundRobinServerSet 086 extends ServerSet 087{ 088 // The bind request to use to authenticate connections created by this 089 // server set. 090 private final BindRequest bindRequest; 091 092 // The port numbers of the target servers. 093 private final int[] ports; 094 095 // The set of connection options to use for new connections. 096 private final LDAPConnectionOptions connectionOptions; 097 098 // The post-connect processor to invoke against connections created by this 099 // server set. 100 private final PostConnectProcessor postConnectProcessor; 101 102 // The socket factory to use to establish connections. 103 private final SocketFactory socketFactory; 104 105 // The addresses of the target servers. 106 private final String[] addresses; 107 108 // The slot to use for the server to be selected for the next connection 109 // attempt. 110 private int nextSlot; 111 112 113 114 /** 115 * Creates a new round robin server set with the specified set of directory 116 * server addresses and port numbers. It will use the default socket factory 117 * provided by the JVM to create the underlying sockets. 118 * 119 * @param addresses The addresses of the directory servers to which the 120 * connections should be established. It must not be 121 * {@code null} or empty. 122 * @param ports The ports of the directory servers to which the 123 * connections should be established. It must not be 124 * {@code null}, and it must have the same number of 125 * elements as the {@code addresses} array. The order of 126 * elements in the {@code addresses} array must correspond 127 * to the order of elements in the {@code ports} array. 128 */ 129 public RoundRobinServerSet(final String[] addresses, final int[] ports) 130 { 131 this(addresses, ports, null, null); 132 } 133 134 135 136 /** 137 * Creates a new round robin server set with the specified set of directory 138 * server addresses and port numbers. It will use the default socket factory 139 * provided by the JVM to create the underlying sockets. 140 * 141 * @param addresses The addresses of the directory servers to which 142 * the connections should be established. It must 143 * not be {@code null} or empty. 144 * @param ports The ports of the directory servers to which the 145 * connections should be established. It must not 146 * be {@code null}, and it must have the same 147 * number of elements as the {@code addresses} 148 * array. The order of elements in the 149 * {@code addresses} array must correspond to the 150 * order of elements in the {@code ports} array. 151 * @param connectionOptions The set of connection options to use for the 152 * underlying connections. 153 */ 154 public RoundRobinServerSet(final String[] addresses, final int[] ports, 155 final LDAPConnectionOptions connectionOptions) 156 { 157 this(addresses, ports, null, connectionOptions); 158 } 159 160 161 162 /** 163 * Creates a new round robin server set with the specified set of directory 164 * server addresses and port numbers. It will use the provided socket factory 165 * to create the underlying sockets. 166 * 167 * @param addresses The addresses of the directory servers to which the 168 * connections should be established. It must not be 169 * {@code null} or empty. 170 * @param ports The ports of the directory servers to which the 171 * connections should be established. It must not be 172 * {@code null}, and it must have the same number of 173 * elements as the {@code addresses} array. The order 174 * of elements in the {@code addresses} array must 175 * correspond to the order of elements in the 176 * {@code ports} array. 177 * @param socketFactory The socket factory to use to create the underlying 178 * connections. 179 */ 180 public RoundRobinServerSet(final String[] addresses, final int[] ports, 181 final SocketFactory socketFactory) 182 { 183 this(addresses, ports, socketFactory, null); 184 } 185 186 187 188 /** 189 * Creates a new round robin server set with the specified set of directory 190 * server addresses and port numbers. It will use the provided socket factory 191 * to create the underlying sockets. 192 * 193 * @param addresses The addresses of the directory servers to which 194 * the connections should be established. It must 195 * not be {@code null} or empty. 196 * @param ports The ports of the directory servers to which the 197 * connections should be established. It must not 198 * be {@code null}, and it must have the same 199 * number of elements as the {@code addresses} 200 * array. The order of elements in the 201 * {@code addresses} array must correspond to the 202 * order of elements in the {@code ports} array. 203 * @param socketFactory The socket factory to use to create the 204 * underlying connections. 205 * @param connectionOptions The set of connection options to use for the 206 * underlying connections. 207 */ 208 public RoundRobinServerSet(final String[] addresses, final int[] ports, 209 final SocketFactory socketFactory, 210 final LDAPConnectionOptions connectionOptions) 211 { 212 this(addresses, ports, socketFactory, connectionOptions, null, null); 213 } 214 215 216 217 /** 218 * Creates a new round robin server set with the specified set of directory 219 * server addresses and port numbers. It will use the provided socket factory 220 * to create the underlying sockets. 221 * 222 * @param addresses The addresses of the directory servers to 223 * which the connections should be established. 224 * It must not be {@code null} or empty. 225 * @param ports The ports of the directory servers to which 226 * the connections should be established. It 227 * must not be {@code null}, and it must have 228 * the same number of elements as the 229 * {@code addresses} array. The order of 230 * elements in the {@code addresses} array must 231 * correspond to the order of elements in the 232 * {@code ports} array. 233 * @param socketFactory The socket factory to use to create the 234 * underlying connections. 235 * @param connectionOptions The set of connection options to use for the 236 * underlying connections. 237 * @param bindRequest The bind request that should be used to 238 * authenticate newly-established connections. 239 * It may be {@code null} if this server set 240 * should not perform any authentication. 241 * @param postConnectProcessor The post-connect processor that should be 242 * invoked on newly-established connections. It 243 * may be {@code null} if this server set should 244 * not perform any post-connect processing. 245 */ 246 public RoundRobinServerSet(final String[] addresses, final int[] ports, 247 final SocketFactory socketFactory, 248 final LDAPConnectionOptions connectionOptions, 249 final BindRequest bindRequest, 250 final PostConnectProcessor postConnectProcessor) 251 { 252 ensureNotNull(addresses, ports); 253 ensureTrue(addresses.length > 0, 254 "RoundRobinServerSet.addresses must not be empty."); 255 ensureTrue(addresses.length == ports.length, 256 "RoundRobinServerSet addresses and ports arrays must be the " + 257 "same size."); 258 259 this.addresses = addresses; 260 this.ports = ports; 261 this.bindRequest = bindRequest; 262 this.postConnectProcessor = postConnectProcessor; 263 264 if (socketFactory == null) 265 { 266 this.socketFactory = SocketFactory.getDefault(); 267 } 268 else 269 { 270 this.socketFactory = socketFactory; 271 } 272 273 if (connectionOptions == null) 274 { 275 this.connectionOptions = new LDAPConnectionOptions(); 276 } 277 else 278 { 279 this.connectionOptions = connectionOptions; 280 } 281 282 nextSlot = 0; 283 } 284 285 286 287 /** 288 * Retrieves the addresses of the directory servers to which the connections 289 * should be established. 290 * 291 * @return The addresses of the directory servers to which the connections 292 * should be established. 293 */ 294 public String[] getAddresses() 295 { 296 return addresses; 297 } 298 299 300 301 /** 302 * Retrieves the ports of the directory servers to which the connections 303 * should be established. 304 * 305 * @return The ports of the directory servers to which the connections should 306 * be established. 307 */ 308 public int[] getPorts() 309 { 310 return ports; 311 } 312 313 314 315 /** 316 * Retrieves the socket factory that will be used to establish connections. 317 * 318 * @return The socket factory that will be used to establish connections. 319 */ 320 public SocketFactory getSocketFactory() 321 { 322 return socketFactory; 323 } 324 325 326 327 /** 328 * Retrieves the set of connection options that will be used for underlying 329 * connections. 330 * 331 * @return The set of connection options that will be used for underlying 332 * connections. 333 */ 334 public LDAPConnectionOptions getConnectionOptions() 335 { 336 return connectionOptions; 337 } 338 339 340 341 /** 342 * {@inheritDoc} 343 */ 344 @Override() 345 public boolean includesAuthentication() 346 { 347 return (bindRequest != null); 348 } 349 350 351 352 /** 353 * {@inheritDoc} 354 */ 355 @Override() 356 public boolean includesPostConnectProcessing() 357 { 358 return (postConnectProcessor != null); 359 } 360 361 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override() 367 public LDAPConnection getConnection() 368 throws LDAPException 369 { 370 return getConnection(null); 371 } 372 373 374 375 /** 376 * {@inheritDoc} 377 */ 378 @Override() 379 public synchronized LDAPConnection getConnection( 380 final LDAPConnectionPoolHealthCheck healthCheck) 381 throws LDAPException 382 { 383 final int initialSlotNumber = nextSlot++; 384 385 if (nextSlot >= addresses.length) 386 { 387 nextSlot = 0; 388 } 389 390 try 391 { 392 final LDAPConnection c = new LDAPConnection(socketFactory, 393 connectionOptions, addresses[initialSlotNumber], 394 ports[initialSlotNumber]); 395 doBindPostConnectAndHealthCheckProcessing(c, bindRequest, 396 postConnectProcessor, healthCheck); 397 return c; 398 } 399 catch (final LDAPException le) 400 { 401 debugException(le); 402 LDAPException lastException = le; 403 404 while (nextSlot != initialSlotNumber) 405 { 406 final int slotNumber = nextSlot++; 407 if (nextSlot >= addresses.length) 408 { 409 nextSlot = 0; 410 } 411 412 try 413 { 414 final LDAPConnection c = new LDAPConnection(socketFactory, 415 connectionOptions, addresses[slotNumber], ports[slotNumber]); 416 doBindPostConnectAndHealthCheckProcessing(c, bindRequest, 417 postConnectProcessor, healthCheck); 418 return c; 419 } 420 catch (final LDAPException le2) 421 { 422 debugException(le2); 423 lastException = le2; 424 } 425 } 426 427 // If we've gotten here, then we've failed to connect to any of the 428 // servers, so propagate the last exception to the caller. 429 throw lastException; 430 } 431 } 432 433 434 435 /** 436 * {@inheritDoc} 437 */ 438 @Override() 439 public void toString(final StringBuilder buffer) 440 { 441 buffer.append("RoundRobinServerSet(servers={"); 442 443 for (int i=0; i < addresses.length; i++) 444 { 445 if (i > 0) 446 { 447 buffer.append(", "); 448 } 449 450 buffer.append(addresses[i]); 451 buffer.append(':'); 452 buffer.append(ports[i]); 453 } 454 455 buffer.append("}, includesAuthentication="); 456 buffer.append(bindRequest != null); 457 buffer.append(", includesPostConnectProcessing="); 458 buffer.append(postConnectProcessor != null); 459 buffer.append(')'); 460 } 461}