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