001/*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import javax.net.SocketFactory;
026
027import com.unboundid.util.NotMutable;
028import com.unboundid.util.ThreadSafety;
029import com.unboundid.util.ThreadSafetyLevel;
030
031import static com.unboundid.util.Debug.*;
032import 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 * // Create arrays with the addresses and ports of the directory server
053 * // instances.
054 * String[] addresses =
055 * {
056 *   server1Address,
057 *   server2Address
058 * };
059 * int[] ports =
060 * {
061 *   server1Port,
062 *   server2Port
063 * };
064 *
065 * // Create the server set using the address and port arrays.
066 * RoundRobinServerSet roundRobinSet =
067 *      new RoundRobinServerSet(addresses, ports);
068 *
069 * // Verify that we can establish a single connection using the server set.
070 * LDAPConnection connection = roundRobinSet.getConnection();
071 * RootDSE rootDSEFromConnection = connection.getRootDSE();
072 * connection.close();
073 *
074 * // Verify that we can establish a connection pool using the server set.
075 * SimpleBindRequest bindRequest =
076 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
077 * LDAPConnectionPool pool =
078 *      new LDAPConnectionPool(roundRobinSet, bindRequest, 10);
079 * RootDSE rootDSEFromPool = pool.getRootDSE();
080 * pool.close();
081 * </PRE>
082 */
083@NotMutable()
084@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
085public final class RoundRobinServerSet
086       extends ServerSet
087{
088  // The port numbers of the target servers.
089  private final int[] ports;
090
091  // The set of connection options to use for new connections.
092  private final LDAPConnectionOptions connectionOptions;
093
094  // The socket factory to use to establish connections.
095  private final SocketFactory socketFactory;
096
097  // The addresses of the target servers.
098  private final String[] addresses;
099
100  // The slot to use for the server to be selected for the next connection
101  // attempt.
102  private int nextSlot;
103
104
105
106  /**
107   * Creates a new round robin server set with the specified set of directory
108   * server addresses and port numbers.  It will use the default socket factory
109   * provided by the JVM to create the underlying sockets.
110   *
111   * @param  addresses  The addresses of the directory servers to which the
112   *                    connections should be established.  It must not be
113   *                    {@code null} or empty.
114   * @param  ports      The ports of the directory servers to which the
115   *                    connections should be established.  It must not be
116   *                    {@code null}, and it must have the same number of
117   *                    elements as the {@code addresses} array.  The order of
118   *                    elements in the {@code addresses} array must correspond
119   *                    to the order of elements in the {@code ports} array.
120   */
121  public RoundRobinServerSet(final String[] addresses, final int[] ports)
122  {
123    this(addresses, ports, null, null);
124  }
125
126
127
128  /**
129   * Creates a new round robin server set with the specified set of directory
130   * server addresses and port numbers.  It will use the default socket factory
131   * provided by the JVM to create the underlying sockets.
132   *
133   * @param  addresses          The addresses of the directory servers to which
134   *                            the connections should be established.  It must
135   *                            not be {@code null} or empty.
136   * @param  ports              The ports of the directory servers to which the
137   *                            connections should be established.  It must not
138   *                            be {@code null}, and it must have the same
139   *                            number of elements as the {@code addresses}
140   *                            array.  The order of elements in the
141   *                            {@code addresses} array must correspond to the
142   *                            order of elements in the {@code ports} array.
143   * @param  connectionOptions  The set of connection options to use for the
144   *                            underlying connections.
145   */
146  public RoundRobinServerSet(final String[] addresses, final int[] ports,
147                             final LDAPConnectionOptions connectionOptions)
148  {
149    this(addresses, ports, null, connectionOptions);
150  }
151
152
153
154  /**
155   * Creates a new round robin server set with the specified set of directory
156   * server addresses and port numbers.  It will use the provided socket factory
157   * 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
166   *                        of elements in the {@code addresses} array must
167   *                        correspond to the order of elements in the
168   *                        {@code ports} array.
169   * @param  socketFactory  The socket factory to use to create the underlying
170   *                        connections.
171   */
172  public RoundRobinServerSet(final String[] addresses, final int[] ports,
173                             final SocketFactory socketFactory)
174  {
175    this(addresses, ports, socketFactory, null);
176  }
177
178
179
180  /**
181   * Creates a new round robin server set with the specified set of directory
182   * server addresses and port numbers.  It will use the provided socket factory
183   * to create the underlying sockets.
184   *
185   * @param  addresses          The addresses of the directory servers to which
186   *                            the connections should be established.  It must
187   *                            not be {@code null} or empty.
188   * @param  ports              The ports of the directory servers to which the
189   *                            connections should be established.  It must not
190   *                            be {@code null}, and it must have the same
191   *                            number of elements as the {@code addresses}
192   *                            array.  The order of elements in the
193   *                            {@code addresses} array must correspond to the
194   *                            order of elements in the {@code ports} array.
195   * @param  socketFactory      The socket factory to use to create the
196   *                            underlying connections.
197   * @param  connectionOptions  The set of connection options to use for the
198   *                            underlying connections.
199   */
200  public RoundRobinServerSet(final String[] addresses, final int[] ports,
201                             final SocketFactory socketFactory,
202                             final LDAPConnectionOptions connectionOptions)
203  {
204    ensureNotNull(addresses, ports);
205    ensureTrue(addresses.length > 0,
206               "RoundRobinServerSet.addresses must not be empty.");
207    ensureTrue(addresses.length == ports.length,
208               "RoundRobinServerSet addresses and ports arrays must be the " +
209                    "same size.");
210
211    this.addresses = addresses;
212    this.ports     = ports;
213
214    if (socketFactory == null)
215    {
216      this.socketFactory = SocketFactory.getDefault();
217    }
218    else
219    {
220      this.socketFactory = socketFactory;
221    }
222
223    if (connectionOptions == null)
224    {
225      this.connectionOptions = new LDAPConnectionOptions();
226    }
227    else
228    {
229      this.connectionOptions = connectionOptions;
230    }
231
232    nextSlot = 0;
233  }
234
235
236
237  /**
238   * Retrieves the addresses of the directory servers to which the connections
239   * should be established.
240   *
241   * @return  The addresses of the directory servers to which the connections
242   *          should be established.
243   */
244  public String[] getAddresses()
245  {
246    return addresses;
247  }
248
249
250
251  /**
252   * Retrieves the ports of the directory servers to which the connections
253   * should be established.
254   *
255   * @return  The ports of the directory servers to which the connections should
256   *          be established.
257   */
258  public int[] getPorts()
259  {
260    return ports;
261  }
262
263
264
265  /**
266   * Retrieves the socket factory that will be used to establish connections.
267   *
268   * @return  The socket factory that will be used to establish connections.
269   */
270  public SocketFactory getSocketFactory()
271  {
272    return socketFactory;
273  }
274
275
276
277  /**
278   * Retrieves the set of connection options that will be used for underlying
279   * connections.
280   *
281   * @return  The set of connection options that will be used for underlying
282   *          connections.
283   */
284  public LDAPConnectionOptions getConnectionOptions()
285  {
286    return connectionOptions;
287  }
288
289
290
291  /**
292   * {@inheritDoc}
293   */
294  @Override()
295  public LDAPConnection getConnection()
296         throws LDAPException
297  {
298    return getConnection(null);
299  }
300
301
302
303  /**
304   * {@inheritDoc}
305   */
306  @Override()
307  public synchronized LDAPConnection getConnection(
308                           final LDAPConnectionPoolHealthCheck healthCheck)
309         throws LDAPException
310  {
311    final int initialSlotNumber = nextSlot++;
312
313    if (nextSlot >= addresses.length)
314    {
315      nextSlot = 0;
316    }
317
318    try
319    {
320      final LDAPConnection c = new LDAPConnection(socketFactory,
321           connectionOptions, addresses[initialSlotNumber],
322           ports[initialSlotNumber]);
323      if (healthCheck != null)
324      {
325        try
326        {
327          healthCheck.ensureNewConnectionValid(c);
328        }
329        catch (LDAPException le)
330        {
331          c.close();
332          throw le;
333        }
334      }
335      return c;
336    }
337    catch (LDAPException le)
338    {
339      debugException(le);
340      LDAPException lastException = le;
341
342      while (nextSlot != initialSlotNumber)
343      {
344        final int slotNumber = nextSlot++;
345        if (nextSlot >= addresses.length)
346        {
347          nextSlot = 0;
348        }
349
350        try
351        {
352          final LDAPConnection c = new LDAPConnection(socketFactory,
353               connectionOptions, addresses[slotNumber], ports[slotNumber]);
354          if (healthCheck != null)
355          {
356            try
357            {
358              healthCheck.ensureNewConnectionValid(c);
359            }
360            catch (LDAPException le2)
361            {
362              c.close();
363              throw le2;
364            }
365          }
366          return c;
367        }
368        catch (LDAPException le2)
369        {
370          debugException(le2);
371          lastException = le2;
372        }
373      }
374
375      // If we've gotten here, then we've failed to connect to any of the
376      // servers, so propagate the last exception to the caller.
377      throw lastException;
378    }
379  }
380
381
382
383  /**
384   * {@inheritDoc}
385   */
386  @Override()
387  public void toString(final StringBuilder buffer)
388  {
389    buffer.append("RoundRobinServerSet(servers={");
390
391    for (int i=0; i < addresses.length; i++)
392    {
393      if (i > 0)
394      {
395        buffer.append(", ");
396      }
397
398      buffer.append(addresses[i]);
399      buffer.append(':');
400      buffer.append(ports[i]);
401    }
402
403    buffer.append("})");
404  }
405}