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 java.util.List;
026 import java.util.concurrent.atomic.AtomicBoolean;
027 import javax.net.SocketFactory;
028
029 import com.unboundid.util.NotMutable;
030 import com.unboundid.util.ThreadSafety;
031 import com.unboundid.util.ThreadSafetyLevel;
032
033 import static com.unboundid.util.Debug.*;
034 import static com.unboundid.util.Validator.*;
035
036
037
038 /**
039 * This class provides a server set implementation that will attempt to
040 * establish connections to servers in the order they are provided. If the
041 * first server is unavailable, then it will attempt to connect to the second,
042 * then to the third, etc. Note that this implementation also makes it possible
043 * to use failover between distinct server sets, which means that it will first
044 * attempt to obtain a connection from the first server set and if all attempts
045 * fail, it will proceed to the second set, and so on. This can provide a
046 * significant degree of flexibility in complex environments (e.g., first use a
047 * round robin server set containing servers in the local data center, but if
048 * none of those are available then fail over to a server set with servers in a
049 * remote data center).
050 * <BR><BR>
051 * <H2>Example</H2>
052 * The following example demonstrates the process for creating a failover server
053 * set with information about individual servers. It will first try to connect
054 * to ds1.example.com:389, but if that fails then it will try connecting to
055 * ds2.example.com:389:
056 * <PRE>
057 * String[] addresses =
058 * {
059 * "ds1.example.com",
060 * "ds2.example.com"
061 * };
062 * int[] ports =
063 * {
064 * 389,
065 * 389
066 * };
067 * FailoverServerSet failoverSet = new FailoverServerSet(addresses, ports);
068 * </PRE>
069 * This second example demonstrates the process for creating a failover server
070 * set which actually fails over between two different data centers (east and
071 * west), with each data center containing two servers that will be accessed in
072 * a round-robin manner. It will first try to connect to one of the servers in
073 * the east data center, and if that attempt fails then it will try to connect
074 * to the other server in the east data center. If both of them fail, then it
075 * will try to connect to one of the servers in the west data center, and
076 * finally as a last resort the other server in the west data center:
077 * <PRE>
078 * String[] eastAddresses =
079 * {
080 * "ds-east-1.example.com",
081 * "ds-east-2.example.com",
082 * };
083 * int[] eastPorts =
084 * {
085 * 389,
086 * 389
087 * }
088 * RoundRobinServerSet eastSet =
089 * new RoundRobinServerSet(eastAddresses, eastPorts);
090 *
091 * String[] westAddresses =
092 * {
093 * "ds-west-1.example.com",
094 * "ds-west-2.example.com",
095 * };
096 * int[] westPorts =
097 * {
098 * 389,
099 * 389
100 * }
101 * RoundRobinServerSet westSet =
102 * new RoundRobinServerSet(westAddresses, westPorts);
103 *
104 * FailoverServerSet failoverSet = new FailoverServerSet(eastSet, westSet);
105 * </PRE>
106 */
107 @NotMutable()
108 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
109 public final class FailoverServerSet
110 extends ServerSet
111 {
112 // Indicates whether to re-order the server set list if failover occurs.
113 private final AtomicBoolean reOrderOnFailover;
114
115 // The server sets for which we will allow failover.
116 private final ServerSet[] serverSets;
117
118
119
120 /**
121 * Creates a new failover server set with the specified set of directory
122 * server addresses and port numbers. It will use the default socket factory
123 * provided by the JVM to create the underlying sockets.
124 *
125 * @param addresses The addresses of the directory servers to which the
126 * connections should be established. It must not be
127 * {@code null} or empty.
128 * @param ports The ports of the directory servers to which the
129 * connections should be established. It must not be
130 * {@code null}, and it must have the same number of
131 * elements as the {@code addresses} array. The order of
132 * elements in the {@code addresses} array must correspond
133 * to the order of elements in the {@code ports} array.
134 */
135 public FailoverServerSet(final String[] addresses, final int[] ports)
136 {
137 this(addresses, ports, null, null);
138 }
139
140
141
142 /**
143 * Creates a new failover server set with the specified set of directory
144 * server addresses and port numbers. It will use the default socket factory
145 * provided by the JVM to create the underlying sockets.
146 *
147 * @param addresses The addresses of the directory servers to which
148 * the connections should be established. It must
149 * not be {@code null} or empty.
150 * @param ports The ports of the directory servers to which the
151 * connections should be established. It must not
152 * be {@code null}, and it must have the same
153 * number of elements as the {@code addresses}
154 * array. The order of elements in the
155 * {@code addresses} array must correspond to the
156 * order of elements in the {@code ports} array.
157 * @param connectionOptions The set of connection options to use for the
158 * underlying connections.
159 */
160 public FailoverServerSet(final String[] addresses, final int[] ports,
161 final LDAPConnectionOptions connectionOptions)
162 {
163 this(addresses, ports, null, connectionOptions);
164 }
165
166
167
168 /**
169 * Creates a new failover server set with the specified set of directory
170 * server addresses and port numbers. It will use the provided socket factory
171 * to create the underlying sockets.
172 *
173 * @param addresses The addresses of the directory servers to which the
174 * connections should be established. It must not be
175 * {@code null} or empty.
176 * @param ports The ports of the directory servers to which the
177 * connections should be established. It must not be
178 * {@code null}, and it must have the same number of
179 * elements as the {@code addresses} array. The order
180 * of elements in the {@code addresses} array must
181 * correspond to the order of elements in the
182 * {@code ports} array.
183 * @param socketFactory The socket factory to use to create the underlying
184 * connections.
185 */
186 public FailoverServerSet(final String[] addresses, final int[] ports,
187 final SocketFactory socketFactory)
188 {
189 this(addresses, ports, socketFactory, null);
190 }
191
192
193
194 /**
195 * Creates a new failover server set with the specified set of directory
196 * server addresses and port numbers. It will use the provided socket factory
197 * to create the underlying sockets.
198 *
199 * @param addresses The addresses of the directory servers to which
200 * the connections should be established. It must
201 * not be {@code null} or empty.
202 * @param ports The ports of the directory servers to which the
203 * connections should be established. It must not
204 * be {@code null}, and it must have the same
205 * number of elements as the {@code addresses}
206 * array. The order of elements in the
207 * {@code addresses} array must correspond to the
208 * order of elements in the {@code ports} array.
209 * @param socketFactory The socket factory to use to create the
210 * underlying connections.
211 * @param connectionOptions The set of connection options to use for the
212 * underlying connections.
213 */
214 public FailoverServerSet(final String[] addresses, final int[] ports,
215 final SocketFactory socketFactory,
216 final LDAPConnectionOptions connectionOptions)
217 {
218 ensureNotNull(addresses, ports);
219 ensureTrue(addresses.length > 0,
220 "FailoverServerSet.addresses must not be empty.");
221 ensureTrue(addresses.length == ports.length,
222 "FailoverServerSet addresses and ports arrays must be the same size.");
223
224 reOrderOnFailover = new AtomicBoolean(false);
225
226 final SocketFactory sf;
227 if (socketFactory == null)
228 {
229 sf = SocketFactory.getDefault();
230 }
231 else
232 {
233 sf = socketFactory;
234 }
235
236 final LDAPConnectionOptions co;
237 if (connectionOptions == null)
238 {
239 co = new LDAPConnectionOptions();
240 }
241 else
242 {
243 co = connectionOptions;
244 }
245
246
247 serverSets = new ServerSet[addresses.length];
248 for (int i=0; i < serverSets.length; i++)
249 {
250 serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co);
251 }
252 }
253
254
255
256 /**
257 * Creates a new failover server set that will fail over between the provided
258 * server sets.
259 *
260 * @param serverSets The server sets between which failover should occur.
261 * It must not be {@code null} or empty.
262 */
263 public FailoverServerSet(final ServerSet... serverSets)
264 {
265 ensureNotNull(serverSets);
266 ensureFalse(serverSets.length == 0,
267 "FailoverServerSet.serverSets must not be empty.");
268
269 this.serverSets = serverSets;
270
271 reOrderOnFailover = new AtomicBoolean(false);
272 }
273
274
275
276 /**
277 * Creates a new failover server set that will fail over between the provided
278 * server sets.
279 *
280 * @param serverSets The server sets between which failover should occur.
281 * It must not be {@code null} or empty.
282 */
283 public FailoverServerSet(final List<ServerSet> serverSets)
284 {
285 ensureNotNull(serverSets);
286 ensureFalse(serverSets.isEmpty(),
287 "FailoverServerSet.serverSets must not be empty.");
288
289 this.serverSets = new ServerSet[serverSets.size()];
290 serverSets.toArray(this.serverSets);
291
292 reOrderOnFailover = new AtomicBoolean(false);
293 }
294
295
296
297 /**
298 * Retrieves the server sets over which failover will occur. If this failover
299 * server set was created from individual servers rather than server sets,
300 * then the elements contained in the returned array will be
301 * {@code SingleServerSet} instances.
302 *
303 * @return The server sets over which failover will occur.
304 */
305 public ServerSet[] getServerSets()
306 {
307 return serverSets;
308 }
309
310
311
312 /**
313 * Indicates whether the list of servers or server sets used by this failover
314 * server set should be re-ordered in the event that a failure is encountered
315 * while attempting to establish a connection. If {@code true}, then any
316 * failed attempt to establish a connection to a server set at the beginning
317 * of the list may cause that server/set to be moved to the end of the list so
318 * that it will be the last one tried on the next attempt.
319 *
320 * @return {@code true} if the order of elements in the associated list of
321 * servers or server sets should be updated if a failure occurs while
322 * attempting to establish a connection, or {@code false} if the
323 * original order should be preserved.
324 */
325 public boolean reOrderOnFailover()
326 {
327 return reOrderOnFailover.get();
328 }
329
330
331
332 /**
333 * Specifies whether the list of servers or server sets used by this failover
334 * server set should be re-ordered in the event that a failure is encountered
335 * while attempting to establish a connection. By default, the original
336 * order will be preserved, but if this method is called with a value of
337 * {@code true}, then a failed attempt to establish a connection to the server
338 * or server set at the beginning of the list may cause that server to be
339 * moved to the end of the list so that it will be the last server/set tried
340 * on the next attempt.
341 *
342 * @param reOrderOnFailover Indicates whether the list of servers or server
343 * sets should be re-ordered in the event that a
344 * failure is encountered while attempting to
345 * establish a connection.
346 */
347 public void setReOrderOnFailover(final boolean reOrderOnFailover)
348 {
349 this.reOrderOnFailover.set(reOrderOnFailover);
350 }
351
352
353
354 /**
355 * {@inheritDoc}
356 */
357 @Override()
358 public LDAPConnection getConnection()
359 throws LDAPException
360 {
361 return getConnection(null);
362 }
363
364
365
366 /**
367 * {@inheritDoc}
368 */
369 @Override()
370 public LDAPConnection getConnection(
371 final LDAPConnectionPoolHealthCheck healthCheck)
372 throws LDAPException
373 {
374 if (reOrderOnFailover.get() && (serverSets.length > 1))
375 {
376 synchronized (this)
377 {
378 // First, try to get a connection using the first set in the list. If
379 // this succeeds, then we don't need to go any further.
380 try
381 {
382 return serverSets[0].getConnection(healthCheck);
383 }
384 catch (final LDAPException le)
385 {
386 debugException(le);
387 }
388
389 // If we've gotten here, then we will need to re-order the list unless
390 // all other attempts fail.
391 int successfulPos = -1;
392 LDAPConnection conn = null;
393 LDAPException lastException = null;
394 for (int i=1; i < serverSets.length; i++)
395 {
396 try
397 {
398 conn = serverSets[i].getConnection(healthCheck);
399 successfulPos = i;
400 break;
401 }
402 catch (final LDAPException le)
403 {
404 debugException(le);
405 lastException = le;
406 }
407 }
408
409 if (successfulPos > 0)
410 {
411 int pos = 0;
412 final ServerSet[] setCopy = new ServerSet[serverSets.length];
413 for (int i=successfulPos; i < serverSets.length; i++)
414 {
415 setCopy[pos++] = serverSets[i];
416 }
417
418 for (int i=0; i < successfulPos; i++)
419 {
420 setCopy[pos++] = serverSets[i];
421 }
422
423 System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length);
424 return conn;
425 }
426 else
427 {
428 throw lastException;
429 }
430 }
431 }
432 else
433 {
434 LDAPException lastException = null;
435
436 for (final ServerSet s : serverSets)
437 {
438 try
439 {
440 return s.getConnection(healthCheck);
441 }
442 catch (LDAPException le)
443 {
444 debugException(le);
445 lastException = le;
446 }
447 }
448
449 throw lastException;
450 }
451 }
452
453
454
455 /**
456 * {@inheritDoc}
457 */
458 @Override()
459 public void toString(final StringBuilder buffer)
460 {
461 buffer.append("FailoverServerSet(serverSets={");
462
463 for (int i=0; i < serverSets.length; i++)
464 {
465 if (i > 0)
466 {
467 buffer.append(", ");
468 }
469
470 serverSets[i].toString(buffer);
471 }
472
473 buffer.append("})");
474 }
475 }