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