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