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
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 }