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