001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     * // 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)
085    public 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    }