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 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import javax.net.SocketFactory;
026
027 import com.unboundid.util.NotMutable;
028 import com.unboundid.util.ThreadSafety;
029 import com.unboundid.util.ThreadSafetyLevel;
030
031 import static com.unboundid.util.Debug.*;
032 import 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)
085 public 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 }