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