001/*
002 * Copyright 2013-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2013-2017 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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.List;
028import java.util.TreeMap;
029import javax.net.SocketFactory;
030
031import com.unboundid.util.NotMutable;
032import com.unboundid.util.ObjectPair;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035
036import static com.unboundid.util.Debug.*;
037import 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)
094public 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}