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