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