001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 */ 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 port numbers of the target servers. 089 private final int[] ports; 090 091 // The set of connection options to use for new connections. 092 private final LDAPConnectionOptions connectionOptions; 093 094 // The socket factory to use to establish connections. 095 private final SocketFactory socketFactory; 096 097 // The addresses of the target servers. 098 private final String[] addresses; 099 100 // The slot to use for the server to be selected for the next connection 101 // attempt. 102 private int nextSlot; 103 104 105 106 /** 107 * Creates a new round robin server set with the specified set of directory 108 * server addresses and port numbers. It will use the default socket factory 109 * provided by the JVM to create the underlying sockets. 110 * 111 * @param addresses The addresses of the directory servers to which the 112 * connections should be established. It must not be 113 * {@code null} or empty. 114 * @param ports The ports of the directory servers to which the 115 * connections should be established. It must not be 116 * {@code null}, and it must have the same number of 117 * elements as the {@code addresses} array. The order of 118 * elements in the {@code addresses} array must correspond 119 * to the order of elements in the {@code ports} array. 120 */ 121 public RoundRobinServerSet(final String[] addresses, final int[] ports) 122 { 123 this(addresses, ports, null, null); 124 } 125 126 127 128 /** 129 * Creates a new round robin server set with the specified set of directory 130 * server addresses and port numbers. It will use the default socket factory 131 * provided by the JVM to create the underlying sockets. 132 * 133 * @param addresses The addresses of the directory servers to which 134 * the connections should be established. It must 135 * not be {@code null} or empty. 136 * @param ports The ports of the directory servers to which the 137 * connections should be established. It must not 138 * be {@code null}, and it must have the same 139 * number of elements as the {@code addresses} 140 * array. The order of elements in the 141 * {@code addresses} array must correspond to the 142 * order of elements in the {@code ports} array. 143 * @param connectionOptions The set of connection options to use for the 144 * underlying connections. 145 */ 146 public RoundRobinServerSet(final String[] addresses, final int[] ports, 147 final LDAPConnectionOptions connectionOptions) 148 { 149 this(addresses, ports, null, connectionOptions); 150 } 151 152 153 154 /** 155 * Creates a new round robin server set with the specified set of directory 156 * server addresses and port numbers. It will use the provided socket factory 157 * to create the underlying sockets. 158 * 159 * @param addresses The addresses of the directory servers to which the 160 * connections should be established. It must not be 161 * {@code null} or empty. 162 * @param ports The ports of the directory servers to which the 163 * connections should be established. It must not be 164 * {@code null}, and it must have the same number of 165 * elements as the {@code addresses} array. The order 166 * of elements in the {@code addresses} array must 167 * correspond to the order of elements in the 168 * {@code ports} array. 169 * @param socketFactory The socket factory to use to create the underlying 170 * connections. 171 */ 172 public RoundRobinServerSet(final String[] addresses, final int[] ports, 173 final SocketFactory socketFactory) 174 { 175 this(addresses, ports, socketFactory, null); 176 } 177 178 179 180 /** 181 * Creates a new round robin server set with the specified set of directory 182 * server addresses and port numbers. It will use the provided socket factory 183 * to create the underlying sockets. 184 * 185 * @param addresses The addresses of the directory servers to which 186 * the connections should be established. It must 187 * not be {@code null} or empty. 188 * @param ports The ports of the directory servers to which the 189 * connections should be established. It must not 190 * be {@code null}, and it must have the same 191 * number of elements as the {@code addresses} 192 * array. The order of elements in the 193 * {@code addresses} array must correspond to the 194 * order of elements in the {@code ports} array. 195 * @param socketFactory The socket factory to use to create the 196 * underlying connections. 197 * @param connectionOptions The set of connection options to use for the 198 * underlying connections. 199 */ 200 public RoundRobinServerSet(final String[] addresses, final int[] ports, 201 final SocketFactory socketFactory, 202 final LDAPConnectionOptions connectionOptions) 203 { 204 ensureNotNull(addresses, ports); 205 ensureTrue(addresses.length > 0, 206 "RoundRobinServerSet.addresses must not be empty."); 207 ensureTrue(addresses.length == ports.length, 208 "RoundRobinServerSet addresses and ports arrays must be the " + 209 "same size."); 210 211 this.addresses = addresses; 212 this.ports = ports; 213 214 if (socketFactory == null) 215 { 216 this.socketFactory = SocketFactory.getDefault(); 217 } 218 else 219 { 220 this.socketFactory = socketFactory; 221 } 222 223 if (connectionOptions == null) 224 { 225 this.connectionOptions = new LDAPConnectionOptions(); 226 } 227 else 228 { 229 this.connectionOptions = connectionOptions; 230 } 231 232 nextSlot = 0; 233 } 234 235 236 237 /** 238 * Retrieves the addresses of the directory servers to which the connections 239 * should be established. 240 * 241 * @return The addresses of the directory servers to which the connections 242 * should be established. 243 */ 244 public String[] getAddresses() 245 { 246 return addresses; 247 } 248 249 250 251 /** 252 * Retrieves the ports of the directory servers to which the connections 253 * should be established. 254 * 255 * @return The ports of the directory servers to which the connections should 256 * be established. 257 */ 258 public int[] getPorts() 259 { 260 return ports; 261 } 262 263 264 265 /** 266 * Retrieves the socket factory that will be used to establish connections. 267 * 268 * @return The socket factory that will be used to establish connections. 269 */ 270 public SocketFactory getSocketFactory() 271 { 272 return socketFactory; 273 } 274 275 276 277 /** 278 * Retrieves the set of connection options that will be used for underlying 279 * connections. 280 * 281 * @return The set of connection options that will be used for underlying 282 * connections. 283 */ 284 public LDAPConnectionOptions getConnectionOptions() 285 { 286 return connectionOptions; 287 } 288 289 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override() 295 public LDAPConnection getConnection() 296 throws LDAPException 297 { 298 return getConnection(null); 299 } 300 301 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override() 307 public synchronized LDAPConnection getConnection( 308 final LDAPConnectionPoolHealthCheck healthCheck) 309 throws LDAPException 310 { 311 final int initialSlotNumber = nextSlot++; 312 313 if (nextSlot >= addresses.length) 314 { 315 nextSlot = 0; 316 } 317 318 try 319 { 320 final LDAPConnection c = new LDAPConnection(socketFactory, 321 connectionOptions, addresses[initialSlotNumber], 322 ports[initialSlotNumber]); 323 if (healthCheck != null) 324 { 325 try 326 { 327 healthCheck.ensureNewConnectionValid(c); 328 } 329 catch (LDAPException le) 330 { 331 c.close(); 332 throw le; 333 } 334 } 335 return c; 336 } 337 catch (LDAPException le) 338 { 339 debugException(le); 340 LDAPException lastException = le; 341 342 while (nextSlot != initialSlotNumber) 343 { 344 final int slotNumber = nextSlot++; 345 if (nextSlot >= addresses.length) 346 { 347 nextSlot = 0; 348 } 349 350 try 351 { 352 final LDAPConnection c = new LDAPConnection(socketFactory, 353 connectionOptions, addresses[slotNumber], ports[slotNumber]); 354 if (healthCheck != null) 355 { 356 try 357 { 358 healthCheck.ensureNewConnectionValid(c); 359 } 360 catch (LDAPException le2) 361 { 362 c.close(); 363 throw le2; 364 } 365 } 366 return c; 367 } 368 catch (LDAPException le2) 369 { 370 debugException(le2); 371 lastException = le2; 372 } 373 } 374 375 // If we've gotten here, then we've failed to connect to any of the 376 // servers, so propagate the last exception to the caller. 377 throw lastException; 378 } 379 } 380 381 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override() 387 public void toString(final StringBuilder buffer) 388 { 389 buffer.append("RoundRobinServerSet(servers={"); 390 391 for (int i=0; i < addresses.length; i++) 392 { 393 if (i > 0) 394 { 395 buffer.append(", "); 396 } 397 398 buffer.append(addresses[i]); 399 buffer.append(':'); 400 buffer.append(ports[i]); 401 } 402 403 buffer.append("})"); 404 } 405}