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