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