001    /*
002     * Copyright 2013-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2013-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 java.util.ArrayList;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.TreeMap;
029    import javax.net.SocketFactory;
030    
031    import com.unboundid.util.NotMutable;
032    import com.unboundid.util.ObjectPair;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.util.Debug.*;
037    import static com.unboundid.util.Validator.*;
038    
039    
040    
041    /**
042     * This class provides a server set implementation that will establish a
043     * connection to the server with the fewest established connections previously
044     * created by the same server set instance.  If there are multiple servers that
045     * share the fewest number of established connections, the first one in the list
046     * will be chosen.  If a server is unavailable when an attempt is made to
047     * establish a connection to it, then the connection will be established to the
048     * available server with the next fewest number of established connections.
049     * <BR><BR>
050     * Note that this server set implementation is primarily intended for use with
051     * connection pools, but is also suitable for cases in which standalone
052     * connections are created as long as there will not be any attempt to close the
053     * connections when they are re-established.  It is not suitable for use in
054     * connections that may be re-established one or more times after being closed.
055     * <BR><BR>
056     * <H2>Example</H2>
057     * The following example demonstrates the process for creating a fewest
058     * connections server set that may be used to establish connections to either of
059     * two servers.
060     * <PRE>
061     * // Create arrays with the addresses and ports of the directory server
062     * // instances.
063     * String[] addresses =
064     * {
065     *   server1Address,
066     *   server2Address
067     * };
068     * int[] ports =
069     * {
070     *   server1Port,
071     *   server2Port
072     * };
073     *
074     * // Create the server set using the address and port arrays.
075     * FewestConnectionsServerSet fewestConnectionsSet =
076     *      new FewestConnectionsServerSet(addresses, ports);
077     *
078     * // Verify that we can establish a single connection using the server set.
079     * LDAPConnection connection = fewestConnectionsSet.getConnection();
080     * RootDSE rootDSEFromConnection = connection.getRootDSE();
081     * connection.close();
082     *
083     * // Verify that we can establish a connection pool using the server set.
084     * SimpleBindRequest bindRequest =
085     *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
086     * LDAPConnectionPool pool =
087     *      new LDAPConnectionPool(fewestConnectionsSet, bindRequest, 10);
088     * RootDSE rootDSEFromPool = pool.getRootDSE();
089     * pool.close();
090     * </PRE>
091     */
092    @NotMutable()
093    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
094    public final class FewestConnectionsServerSet
095           extends ServerSet
096    {
097      // The port numbers of the target servers.
098      private final int[] ports;
099    
100      // The set of connection options to use for new connections.
101      private final LDAPConnectionOptions connectionOptions;
102    
103      // A list of the potentially-established connections created by this server
104      // set.
105      private final List<LDAPConnection> establishedConnections;
106    
107      // The socket factory to use to establish connections.
108      private final SocketFactory socketFactory;
109    
110      // The addresses of the target servers.
111      private final String[] addresses;
112    
113    
114    
115      /**
116       * Creates a new fewest connections server set with the specified set of
117       * directory server addresses and port numbers.  It will use the default
118       * socket factory provided by the JVM to create the underlying sockets.
119       *
120       * @param  addresses  The addresses of the directory servers to which the
121       *                    connections should be established.  It must not be
122       *                    {@code null} or empty.
123       * @param  ports      The ports of the directory servers to which the
124       *                    connections should be established.  It must not be
125       *                    {@code null}, and it must have the same number of
126       *                    elements as the {@code addresses} array.  The order of
127       *                    elements in the {@code addresses} array must correspond
128       *                    to the order of elements in the {@code ports} array.
129       */
130      public FewestConnectionsServerSet(final String[] addresses, final int[] ports)
131      {
132        this(addresses, ports, null, null);
133      }
134    
135    
136    
137      /**
138       * Creates a new fewest connections server set with the specified set of
139       * directory server addresses and port numbers.  It will use the default
140       * socket factory provided by the JVM to create the underlying sockets.
141       *
142       * @param  addresses          The addresses of the directory servers to which
143       *                            the connections should be established.  It must
144       *                            not be {@code null} or empty.
145       * @param  ports              The ports of the directory servers to which the
146       *                            connections should be established.  It must not
147       *                            be {@code null}, and it must have the same
148       *                            number of elements as the {@code addresses}
149       *                            array.  The order of elements in the
150       *                            {@code addresses} array must correspond to the
151       *                            order of elements in the {@code ports} array.
152       * @param  connectionOptions  The set of connection options to use for the
153       *                            underlying connections.
154       */
155      public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
156                  final LDAPConnectionOptions connectionOptions)
157      {
158        this(addresses, ports, null, connectionOptions);
159      }
160    
161    
162    
163      /**
164       * Creates a new fewest connections server set with the specified set of
165       * directory server addresses and port numbers.  It will use the provided
166       * socket factory to create the underlying sockets.
167       *
168       * @param  addresses      The addresses of the directory servers to which the
169       *                        connections should be established.  It must not be
170       *                        {@code null} or empty.
171       * @param  ports          The ports of the directory servers to which the
172       *                        connections should be established.  It must not be
173       *                        {@code null}, and it must have the same number of
174       *                        elements as the {@code addresses} array.  The order
175       *                        of elements in the {@code addresses} array must
176       *                        correspond to the order of elements in the
177       *                        {@code ports} array.
178       * @param  socketFactory  The socket factory to use to create the underlying
179       *                        connections.
180       */
181      public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
182                                        final SocketFactory socketFactory)
183      {
184        this(addresses, ports, socketFactory, null);
185      }
186    
187    
188    
189      /**
190       * Creates a new fewest connections server set with the specified set of
191       * directory server addresses and port numbers.  It will use the provided
192       * socket factory to create the underlying sockets.
193       *
194       * @param  addresses          The addresses of the directory servers to which
195       *                            the connections should be established.  It must
196       *                            not be {@code null} or empty.
197       * @param  ports              The ports of the directory servers to which the
198       *                            connections should be established.  It must not
199       *                            be {@code null}, and it must have the same
200       *                            number of elements as the {@code addresses}
201       *                            array.  The order of elements in the
202       *                            {@code addresses} array must correspond to the
203       *                            order of elements in the {@code ports} array.
204       * @param  socketFactory      The socket factory to use to create the
205       *                            underlying connections.
206       * @param  connectionOptions  The set of connection options to use for the
207       *                            underlying connections.
208       */
209      public FewestConnectionsServerSet(final String[] addresses, final int[] ports,
210                  final SocketFactory socketFactory,
211                  final LDAPConnectionOptions connectionOptions)
212      {
213        ensureNotNull(addresses, ports);
214        ensureTrue(addresses.length > 0,
215                   "FewestConnectionsServerSet.addresses must not be empty.");
216        ensureTrue(addresses.length == ports.length,
217                   "FewestConnectionsServerSet addresses and ports arrays must " +
218                        "be the same size.");
219    
220        this.addresses = addresses;
221        this.ports     = ports;
222    
223        establishedConnections = new ArrayList<LDAPConnection>(100);
224    
225        if (socketFactory == null)
226        {
227          this.socketFactory = SocketFactory.getDefault();
228        }
229        else
230        {
231          this.socketFactory = socketFactory;
232        }
233    
234        if (connectionOptions == null)
235        {
236          this.connectionOptions = new LDAPConnectionOptions();
237        }
238        else
239        {
240          this.connectionOptions = connectionOptions;
241        }
242      }
243    
244    
245    
246      /**
247       * Retrieves the addresses of the directory servers to which the connections
248       * should be established.
249       *
250       * @return  The addresses of the directory servers to which the connections
251       *          should be established.
252       */
253      public String[] getAddresses()
254      {
255        return addresses;
256      }
257    
258    
259    
260      /**
261       * Retrieves the ports of the directory servers to which the connections
262       * should be established.
263       *
264       * @return  The ports of the directory servers to which the connections should
265       *          be established.
266       */
267      public int[] getPorts()
268      {
269        return ports;
270      }
271    
272    
273    
274      /**
275       * Retrieves the socket factory that will be used to establish connections.
276       *
277       * @return  The socket factory that will be used to establish connections.
278       */
279      public SocketFactory getSocketFactory()
280      {
281        return socketFactory;
282      }
283    
284    
285    
286      /**
287       * Retrieves the set of connection options that will be used for underlying
288       * connections.
289       *
290       * @return  The set of connection options that will be used for underlying
291       *          connections.
292       */
293      public LDAPConnectionOptions getConnectionOptions()
294      {
295        return connectionOptions;
296      }
297    
298    
299    
300      /**
301       * {@inheritDoc}
302       */
303      @Override()
304      public LDAPConnection getConnection()
305             throws LDAPException
306      {
307        return getConnection(null);
308      }
309    
310    
311    
312      /**
313       * {@inheritDoc}
314       */
315      @Override()
316      public synchronized LDAPConnection getConnection(
317                               final LDAPConnectionPoolHealthCheck healthCheck)
318             throws LDAPException
319      {
320        // Count the number of connections established to each server.
321        final int[] counts = new int[addresses.length];
322        final Iterator<LDAPConnection> iterator = establishedConnections.iterator();
323        while (iterator.hasNext())
324        {
325          final LDAPConnection conn = iterator.next();
326          if (! conn.isConnected())
327          {
328            iterator.remove();
329            continue;
330          }
331    
332          int slot = -1;
333          for (int i=0; i < addresses.length; i++)
334          {
335            if (addresses[i].equals(conn.getConnectedAddress()) &&
336                (ports[i] == conn.getConnectedPort()))
337            {
338              slot = i;
339              break;
340            }
341          }
342    
343          if (slot < 0)
344          {
345            // This indicates a connection is established to some address:port that
346            // we don't expect.  This shouldn't happen under normal circumstances.
347            iterator.remove();
348            break;
349          }
350          else
351          {
352            counts[slot]++;
353          }
354        }
355    
356    
357        // Sort the servers based on the number of established connections.
358        final TreeMap<Integer,List<ObjectPair<String,Integer>>> m =
359             new TreeMap<Integer,List<ObjectPair<String,Integer>>>();
360        for (int i=0; i < counts.length; i++)
361        {
362          final Integer count = counts[i];
363          List<ObjectPair<String,Integer>> serverList = m.get(count);
364          if (serverList == null)
365          {
366            serverList = new ArrayList<ObjectPair<String,Integer>>(counts.length);
367            m.put(count, serverList);
368          }
369          serverList.add(new ObjectPair<String,Integer>(addresses[i], ports[i]));
370        }
371    
372    
373        // Iterate through the sorted elements, trying each server in sequence until
374        // we are able to successfully establish a connection.
375        LDAPException lastException = null;
376        for (final List<ObjectPair<String,Integer>> l : m.values())
377        {
378          for (final ObjectPair<String,Integer> p : l)
379          {
380            try
381            {
382              final LDAPConnection conn = new LDAPConnection(socketFactory,
383                   connectionOptions, p.getFirst(), p.getSecond());
384              if (healthCheck != null)
385              {
386                try
387                {
388                  healthCheck.ensureNewConnectionValid(conn);
389                }
390                catch (final LDAPException le)
391                {
392                  debugException(le);
393                  conn.close();
394                  throw le;
395                }
396              }
397    
398              establishedConnections.add(conn);
399              return conn;
400            }
401            catch (final LDAPException le)
402            {
403              debugException(le);
404              lastException = le;
405            }
406          }
407        }
408    
409    
410        // If we've gotten here, then we've tried all servers without any success,
411        // so throw the last exception that was encountered.
412        throw lastException;
413      }
414    
415    
416    
417      /**
418       * {@inheritDoc}
419       */
420      @Override()
421      public void toString(final StringBuilder buffer)
422      {
423        buffer.append("FewestConnectionsServerSet(servers={");
424    
425        for (int i=0; i < addresses.length; i++)
426        {
427          if (i > 0)
428          {
429            buffer.append(", ");
430          }
431    
432          buffer.append(addresses[i]);
433          buffer.append(':');
434          buffer.append(ports[i]);
435        }
436    
437        buffer.append("})");
438      }
439    }