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