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