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