001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 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.Debug;
030import com.unboundid.util.NotMutable;
031import com.unboundid.util.StaticUtils;
032import com.unboundid.util.ThreadSafety;
033import com.unboundid.util.ThreadSafetyLevel;
034import 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    this(addresses, ports, socketFactory, connectionOptions, null, null);
258  }
259
260
261
262  /**
263   * Creates a new failover server set with the specified set of directory
264   * server addresses and port numbers.  It will use the provided socket factory
265   * to create the underlying sockets.
266   *
267   * @param  addresses             The addresses of the directory servers to
268   *                               which the connections should be established.
269   *                               It must not be {@code null} or empty.
270   * @param  ports                 The ports of the directory servers to which
271   *                               the connections should be established.  It
272   *                               must not be {@code null}, and it must have
273   *                               the same number of elements as the
274   *                               {@code addresses} array.  The order of
275   *                               elements in the {@code addresses} array must
276   *                               correspond to the order of elements in the
277   *                               {@code ports} array.
278   * @param  socketFactory         The socket factory to use to create the
279   *                               underlying connections.
280   * @param  connectionOptions     The set of connection options to use for the
281   *                               underlying connections.
282   * @param  bindRequest           The bind request that should be used to
283   *                               authenticate newly-established connections.
284   *                               It may be {@code null} if this server set
285   *                               should not perform any authentication.
286   * @param  postConnectProcessor  The post-connect processor that should be
287   *                               invoked on newly-established connections.  It
288   *                               may be {@code null} if this server set should
289   *                               not perform any post-connect processing.
290   */
291  public FailoverServerSet(final String[] addresses, final int[] ports,
292                           final SocketFactory socketFactory,
293                           final LDAPConnectionOptions connectionOptions,
294                           final BindRequest bindRequest,
295                           final PostConnectProcessor postConnectProcessor)
296  {
297    Validator.ensureNotNull(addresses, ports);
298    Validator.ensureTrue(addresses.length > 0,
299         "FailoverServerSet.addresses must not be empty.");
300    Validator.ensureTrue(addresses.length == ports.length,
301         "FailoverServerSet addresses and ports arrays must be the same size.");
302
303    reOrderOnFailover = new AtomicBoolean(false);
304    maxFailoverConnectionAge = null;
305
306    final SocketFactory sf;
307    if (socketFactory == null)
308    {
309      sf = SocketFactory.getDefault();
310    }
311    else
312    {
313      sf = socketFactory;
314    }
315
316    final LDAPConnectionOptions co;
317    if (connectionOptions == null)
318    {
319      co = new LDAPConnectionOptions();
320    }
321    else
322    {
323      co = connectionOptions;
324    }
325
326    serverSets = new ServerSet[addresses.length];
327    for (int i=0; i < serverSets.length; i++)
328    {
329      serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co,
330           bindRequest, postConnectProcessor);
331    }
332  }
333
334
335
336  /**
337   * Creates a new failover server set that will fail over between the provided
338   * server sets.
339   *
340   * @param  serverSets  The server sets between which failover should occur.
341   *                     It must not be {@code null} or empty.  All of the
342   *                     provided sets must have the same return value for their
343   *                     {@link #includesAuthentication()} method, and all of
344   *                     the provided sets must have the same return value for
345   *                     their {@link #includesPostConnectProcessing()}
346   *                     method.
347   */
348  public FailoverServerSet(final ServerSet... serverSets)
349  {
350    this(StaticUtils.toList(serverSets));
351  }
352
353
354
355  /**
356   * Creates a new failover server set that will fail over between the provided
357   * server sets.
358   *
359   * @param  serverSets  The server sets between which failover should occur.
360   *                     It must not be {@code null} or empty.  All of the
361   *                     provided sets must have the same return value for their
362   *                     {@link #includesAuthentication()} method, and all of
363   *                     the provided sets must have the same return value for
364   *                     their {@link #includesPostConnectProcessing()}
365   *                     method.
366   */
367  public FailoverServerSet(final List<ServerSet> serverSets)
368  {
369    Validator.ensureNotNull(serverSets);
370    Validator.ensureFalse(serverSets.isEmpty(),
371         "FailoverServerSet.serverSets must not be empty.");
372
373    this.serverSets = new ServerSet[serverSets.size()];
374    serverSets.toArray(this.serverSets);
375
376    boolean anySupportsAuthentication = false;
377    boolean allSupportAuthentication = true;
378    boolean anySupportsPostConnectProcessing = false;
379    boolean allSupportPostConnectProcessing = true;
380    for (final ServerSet serverSet : this.serverSets)
381    {
382      if (serverSet.includesAuthentication())
383      {
384        anySupportsAuthentication = true;
385      }
386      else
387      {
388        allSupportAuthentication = false;
389      }
390
391      if (serverSet.includesPostConnectProcessing())
392      {
393        anySupportsPostConnectProcessing = true;
394      }
395      else
396      {
397        allSupportPostConnectProcessing = false;
398      }
399    }
400
401    if (anySupportsAuthentication)
402    {
403      Validator.ensureTrue(allSupportAuthentication,
404           "When creating a FailoverServerSet from a collection of server " +
405                "sets, either all of those sets must include authentication, " +
406                "or none of those sets may include authentication.");
407    }
408
409    if (anySupportsPostConnectProcessing)
410    {
411      Validator.ensureTrue(allSupportPostConnectProcessing,
412           "When creating a FailoverServerSet from a collection of server " +
413                "sets, either all of those sets must include post-connect " +
414                "processing, or none of those sets may include post-connect " +
415                "processing.");
416    }
417
418    reOrderOnFailover = new AtomicBoolean(false);
419    maxFailoverConnectionAge = null;
420  }
421
422
423
424  /**
425   * Retrieves the server sets over which failover will occur.  If this failover
426   * server set was created from individual servers rather than server sets,
427   * then the elements contained in the returned array will be
428   * {@code SingleServerSet} instances.
429   *
430   * @return  The server sets over which failover will occur.
431   */
432  public ServerSet[] getServerSets()
433  {
434    return serverSets;
435  }
436
437
438
439  /**
440   * Indicates whether the list of servers or server sets used by this failover
441   * server set should be re-ordered in the event that a failure is encountered
442   * while attempting to establish a connection.  If {@code true}, then any
443   * failed attempt to establish a connection to a server set at the beginning
444   * of the list may cause that server/set to be moved to the end of the list so
445   * that it will be the last one tried on the next attempt.
446   *
447   * @return  {@code true} if the order of elements in the associated list of
448   *          servers or server sets should be updated if a failure occurs while
449   *          attempting to establish a connection, or {@code false} if the
450   *          original order should be preserved.
451   */
452  public boolean reOrderOnFailover()
453  {
454    return reOrderOnFailover.get();
455  }
456
457
458
459  /**
460   * Specifies whether the list of servers or server sets used by this failover
461   * server set should be re-ordered in the event that a failure is encountered
462   * while attempting to establish a connection.  By default, the original
463   * order will be preserved, but if this method is called with a value of
464   * {@code true}, then a failed attempt to establish a connection to the server
465   * or server set at the beginning of the list may cause that server to be
466   * moved to the end of the list so that it will be the last server/set tried
467   * on the next attempt.
468   *
469   * @param  reOrderOnFailover  Indicates whether the list of servers or server
470   *                            sets should be re-ordered in the event that a
471   *                            failure is encountered while attempting to
472   *                            establish a connection.
473   */
474  public void setReOrderOnFailover(final boolean reOrderOnFailover)
475  {
476    this.reOrderOnFailover.set(reOrderOnFailover);
477  }
478
479
480
481  /**
482   * Retrieves the maximum connection age that should be used for "failover"
483   * connections (i.e., connections that are established to any server other
484   * than the most-preferred server, or established using any server set other
485   * than the most-preferred set).  This will only be used if this failover
486   * server set is used to create an {@link LDAPConnectionPool}, for connections
487   * within that pool.
488   *
489   * @return  The maximum connection age that should be used for failover
490   *          connections, a value of zero to indicate that no maximum age
491   *          should apply to those connections, or {@code null} if the maximum
492   *          connection age should be determined by the associated connection
493   *          pool.
494   */
495  public Long getMaxFailoverConnectionAgeMillis()
496  {
497    return maxFailoverConnectionAge;
498  }
499
500
501
502  /**
503   * Specifies the maximum connection age that should be used for "failover"
504   * connections (i.e., connections that are established to any server other
505   * than the most-preferred server, or established using any server set other
506   * than the most-preferred set).  This will only be used if this failover
507   * server set is used to create an {@link LDAPConnectionPool}, for connections
508   * within that pool.
509   *
510   * @param  maxFailoverConnectionAge  The maximum connection age that should be
511   *                                   used for failover connections.  It may be
512   *                                   less than or equal to zero to indicate
513   *                                   that no maximum age should apply to such
514   *                                   connections, or {@code null} to indicate
515   *                                   that the maximum connection age should be
516   *                                   determined by the associated connection
517   *                                   pool.
518   */
519  public void setMaxFailoverConnectionAgeMillis(
520                   final Long maxFailoverConnectionAge)
521  {
522    if (maxFailoverConnectionAge == null)
523    {
524      this.maxFailoverConnectionAge = null;
525    }
526    else if (maxFailoverConnectionAge > 0L)
527    {
528      this.maxFailoverConnectionAge = maxFailoverConnectionAge;
529    }
530    else
531    {
532      this.maxFailoverConnectionAge = 0L;
533    }
534  }
535
536
537
538  /**
539   * {@inheritDoc}
540   */
541  @Override()
542  public boolean includesAuthentication()
543  {
544    return serverSets[0].includesAuthentication();
545  }
546
547
548
549  /**
550   * {@inheritDoc}
551   */
552  @Override()
553  public boolean includesPostConnectProcessing()
554  {
555    return serverSets[0].includesPostConnectProcessing();
556  }
557
558
559
560  /**
561   * {@inheritDoc}
562   */
563  @Override()
564  public LDAPConnection getConnection()
565         throws LDAPException
566  {
567    return getConnection(null);
568  }
569
570
571
572  /**
573   * {@inheritDoc}
574   */
575  @Override()
576  public LDAPConnection getConnection(
577                             final LDAPConnectionPoolHealthCheck healthCheck)
578         throws LDAPException
579  {
580    if (reOrderOnFailover.get() && (serverSets.length > 1))
581    {
582      synchronized (this)
583      {
584        // First, try to get a connection using the first set in the list.  If
585        // this succeeds, then we don't need to go any further.
586        try
587        {
588          return serverSets[0].getConnection(healthCheck);
589        }
590        catch (final LDAPException le)
591        {
592          Debug.debugException(le);
593        }
594
595        // If we've gotten here, then we will need to re-order the list unless
596        // all other attempts fail.
597        int successfulPos = -1;
598        LDAPConnection conn = null;
599        LDAPException lastException = null;
600        for (int i=1; i < serverSets.length; i++)
601        {
602          try
603          {
604            conn = serverSets[i].getConnection(healthCheck);
605            successfulPos = i;
606            break;
607          }
608          catch (final LDAPException le)
609          {
610            Debug.debugException(le);
611            lastException = le;
612          }
613        }
614
615        if (successfulPos > 0)
616        {
617          int pos = 0;
618          final ServerSet[] setCopy = new ServerSet[serverSets.length];
619          for (int i=successfulPos; i < serverSets.length; i++)
620          {
621            setCopy[pos++] = serverSets[i];
622          }
623
624          for (int i=0; i < successfulPos; i++)
625          {
626            setCopy[pos++] = serverSets[i];
627          }
628
629          System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length);
630          if (maxFailoverConnectionAge != null)
631          {
632            conn.setAttachment(
633                 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE,
634                 maxFailoverConnectionAge);
635          }
636          return conn;
637        }
638        else
639        {
640          throw lastException;
641        }
642      }
643    }
644    else
645    {
646      LDAPException lastException = null;
647
648      boolean first = true;
649      for (final ServerSet s : serverSets)
650      {
651        try
652        {
653          final LDAPConnection conn = s.getConnection(healthCheck);
654          if ((! first) && (maxFailoverConnectionAge != null))
655          {
656            conn.setAttachment(
657                 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE,
658                 maxFailoverConnectionAge);
659          }
660          return conn;
661        }
662        catch (final LDAPException le)
663        {
664          first = false;
665          Debug.debugException(le);
666          lastException = le;
667        }
668      }
669
670      throw lastException;
671    }
672  }
673
674
675
676  /**
677   * {@inheritDoc}
678   */
679  @Override()
680  public void toString(final StringBuilder buffer)
681  {
682    buffer.append("FailoverServerSet(serverSets={");
683
684    for (int i=0; i < serverSets.length; i++)
685    {
686      if (i > 0)
687      {
688        buffer.append(", ");
689      }
690
691      serverSets[i].toString(buffer);
692    }
693
694    buffer.append("})");
695  }
696}