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