001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
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 bind request to use to authenticate connections created by this
089  // server set.
090  private final BindRequest bindRequest;
091
092  // The port numbers of the target servers.
093  private final int[] ports;
094
095  // The set of connection options to use for new connections.
096  private final LDAPConnectionOptions connectionOptions;
097
098  // The post-connect processor to invoke against connections created by this
099  // server set.
100  private final PostConnectProcessor postConnectProcessor;
101
102  // The socket factory to use to establish connections.
103  private final SocketFactory socketFactory;
104
105  // The addresses of the target servers.
106  private final String[] addresses;
107
108  // The slot to use for the server to be selected for the next connection
109  // attempt.
110  private int nextSlot;
111
112
113
114  /**
115   * Creates a new round robin server set with the specified set of directory
116   * server addresses and port numbers.  It will use the default socket factory
117   * provided by the JVM to create the underlying sockets.
118   *
119   * @param  addresses  The addresses of the directory servers to which the
120   *                    connections should be established.  It must not be
121   *                    {@code null} or empty.
122   * @param  ports      The ports of the directory servers to which the
123   *                    connections should be established.  It must not be
124   *                    {@code null}, and it must have the same number of
125   *                    elements as the {@code addresses} array.  The order of
126   *                    elements in the {@code addresses} array must correspond
127   *                    to the order of elements in the {@code ports} array.
128   */
129  public RoundRobinServerSet(final String[] addresses, final int[] ports)
130  {
131    this(addresses, ports, null, null);
132  }
133
134
135
136  /**
137   * Creates a new round robin server set with the specified set of directory
138   * server addresses and port numbers.  It will use the default socket factory
139   * provided by the JVM to create the underlying sockets.
140   *
141   * @param  addresses          The addresses of the directory servers to which
142   *                            the connections should be established.  It must
143   *                            not be {@code null} or empty.
144   * @param  ports              The ports of the directory servers to which the
145   *                            connections should be established.  It must not
146   *                            be {@code null}, and it must have the same
147   *                            number of elements as the {@code addresses}
148   *                            array.  The order of elements in the
149   *                            {@code addresses} array must correspond to the
150   *                            order of elements in the {@code ports} array.
151   * @param  connectionOptions  The set of connection options to use for the
152   *                            underlying connections.
153   */
154  public RoundRobinServerSet(final String[] addresses, final int[] ports,
155                             final LDAPConnectionOptions connectionOptions)
156  {
157    this(addresses, ports, null, connectionOptions);
158  }
159
160
161
162  /**
163   * Creates a new round robin server set with the specified set of directory
164   * server addresses and port numbers.  It will use the provided socket factory
165   * to create the underlying sockets.
166   *
167   * @param  addresses      The addresses of the directory servers to which the
168   *                        connections should be established.  It must not be
169   *                        {@code null} or empty.
170   * @param  ports          The ports of the directory servers to which the
171   *                        connections should be established.  It must not be
172   *                        {@code null}, and it must have the same number of
173   *                        elements as the {@code addresses} array.  The order
174   *                        of elements in the {@code addresses} array must
175   *                        correspond to the order of elements in the
176   *                        {@code ports} array.
177   * @param  socketFactory  The socket factory to use to create the underlying
178   *                        connections.
179   */
180  public RoundRobinServerSet(final String[] addresses, final int[] ports,
181                             final SocketFactory socketFactory)
182  {
183    this(addresses, ports, socketFactory, null);
184  }
185
186
187
188  /**
189   * Creates a new round robin server set with the specified set of directory
190   * server addresses and port numbers.  It will use the provided socket factory
191   * to create the underlying sockets.
192   *
193   * @param  addresses          The addresses of the directory servers to which
194   *                            the connections should be established.  It must
195   *                            not be {@code null} or empty.
196   * @param  ports              The ports of the directory servers to which the
197   *                            connections should be established.  It must not
198   *                            be {@code null}, and it must have the same
199   *                            number of elements as the {@code addresses}
200   *                            array.  The order of elements in the
201   *                            {@code addresses} array must correspond to the
202   *                            order of elements in the {@code ports} array.
203   * @param  socketFactory      The socket factory to use to create the
204   *                            underlying connections.
205   * @param  connectionOptions  The set of connection options to use for the
206   *                            underlying connections.
207   */
208  public RoundRobinServerSet(final String[] addresses, final int[] ports,
209                             final SocketFactory socketFactory,
210                             final LDAPConnectionOptions connectionOptions)
211  {
212    this(addresses, ports, socketFactory, connectionOptions, null, null);
213  }
214
215
216
217  /**
218   * Creates a new round robin server set with the specified set of directory
219   * server addresses and port numbers.  It will use the provided socket factory
220   * to create the underlying sockets.
221   *
222   * @param  addresses             The addresses of the directory servers to
223   *                               which the connections should be established.
224   *                               It must not be {@code null} or empty.
225   * @param  ports                 The ports of the directory servers to which
226   *                               the connections should be established.  It
227   *                               must not be {@code null}, and it must have
228   *                               the same number of elements as the
229   *                               {@code addresses} array.  The order of
230   *                               elements in the {@code addresses} array must
231   *                               correspond to the order of elements in the
232   *                               {@code ports} array.
233   * @param  socketFactory         The socket factory to use to create the
234   *                               underlying connections.
235   * @param  connectionOptions     The set of connection options to use for the
236   *                               underlying connections.
237   * @param  bindRequest           The bind request that should be used to
238   *                               authenticate newly-established connections.
239   *                               It may be {@code null} if this server set
240   *                               should not perform any authentication.
241   * @param  postConnectProcessor  The post-connect processor that should be
242   *                               invoked on newly-established connections.  It
243   *                               may be {@code null} if this server set should
244   *                               not perform any post-connect processing.
245   */
246  public RoundRobinServerSet(final String[] addresses, final int[] ports,
247                             final SocketFactory socketFactory,
248                             final LDAPConnectionOptions connectionOptions,
249                             final BindRequest bindRequest,
250                             final PostConnectProcessor postConnectProcessor)
251  {
252    ensureNotNull(addresses, ports);
253    ensureTrue(addresses.length > 0,
254               "RoundRobinServerSet.addresses must not be empty.");
255    ensureTrue(addresses.length == ports.length,
256               "RoundRobinServerSet addresses and ports arrays must be the " +
257                    "same size.");
258
259    this.addresses = addresses;
260    this.ports = ports;
261    this.bindRequest = bindRequest;
262    this.postConnectProcessor = postConnectProcessor;
263
264    if (socketFactory == null)
265    {
266      this.socketFactory = SocketFactory.getDefault();
267    }
268    else
269    {
270      this.socketFactory = socketFactory;
271    }
272
273    if (connectionOptions == null)
274    {
275      this.connectionOptions = new LDAPConnectionOptions();
276    }
277    else
278    {
279      this.connectionOptions = connectionOptions;
280    }
281
282    nextSlot = 0;
283  }
284
285
286
287  /**
288   * Retrieves the addresses of the directory servers to which the connections
289   * should be established.
290   *
291   * @return  The addresses of the directory servers to which the connections
292   *          should be established.
293   */
294  public String[] getAddresses()
295  {
296    return addresses;
297  }
298
299
300
301  /**
302   * Retrieves the ports of the directory servers to which the connections
303   * should be established.
304   *
305   * @return  The ports of the directory servers to which the connections should
306   *          be established.
307   */
308  public int[] getPorts()
309  {
310    return ports;
311  }
312
313
314
315  /**
316   * Retrieves the socket factory that will be used to establish connections.
317   *
318   * @return  The socket factory that will be used to establish connections.
319   */
320  public SocketFactory getSocketFactory()
321  {
322    return socketFactory;
323  }
324
325
326
327  /**
328   * Retrieves the set of connection options that will be used for underlying
329   * connections.
330   *
331   * @return  The set of connection options that will be used for underlying
332   *          connections.
333   */
334  public LDAPConnectionOptions getConnectionOptions()
335  {
336    return connectionOptions;
337  }
338
339
340
341  /**
342   * {@inheritDoc}
343   */
344  @Override()
345  public boolean includesAuthentication()
346  {
347    return (bindRequest != null);
348  }
349
350
351
352  /**
353   * {@inheritDoc}
354   */
355  @Override()
356  public boolean includesPostConnectProcessing()
357  {
358    return (postConnectProcessor != null);
359  }
360
361
362
363  /**
364   * {@inheritDoc}
365   */
366  @Override()
367  public LDAPConnection getConnection()
368         throws LDAPException
369  {
370    return getConnection(null);
371  }
372
373
374
375  /**
376   * {@inheritDoc}
377   */
378  @Override()
379  public synchronized LDAPConnection getConnection(
380                           final LDAPConnectionPoolHealthCheck healthCheck)
381         throws LDAPException
382  {
383    final int initialSlotNumber = nextSlot++;
384
385    if (nextSlot >= addresses.length)
386    {
387      nextSlot = 0;
388    }
389
390    try
391    {
392      final LDAPConnection c = new LDAPConnection(socketFactory,
393           connectionOptions, addresses[initialSlotNumber],
394           ports[initialSlotNumber]);
395      doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
396           postConnectProcessor, healthCheck);
397      return c;
398    }
399    catch (final LDAPException le)
400    {
401      debugException(le);
402      LDAPException lastException = le;
403
404      while (nextSlot != initialSlotNumber)
405      {
406        final int slotNumber = nextSlot++;
407        if (nextSlot >= addresses.length)
408        {
409          nextSlot = 0;
410        }
411
412        try
413        {
414          final LDAPConnection c = new LDAPConnection(socketFactory,
415               connectionOptions, addresses[slotNumber], ports[slotNumber]);
416          doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
417               postConnectProcessor, healthCheck);
418          return c;
419        }
420        catch (final LDAPException le2)
421        {
422          debugException(le2);
423          lastException = le2;
424        }
425      }
426
427      // If we've gotten here, then we've failed to connect to any of the
428      // servers, so propagate the last exception to the caller.
429      throw lastException;
430    }
431  }
432
433
434
435  /**
436   * {@inheritDoc}
437   */
438  @Override()
439  public void toString(final StringBuilder buffer)
440  {
441    buffer.append("RoundRobinServerSet(servers={");
442
443    for (int i=0; i < addresses.length; i++)
444    {
445      if (i > 0)
446      {
447        buffer.append(", ");
448      }
449
450      buffer.append(addresses[i]);
451      buffer.append(':');
452      buffer.append(ports[i]);
453    }
454
455    buffer.append("}, includesAuthentication=");
456    buffer.append(bindRequest != null);
457    buffer.append(", includesPostConnectProcessing=");
458    buffer.append(postConnectProcessor != null);
459    buffer.append(')');
460  }
461}