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 javax.net.SocketFactory;
026    
027    import com.unboundid.util.NotMutable;
028    import com.unboundid.util.ThreadSafety;
029    import com.unboundid.util.ThreadSafetyLevel;
030    
031    import static com.unboundid.util.Debug.*;
032    import static com.unboundid.util.Validator.*;
033    
034    
035    
036    /**
037     * This class provides a server set implementation that will use a round-robin
038     * algorithm to select the server to which the connection should be established.
039     * Any number of servers may be included in this server set, and each request
040     * will attempt to retrieve a connection to the next server in the list,
041     * circling back to the beginning of the list as necessary.  If a server is
042     * unavailable when an attempt is made to establish a connection to it, then
043     * the connection will be established to the next available server in the set.
044     * <BR><BR>
045     * <H2>Example</H2>
046     * The following example demonstrates the process for creating a round-robin
047     * server set that may be used to establish connections to either of two
048     * servers.  When using the server set to attempt to create a connection, it
049     * will first try one of the servers, but will fail over to the other if the
050     * first one attempted is not available:
051     * <PRE>
052     *   String[] addresses =
053     *   {
054     *     "ds1.example.com",
055     *     "ds2.example.com",
056     *   };
057     *   int[] ports =
058     *   {
059     *     389,
060     *     389
061     *   };
062     *   RoundRobinServerSet roundRobinSet =
063     *        new RoundRobinServerSet(addresses, ports);
064     * </PRE>
065     */
066    @NotMutable()
067    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
068    public final class RoundRobinServerSet
069           extends ServerSet
070    {
071      // The port numbers of the target servers.
072      private final int[] ports;
073    
074      // The set of connection options to use for new connections.
075      private final LDAPConnectionOptions connectionOptions;
076    
077      // The socket factory to use to establish connections.
078      private final SocketFactory socketFactory;
079    
080      // The addresses of the target servers.
081      private final String[] addresses;
082    
083      // The slot to use for the server to be selected for the next connection
084      // attempt.
085      private int nextSlot;
086    
087    
088    
089      /**
090       * Creates a new round robin server set with the specified set of directory
091       * server addresses and port numbers.  It will use the default socket factory
092       * provided by the JVM to create the underlying sockets.
093       *
094       * @param  addresses  The addresses of the directory servers to which the
095       *                    connections should be established.  It must not be
096       *                    {@code null} or empty.
097       * @param  ports      The ports of the directory servers to which the
098       *                    connections should be established.  It must not be
099       *                    {@code null}, and it must have the same number of
100       *                    elements as the {@code addresses} array.  The order of
101       *                    elements in the {@code addresses} array must correspond
102       *                    to the order of elements in the {@code ports} array.
103       */
104      public RoundRobinServerSet(final String[] addresses, final int[] ports)
105      {
106        this(addresses, ports, null, null);
107      }
108    
109    
110    
111      /**
112       * Creates a new round robin server set with the specified set of directory
113       * server addresses and port numbers.  It will use the default socket factory
114       * provided by the JVM to create the underlying sockets.
115       *
116       * @param  addresses          The addresses of the directory servers to which
117       *                            the connections should be established.  It must
118       *                            not be {@code null} or empty.
119       * @param  ports              The ports of the directory servers to which the
120       *                            connections should be established.  It must not
121       *                            be {@code null}, and it must have the same
122       *                            number of elements as the {@code addresses}
123       *                            array.  The order of elements in the
124       *                            {@code addresses} array must correspond to the
125       *                            order of elements in the {@code ports} array.
126       * @param  connectionOptions  The set of connection options to use for the
127       *                            underlying connections.
128       */
129      public RoundRobinServerSet(final String[] addresses, final int[] ports,
130                                 final LDAPConnectionOptions connectionOptions)
131      {
132        this(addresses, ports, null, connectionOptions);
133      }
134    
135    
136    
137      /**
138       * Creates a new round robin server set with the specified set of directory
139       * server addresses and port numbers.  It will use the provided socket factory
140       * to create the underlying sockets.
141       *
142       * @param  addresses      The addresses of the directory servers to which the
143       *                        connections should be established.  It must not be
144       *                        {@code null} or empty.
145       * @param  ports          The ports of the directory servers to which the
146       *                        connections should be established.  It must not be
147       *                        {@code null}, and it must have the same number of
148       *                        elements as the {@code addresses} array.  The order
149       *                        of elements in the {@code addresses} array must
150       *                        correspond to the order of elements in the
151       *                        {@code ports} array.
152       * @param  socketFactory  The socket factory to use to create the underlying
153       *                        connections.
154       */
155      public RoundRobinServerSet(final String[] addresses, final int[] ports,
156                                 final SocketFactory socketFactory)
157      {
158        this(addresses, ports, socketFactory, null);
159      }
160    
161    
162    
163      /**
164       * Creates a new round robin server set with the specified set of directory
165       * server addresses and port numbers.  It will use the provided socket factory
166       * to create the underlying sockets.
167       *
168       * @param  addresses          The addresses of the directory servers to which
169       *                            the connections should be established.  It must
170       *                            not be {@code null} or empty.
171       * @param  ports              The ports of the directory servers to which the
172       *                            connections should be established.  It must not
173       *                            be {@code null}, and it must have the same
174       *                            number of elements as the {@code addresses}
175       *                            array.  The order of elements in the
176       *                            {@code addresses} array must correspond to the
177       *                            order of elements in the {@code ports} array.
178       * @param  socketFactory      The socket factory to use to create the
179       *                            underlying connections.
180       * @param  connectionOptions  The set of connection options to use for the
181       *                            underlying connections.
182       */
183      public RoundRobinServerSet(final String[] addresses, final int[] ports,
184                                 final SocketFactory socketFactory,
185                                 final LDAPConnectionOptions connectionOptions)
186      {
187        ensureNotNull(addresses, ports);
188        ensureTrue(addresses.length > 0,
189                   "RoundRobinServerSet.addresses must not be empty.");
190        ensureTrue(addresses.length == ports.length,
191                   "RoundRobinServerSet addresses and ports arrays must be the " +
192                        "same size.");
193    
194        this.addresses = addresses;
195        this.ports     = ports;
196    
197        if (socketFactory == null)
198        {
199          this.socketFactory = SocketFactory.getDefault();
200        }
201        else
202        {
203          this.socketFactory = socketFactory;
204        }
205    
206        if (connectionOptions == null)
207        {
208          this.connectionOptions = new LDAPConnectionOptions();
209        }
210        else
211        {
212          this.connectionOptions = connectionOptions;
213        }
214    
215        nextSlot = 0;
216      }
217    
218    
219    
220      /**
221       * Retrieves the addresses of the directory servers to which the connections
222       * should be established.
223       *
224       * @return  The addresses of the directory servers to which the connections
225       *          should be established.
226       */
227      public String[] getAddresses()
228      {
229        return addresses;
230      }
231    
232    
233    
234      /**
235       * Retrieves the ports of the directory servers to which the connections
236       * should be established.
237       *
238       * @return  The ports of the directory servers to which the connections should
239       *          be established.
240       */
241      public int[] getPorts()
242      {
243        return ports;
244      }
245    
246    
247    
248      /**
249       * Retrieves the socket factory that will be used to establish connections.
250       *
251       * @return  The socket factory that will be used to establish connections.
252       */
253      public SocketFactory getSocketFactory()
254      {
255        return socketFactory;
256      }
257    
258    
259    
260      /**
261       * Retrieves the set of connection options that will be used for underlying
262       * connections.
263       *
264       * @return  The set of connection options that will be used for underlying
265       *          connections.
266       */
267      public LDAPConnectionOptions getConnectionOptions()
268      {
269        return connectionOptions;
270      }
271    
272    
273    
274      /**
275       * {@inheritDoc}
276       */
277      @Override()
278      public LDAPConnection getConnection()
279             throws LDAPException
280      {
281        return getConnection(null);
282      }
283    
284    
285    
286      /**
287       * {@inheritDoc}
288       */
289      @Override()
290      public synchronized LDAPConnection getConnection(
291                               final LDAPConnectionPoolHealthCheck healthCheck)
292             throws LDAPException
293      {
294        final int initialSlotNumber = nextSlot++;
295    
296        if (nextSlot >= addresses.length)
297        {
298          nextSlot = 0;
299        }
300    
301        try
302        {
303          final LDAPConnection c = new LDAPConnection(socketFactory,
304               connectionOptions, addresses[initialSlotNumber],
305               ports[initialSlotNumber]);
306          if (healthCheck != null)
307          {
308            try
309            {
310              healthCheck.ensureNewConnectionValid(c);
311            }
312            catch (LDAPException le)
313            {
314              c.close();
315              throw le;
316            }
317          }
318          return c;
319        }
320        catch (LDAPException le)
321        {
322          debugException(le);
323          LDAPException lastException = le;
324    
325          while (nextSlot != initialSlotNumber)
326          {
327            final int slotNumber = nextSlot++;
328            if (nextSlot >= addresses.length)
329            {
330              nextSlot = 0;
331            }
332    
333            try
334            {
335              final LDAPConnection c = new LDAPConnection(socketFactory,
336                   connectionOptions, addresses[slotNumber], ports[slotNumber]);
337              if (healthCheck != null)
338              {
339                try
340                {
341                  healthCheck.ensureNewConnectionValid(c);
342                }
343                catch (LDAPException le2)
344                {
345                  c.close();
346                  throw le2;
347                }
348              }
349              return c;
350            }
351            catch (LDAPException le2)
352            {
353              debugException(le2);
354              lastException = le2;
355            }
356          }
357    
358          // If we've gotten here, then we've failed to connect to any of the
359          // servers, so propagate the last exception to the caller.
360          throw lastException;
361        }
362      }
363    
364    
365    
366      /**
367       * {@inheritDoc}
368       */
369      @Override()
370      public void toString(final StringBuilder buffer)
371      {
372        buffer.append("RoundRobinServerSet(servers={");
373    
374        for (int i=0; i < addresses.length; i++)
375        {
376          if (i > 0)
377          {
378            buffer.append(", ");
379          }
380    
381          buffer.append(addresses[i]);
382          buffer.append(':');
383          buffer.append(ports[i]);
384        }
385    
386        buffer.append("})");
387      }
388    }