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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.List;
026import java.util.concurrent.atomic.AtomicBoolean;
027import javax.net.SocketFactory;
028
029import com.unboundid.util.NotMutable;
030import com.unboundid.util.ThreadSafety;
031import com.unboundid.util.ThreadSafetyLevel;
032
033import static com.unboundid.util.Debug.*;
034import 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)
144public 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}