001    /*
002     * Copyright 2008-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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     *   String[] addresses =
058     *   {
059     *     "ds1.example.com",
060     *     "ds2.example.com"
061     *   };
062     *   int[] ports =
063     *   {
064     *     389,
065     *     389
066     *   };
067     *   FailoverServerSet failoverSet = new FailoverServerSet(addresses, ports);
068     * </PRE>
069     * This second example demonstrates the process for creating a failover server
070     * set which actually fails over between two different data centers (east and
071     * west), with each data center containing two servers that will be accessed in
072     * a round-robin manner.  It will first try to connect to one of the servers in
073     * the east data center, and if that attempt fails then it will try to connect
074     * to the other server in the east data center.  If both of them fail, then it
075     * will try to connect to one of the servers in the west data center, and
076     * finally as a last resort the other server in the west data center:
077     * <PRE>
078     *   String[] eastAddresses =
079     *   {
080     *     "ds-east-1.example.com",
081     *     "ds-east-2.example.com",
082     *   };
083     *   int[] eastPorts =
084     *   {
085     *     389,
086     *     389
087     *   }
088     *   RoundRobinServerSet eastSet =
089     *        new RoundRobinServerSet(eastAddresses, eastPorts);
090     *
091     *   String[] westAddresses =
092     *   {
093     *     "ds-west-1.example.com",
094     *     "ds-west-2.example.com",
095     *   };
096     *   int[] westPorts =
097     *   {
098     *     389,
099     *     389
100     *   }
101     *   RoundRobinServerSet westSet =
102     *        new RoundRobinServerSet(westAddresses, westPorts);
103     *
104     *   FailoverServerSet failoverSet = new FailoverServerSet(eastSet, westSet);
105     * </PRE>
106     */
107    @NotMutable()
108    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
109    public final class FailoverServerSet
110           extends ServerSet
111    {
112      // Indicates whether to re-order the server set list if failover occurs.
113      private final AtomicBoolean reOrderOnFailover;
114    
115      // The server sets for which we will allow failover.
116      private final ServerSet[] serverSets;
117    
118    
119    
120      /**
121       * Creates a new failover server set with the specified set of directory
122       * server addresses and port numbers.  It will use the default socket factory
123       * provided by the JVM to create the underlying sockets.
124       *
125       * @param  addresses  The addresses of the directory servers to which the
126       *                    connections should be established.  It must not be
127       *                    {@code null} or empty.
128       * @param  ports      The ports of the directory servers to which the
129       *                    connections should be established.  It must not be
130       *                    {@code null}, and it must have the same number of
131       *                    elements as the {@code addresses} array.  The order of
132       *                    elements in the {@code addresses} array must correspond
133       *                    to the order of elements in the {@code ports} array.
134       */
135      public FailoverServerSet(final String[] addresses, final int[] ports)
136      {
137        this(addresses, ports, null, null);
138      }
139    
140    
141    
142      /**
143       * Creates a new failover server set with the specified set of directory
144       * server addresses and port numbers.  It will use the default socket factory
145       * provided by the JVM to create the underlying sockets.
146       *
147       * @param  addresses          The addresses of the directory servers to which
148       *                            the connections should be established.  It must
149       *                            not be {@code null} or empty.
150       * @param  ports              The ports of the directory servers to which the
151       *                            connections should be established.  It must not
152       *                            be {@code null}, and it must have the same
153       *                            number of elements as the {@code addresses}
154       *                            array.  The order of elements in the
155       *                            {@code addresses} array must correspond to the
156       *                            order of elements in the {@code ports} array.
157       * @param  connectionOptions  The set of connection options to use for the
158       *                            underlying connections.
159       */
160      public FailoverServerSet(final String[] addresses, final int[] ports,
161                               final LDAPConnectionOptions connectionOptions)
162      {
163        this(addresses, ports, null, connectionOptions);
164      }
165    
166    
167    
168      /**
169       * Creates a new failover server set with the specified set of directory
170       * server addresses and port numbers.  It will use the provided socket factory
171       * to create the underlying sockets.
172       *
173       * @param  addresses      The addresses of the directory servers to which the
174       *                        connections should be established.  It must not be
175       *                        {@code null} or empty.
176       * @param  ports          The ports of the directory servers to which the
177       *                        connections should be established.  It must not be
178       *                        {@code null}, and it must have the same number of
179       *                        elements as the {@code addresses} array.  The order
180       *                        of elements in the {@code addresses} array must
181       *                        correspond to the order of elements in the
182       *                        {@code ports} array.
183       * @param  socketFactory  The socket factory to use to create the underlying
184       *                        connections.
185       */
186      public FailoverServerSet(final String[] addresses, final int[] ports,
187                               final SocketFactory socketFactory)
188      {
189        this(addresses, ports, socketFactory, null);
190      }
191    
192    
193    
194      /**
195       * Creates a new failover server set with the specified set of directory
196       * server addresses and port numbers.  It will use the provided socket factory
197       * to create the underlying sockets.
198       *
199       * @param  addresses          The addresses of the directory servers to which
200       *                            the connections should be established.  It must
201       *                            not be {@code null} or empty.
202       * @param  ports              The ports of the directory servers to which the
203       *                            connections should be established.  It must not
204       *                            be {@code null}, and it must have the same
205       *                            number of elements as the {@code addresses}
206       *                            array.  The order of elements in the
207       *                            {@code addresses} array must correspond to the
208       *                            order of elements in the {@code ports} array.
209       * @param  socketFactory      The socket factory to use to create the
210       *                            underlying connections.
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 SocketFactory socketFactory,
216                               final LDAPConnectionOptions connectionOptions)
217      {
218        ensureNotNull(addresses, ports);
219        ensureTrue(addresses.length > 0,
220                   "FailoverServerSet.addresses must not be empty.");
221        ensureTrue(addresses.length == ports.length,
222             "FailoverServerSet addresses and ports arrays must be the same size.");
223    
224        reOrderOnFailover = new AtomicBoolean(false);
225    
226        final SocketFactory sf;
227        if (socketFactory == null)
228        {
229          sf = SocketFactory.getDefault();
230        }
231        else
232        {
233          sf = socketFactory;
234        }
235    
236        final LDAPConnectionOptions co;
237        if (connectionOptions == null)
238        {
239          co = new LDAPConnectionOptions();
240        }
241        else
242        {
243          co = connectionOptions;
244        }
245    
246    
247        serverSets = new ServerSet[addresses.length];
248        for (int i=0; i < serverSets.length; i++)
249        {
250          serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co);
251        }
252      }
253    
254    
255    
256      /**
257       * Creates a new failover server set that will fail over between the provided
258       * server sets.
259       *
260       * @param  serverSets  The server sets between which failover should occur.
261       *                     It must not be {@code null} or empty.
262       */
263      public FailoverServerSet(final ServerSet... serverSets)
264      {
265        ensureNotNull(serverSets);
266        ensureFalse(serverSets.length == 0,
267                    "FailoverServerSet.serverSets must not be empty.");
268    
269        this.serverSets = serverSets;
270    
271        reOrderOnFailover = new AtomicBoolean(false);
272      }
273    
274    
275    
276      /**
277       * Creates a new failover server set that will fail over between the provided
278       * server sets.
279       *
280       * @param  serverSets  The server sets between which failover should occur.
281       *                     It must not be {@code null} or empty.
282       */
283      public FailoverServerSet(final List<ServerSet> serverSets)
284      {
285        ensureNotNull(serverSets);
286        ensureFalse(serverSets.isEmpty(),
287                    "FailoverServerSet.serverSets must not be empty.");
288    
289        this.serverSets = new ServerSet[serverSets.size()];
290        serverSets.toArray(this.serverSets);
291    
292        reOrderOnFailover = new AtomicBoolean(false);
293      }
294    
295    
296    
297      /**
298       * Retrieves the server sets over which failover will occur.  If this failover
299       * server set was created from individual servers rather than server sets,
300       * then the elements contained in the returned array will be
301       * {@code SingleServerSet} instances.
302       *
303       * @return  The server sets over which failover will occur.
304       */
305      public ServerSet[] getServerSets()
306      {
307        return serverSets;
308      }
309    
310    
311    
312      /**
313       * Indicates whether the list of servers or server sets used by this failover
314       * server set should be re-ordered in the event that a failure is encountered
315       * while attempting to establish a connection.  If {@code true}, then any
316       * failed attempt to establish a connection to a server set at the beginning
317       * of the list may cause that server/set to be moved to the end of the list so
318       * that it will be the last one tried on the next attempt.
319       *
320       * @return  {@code true} if the order of elements in the associated list of
321       *          servers or server sets should be updated if a failure occurs while
322       *          attempting to establish a connection, or {@code false} if the
323       *          original order should be preserved.
324       */
325      public boolean reOrderOnFailover()
326      {
327        return reOrderOnFailover.get();
328      }
329    
330    
331    
332      /**
333       * Specifies whether the list of servers or server sets used by this failover
334       * server set should be re-ordered in the event that a failure is encountered
335       * while attempting to establish a connection.  By default, the original
336       * order will be preserved, but if this method is called with a value of
337       * {@code true}, then a failed attempt to establish a connection to the server
338       * or server set at the beginning of the list may cause that server to be
339       * moved to the end of the list so that it will be the last server/set tried
340       * on the next attempt.
341       *
342       * @param  reOrderOnFailover  Indicates whether the list of servers or server
343       *                            sets should be re-ordered in the event that a
344       *                            failure is encountered while attempting to
345       *                            establish a connection.
346       */
347      public void setReOrderOnFailover(final boolean reOrderOnFailover)
348      {
349        this.reOrderOnFailover.set(reOrderOnFailover);
350      }
351    
352    
353    
354      /**
355       * {@inheritDoc}
356       */
357      @Override()
358      public LDAPConnection getConnection()
359             throws LDAPException
360      {
361        return getConnection(null);
362      }
363    
364    
365    
366      /**
367       * {@inheritDoc}
368       */
369      @Override()
370      public LDAPConnection getConnection(
371                                 final LDAPConnectionPoolHealthCheck healthCheck)
372             throws LDAPException
373      {
374        if (reOrderOnFailover.get() && (serverSets.length > 1))
375        {
376          synchronized (this)
377          {
378            // First, try to get a connection using the first set in the list.  If
379            // this succeeds, then we don't need to go any further.
380            try
381            {
382              return serverSets[0].getConnection(healthCheck);
383            }
384            catch (final LDAPException le)
385            {
386              debugException(le);
387            }
388    
389            // If we've gotten here, then we will need to re-order the list unless
390            // all other attempts fail.
391            int successfulPos = -1;
392            LDAPConnection conn = null;
393            LDAPException lastException = null;
394            for (int i=1; i < serverSets.length; i++)
395            {
396              try
397              {
398                conn = serverSets[i].getConnection(healthCheck);
399                successfulPos = i;
400                break;
401              }
402              catch (final LDAPException le)
403              {
404                debugException(le);
405                lastException = le;
406              }
407            }
408    
409            if (successfulPos > 0)
410            {
411              int pos = 0;
412              final ServerSet[] setCopy = new ServerSet[serverSets.length];
413              for (int i=successfulPos; i < serverSets.length; i++)
414              {
415                setCopy[pos++] = serverSets[i];
416              }
417    
418              for (int i=0; i < successfulPos; i++)
419              {
420                setCopy[pos++] = serverSets[i];
421              }
422    
423              System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length);
424              return conn;
425            }
426            else
427            {
428              throw lastException;
429            }
430          }
431        }
432        else
433        {
434          LDAPException lastException = null;
435    
436          for (final ServerSet s : serverSets)
437          {
438            try
439            {
440              return s.getConnection(healthCheck);
441            }
442            catch (LDAPException le)
443            {
444              debugException(le);
445              lastException = le;
446            }
447          }
448    
449          throw lastException;
450        }
451      }
452    
453    
454    
455      /**
456       * {@inheritDoc}
457       */
458      @Override()
459      public void toString(final StringBuilder buffer)
460      {
461        buffer.append("FailoverServerSet(serverSets={");
462    
463        for (int i=0; i < serverSets.length; i++)
464        {
465          if (i > 0)
466          {
467            buffer.append(", ");
468          }
469    
470          serverSets[i].toString(buffer);
471        }
472    
473        buffer.append("})");
474      }
475    }