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 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 * // Create arrays with the addresses and ports of the directory server
058 * // instances.
059 * String[] addresses =
060 * {
061 * server1Address,
062 * server2Address
063 * };
064 * int[] ports =
065 * {
066 * server1Port,
067 * server2Port
068 * };
069 *
070 * // Create the server set using the address and port arrays.
071 * FailoverServerSet failoverSet = new FailoverServerSet(addresses, ports);
072 *
073 * // Verify that we can establish a single connection using the server set.
074 * LDAPConnection connection = failoverSet.getConnection();
075 * RootDSE rootDSEFromConnection = connection.getRootDSE();
076 * connection.close();
077 *
078 * // Verify that we can establish a connection pool using the server set.
079 * SimpleBindRequest bindRequest =
080 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
081 * LDAPConnectionPool pool =
082 * new LDAPConnectionPool(failoverSet, bindRequest, 10);
083 * RootDSE rootDSEFromPool = pool.getRootDSE();
084 * pool.close();
085 * </PRE>
086 * This second example demonstrates the process for creating a failover server
087 * set which actually fails over between two different data centers (east and
088 * west), with each data center containing two servers that will be accessed in
089 * a round-robin manner. It will first try to connect to one of the servers in
090 * the east data center, and if that attempt fails then it will try to connect
091 * to the other server in the east data center. If both of them fail, then it
092 * will try to connect to one of the servers in the west data center, and
093 * finally as a last resort the other server in the west data center:
094 * <PRE>
095 * // Create a round-robin server set for the servers in the "east" data
096 * // center.
097 * String[] eastAddresses =
098 * {
099 * eastServer1Address,
100 * eastServer2Address
101 * };
102 * int[] eastPorts =
103 * {
104 * eastServer1Port,
105 * eastServer2Port
106 * };
107 * RoundRobinServerSet eastSet =
108 * new RoundRobinServerSet(eastAddresses, eastPorts);
109 *
110 * // Create a round-robin server set for the servers in the "west" data
111 * // center.
112 * String[] westAddresses =
113 * {
114 * westServer1Address,
115 * westServer2Address
116 * };
117 * int[] westPorts =
118 * {
119 * westServer1Port,
120 * westServer2Port
121 * };
122 * RoundRobinServerSet westSet =
123 * new RoundRobinServerSet(westAddresses, westPorts);
124 *
125 * // Create the failover server set across the east and west round-robin sets.
126 * FailoverServerSet failoverSet = new FailoverServerSet(eastSet, westSet);
127 *
128 * // Verify that we can establish a single connection using the server set.
129 * LDAPConnection connection = failoverSet.getConnection();
130 * RootDSE rootDSEFromConnection = connection.getRootDSE();
131 * connection.close();
132 *
133 * // Verify that we can establish a connection pool using the server set.
134 * SimpleBindRequest bindRequest =
135 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
136 * LDAPConnectionPool pool =
137 * new LDAPConnectionPool(failoverSet, bindRequest, 10);
138 * RootDSE rootDSEFromPool = pool.getRootDSE();
139 * pool.close();
140 * </PRE>
141 */
142 @NotMutable()
143 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
144 public final class FailoverServerSet
145 extends ServerSet
146 {
147 // Indicates whether to re-order the server set list if failover occurs.
148 private final AtomicBoolean reOrderOnFailover;
149
150 // The maximum connection age that should be set for connections established
151 // using anything but the first server set.
152 private volatile Long maxFailoverConnectionAge;
153
154 // The server sets for which we will allow failover.
155 private final ServerSet[] serverSets;
156
157
158
159 /**
160 * Creates a new failover server set with the specified set of directory
161 * server addresses and port numbers. It will use the default socket factory
162 * provided by the JVM to create the underlying sockets.
163 *
164 * @param addresses The addresses of the directory servers to which the
165 * connections should be established. It must not be
166 * {@code null} or empty.
167 * @param ports The ports of the directory servers to which the
168 * connections should be established. It must not be
169 * {@code null}, and it must have the same number of
170 * elements as the {@code addresses} array. The order of
171 * elements in the {@code addresses} array must correspond
172 * to the order of elements in the {@code ports} array.
173 */
174 public FailoverServerSet(final String[] addresses, final int[] ports)
175 {
176 this(addresses, ports, null, null);
177 }
178
179
180
181 /**
182 * Creates a new failover server set with the specified set of directory
183 * server addresses and port numbers. It will use the default socket factory
184 * provided by the JVM to create the underlying sockets.
185 *
186 * @param addresses The addresses of the directory servers to which
187 * the connections should be established. It must
188 * not be {@code null} or empty.
189 * @param ports The ports of the directory servers to which the
190 * connections should be established. It must not
191 * be {@code null}, and it must have the same
192 * number of elements as the {@code addresses}
193 * array. The order of elements in the
194 * {@code addresses} array must correspond to the
195 * order of elements in the {@code ports} array.
196 * @param connectionOptions The set of connection options to use for the
197 * underlying connections.
198 */
199 public FailoverServerSet(final String[] addresses, final int[] ports,
200 final LDAPConnectionOptions connectionOptions)
201 {
202 this(addresses, ports, null, connectionOptions);
203 }
204
205
206
207 /**
208 * Creates a new failover server set with the specified set of directory
209 * server addresses and port numbers. It will use the provided socket factory
210 * to create the underlying sockets.
211 *
212 * @param addresses The addresses of the directory servers to which the
213 * connections should be established. It must not be
214 * {@code null} or empty.
215 * @param ports The ports of the directory servers to which the
216 * connections should be established. It must not be
217 * {@code null}, and it must have the same number of
218 * elements as the {@code addresses} array. The order
219 * of elements in the {@code addresses} array must
220 * correspond to the order of elements in the
221 * {@code ports} array.
222 * @param socketFactory The socket factory to use to create the underlying
223 * connections.
224 */
225 public FailoverServerSet(final String[] addresses, final int[] ports,
226 final SocketFactory socketFactory)
227 {
228 this(addresses, ports, socketFactory, null);
229 }
230
231
232
233 /**
234 * Creates a new failover server set with the specified set of directory
235 * server addresses and port numbers. It will use the provided socket factory
236 * to create the underlying sockets.
237 *
238 * @param addresses The addresses of the directory servers to which
239 * the connections should be established. It must
240 * not be {@code null} or empty.
241 * @param ports The ports of the directory servers to which the
242 * connections should be established. It must not
243 * be {@code null}, and it must have the same
244 * number of elements as the {@code addresses}
245 * array. The order of elements in the
246 * {@code addresses} array must correspond to the
247 * order of elements in the {@code ports} array.
248 * @param socketFactory The socket factory to use to create the
249 * underlying connections.
250 * @param connectionOptions The set of connection options to use for the
251 * underlying connections.
252 */
253 public FailoverServerSet(final String[] addresses, final int[] ports,
254 final SocketFactory socketFactory,
255 final LDAPConnectionOptions connectionOptions)
256 {
257 ensureNotNull(addresses, ports);
258 ensureTrue(addresses.length > 0,
259 "FailoverServerSet.addresses must not be empty.");
260 ensureTrue(addresses.length == ports.length,
261 "FailoverServerSet addresses and ports arrays must be the same size.");
262
263 reOrderOnFailover = new AtomicBoolean(false);
264 maxFailoverConnectionAge = null;
265
266 final SocketFactory sf;
267 if (socketFactory == null)
268 {
269 sf = SocketFactory.getDefault();
270 }
271 else
272 {
273 sf = socketFactory;
274 }
275
276 final LDAPConnectionOptions co;
277 if (connectionOptions == null)
278 {
279 co = new LDAPConnectionOptions();
280 }
281 else
282 {
283 co = connectionOptions;
284 }
285
286 serverSets = new ServerSet[addresses.length];
287 for (int i=0; i < serverSets.length; i++)
288 {
289 serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co);
290 }
291 }
292
293
294
295 /**
296 * Creates a new failover server set that will fail over between the provided
297 * server sets.
298 *
299 * @param serverSets The server sets between which failover should occur.
300 * It must not be {@code null} or empty.
301 */
302 public FailoverServerSet(final ServerSet... serverSets)
303 {
304 ensureNotNull(serverSets);
305 ensureFalse(serverSets.length == 0,
306 "FailoverServerSet.serverSets must not be empty.");
307
308 this.serverSets = serverSets;
309
310 reOrderOnFailover = new AtomicBoolean(false);
311 maxFailoverConnectionAge = null;
312 }
313
314
315
316 /**
317 * Creates a new failover server set that will fail over between the provided
318 * server sets.
319 *
320 * @param serverSets The server sets between which failover should occur.
321 * It must not be {@code null} or empty.
322 */
323 public FailoverServerSet(final List<ServerSet> serverSets)
324 {
325 ensureNotNull(serverSets);
326 ensureFalse(serverSets.isEmpty(),
327 "FailoverServerSet.serverSets must not be empty.");
328
329 this.serverSets = new ServerSet[serverSets.size()];
330 serverSets.toArray(this.serverSets);
331
332 reOrderOnFailover = new AtomicBoolean(false);
333 maxFailoverConnectionAge = null;
334 }
335
336
337
338 /**
339 * Retrieves the server sets over which failover will occur. If this failover
340 * server set was created from individual servers rather than server sets,
341 * then the elements contained in the returned array will be
342 * {@code SingleServerSet} instances.
343 *
344 * @return The server sets over which failover will occur.
345 */
346 public ServerSet[] getServerSets()
347 {
348 return serverSets;
349 }
350
351
352
353 /**
354 * Indicates whether the list of servers or server sets used by this failover
355 * server set should be re-ordered in the event that a failure is encountered
356 * while attempting to establish a connection. If {@code true}, then any
357 * failed attempt to establish a connection to a server set at the beginning
358 * of the list may cause that server/set to be moved to the end of the list so
359 * that it will be the last one tried on the next attempt.
360 *
361 * @return {@code true} if the order of elements in the associated list of
362 * servers or server sets should be updated if a failure occurs while
363 * attempting to establish a connection, or {@code false} if the
364 * original order should be preserved.
365 */
366 public boolean reOrderOnFailover()
367 {
368 return reOrderOnFailover.get();
369 }
370
371
372
373 /**
374 * Specifies whether the list of servers or server sets used by this failover
375 * server set should be re-ordered in the event that a failure is encountered
376 * while attempting to establish a connection. By default, the original
377 * order will be preserved, but if this method is called with a value of
378 * {@code true}, then a failed attempt to establish a connection to the server
379 * or server set at the beginning of the list may cause that server to be
380 * moved to the end of the list so that it will be the last server/set tried
381 * on the next attempt.
382 *
383 * @param reOrderOnFailover Indicates whether the list of servers or server
384 * sets should be re-ordered in the event that a
385 * failure is encountered while attempting to
386 * establish a connection.
387 */
388 public void setReOrderOnFailover(final boolean reOrderOnFailover)
389 {
390 this.reOrderOnFailover.set(reOrderOnFailover);
391 }
392
393
394
395 /**
396 * Retrieves the maximum connection age that should be used for "failover"
397 * connections (i.e., connections that are established to any server other
398 * than the most-preferred server, or established using any server set other
399 * than the most-preferred set). This will only be used if this failover
400 * server set is used to create an {@link LDAPConnectionPool}, for connections
401 * within that pool.
402 *
403 * @return The maximum connection age that should be used for failover
404 * connections, a value of zero to indicate that no maximum age
405 * should apply to those connections, or {@code null} if the maximum
406 * connection age should be determined by the associated connection
407 * pool.
408 */
409 public Long getMaxFailoverConnectionAgeMillis()
410 {
411 return maxFailoverConnectionAge;
412 }
413
414
415
416 /**
417 * Specifies the maximum connection age that should be used for "failover"
418 * connections (i.e., connections that are established to any server other
419 * than the most-preferred server, or established using any server set other
420 * than the most-preferred set). This will only be used if this failover
421 * server set is used to create an {@link LDAPConnectionPool}, for connections
422 * within that pool.
423 *
424 * @param maxFailoverConnectionAge The maximum connection age that should be
425 * used for failover connections. It may be
426 * less than or equal to zero to indicate
427 * that no maximum age should apply to such
428 * connections, or {@code null} to indicate
429 * that the maximum connection age should be
430 * determined by the associated connection
431 * pool.
432 */
433 public void setMaxFailoverConnectionAgeMillis(
434 final Long maxFailoverConnectionAge)
435 {
436 if (maxFailoverConnectionAge == null)
437 {
438 this.maxFailoverConnectionAge = null;
439 }
440 else if (maxFailoverConnectionAge > 0L)
441 {
442 this.maxFailoverConnectionAge = maxFailoverConnectionAge;
443 }
444 else
445 {
446 this.maxFailoverConnectionAge = 0L;
447 }
448 }
449
450
451
452 /**
453 * {@inheritDoc}
454 */
455 @Override()
456 public LDAPConnection getConnection()
457 throws LDAPException
458 {
459 return getConnection(null);
460 }
461
462
463
464 /**
465 * {@inheritDoc}
466 */
467 @Override()
468 public LDAPConnection getConnection(
469 final LDAPConnectionPoolHealthCheck healthCheck)
470 throws LDAPException
471 {
472 if (reOrderOnFailover.get() && (serverSets.length > 1))
473 {
474 synchronized (this)
475 {
476 // First, try to get a connection using the first set in the list. If
477 // this succeeds, then we don't need to go any further.
478 try
479 {
480 return serverSets[0].getConnection(healthCheck);
481 }
482 catch (final LDAPException le)
483 {
484 debugException(le);
485 }
486
487 // If we've gotten here, then we will need to re-order the list unless
488 // all other attempts fail.
489 int successfulPos = -1;
490 LDAPConnection conn = null;
491 LDAPException lastException = null;
492 for (int i=1; i < serverSets.length; i++)
493 {
494 try
495 {
496 conn = serverSets[i].getConnection(healthCheck);
497 successfulPos = i;
498 break;
499 }
500 catch (final LDAPException le)
501 {
502 debugException(le);
503 lastException = le;
504 }
505 }
506
507 if (successfulPos > 0)
508 {
509 int pos = 0;
510 final ServerSet[] setCopy = new ServerSet[serverSets.length];
511 for (int i=successfulPos; i < serverSets.length; i++)
512 {
513 setCopy[pos++] = serverSets[i];
514 }
515
516 for (int i=0; i < successfulPos; i++)
517 {
518 setCopy[pos++] = serverSets[i];
519 }
520
521 System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length);
522 if (maxFailoverConnectionAge != null)
523 {
524 conn.setAttachment(
525 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE,
526 maxFailoverConnectionAge);
527 }
528 return conn;
529 }
530 else
531 {
532 throw lastException;
533 }
534 }
535 }
536 else
537 {
538 LDAPException lastException = null;
539
540 boolean first = true;
541 for (final ServerSet s : serverSets)
542 {
543 try
544 {
545 final LDAPConnection conn = s.getConnection(healthCheck);
546 if ((! first) && (maxFailoverConnectionAge != null))
547 {
548 conn.setAttachment(
549 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE,
550 maxFailoverConnectionAge);
551 }
552 return conn;
553 }
554 catch (LDAPException le)
555 {
556 first = false;
557 debugException(le);
558 lastException = le;
559 }
560 }
561
562 throw lastException;
563 }
564 }
565
566
567
568 /**
569 * {@inheritDoc}
570 */
571 @Override()
572 public void toString(final StringBuilder buffer)
573 {
574 buffer.append("FailoverServerSet(serverSets={");
575
576 for (int i=0; i < serverSets.length; i++)
577 {
578 if (i > 0)
579 {
580 buffer.append(", ");
581 }
582
583 serverSets[i].toString(buffer);
584 }
585
586 buffer.append("})");
587 }
588 }