001/*
002 * Copyright 2013-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2013-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2013-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.TreeMap;
047import java.util.concurrent.atomic.AtomicLong;
048import javax.net.SocketFactory;
049
050import com.unboundid.util.Debug;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.Nullable;
054import com.unboundid.util.ObjectPair;
055import com.unboundid.util.PropertyManager;
056import com.unboundid.util.StaticUtils;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060
061
062
063/**
064 * This class provides a server set implementation that will establish a
065 * connection to the server with the fewest established connections previously
066 * created by the same server set instance.  If there are multiple servers that
067 * share the fewest number of established connections, the first one in the list
068 * will be chosen.  If a server is unavailable when an attempt is made to
069 * establish a connection to it, then the connection will be established to the
070 * available server with the next fewest number of established connections.
071 * <BR><BR>
072 * This server set implementation has the ability to maintain a temporary
073 * blacklist of servers that have been recently found to be unavailable or
074 * unsuitable for use.  If an attempt to establish or authenticate a
075 * connection fails, if post-connect processing fails for that connection, or if
076 * health checking indicates that the connection is not suitable, then that
077 * server may be placed on the blacklist so that it will only be tried as a last
078 * resort after all non-blacklisted servers have been attempted.  The blacklist
079 * will be checked at regular intervals to determine whether a server should be
080 * re-instated to availability.
081 * <BR><BR>
082 * Note that this server set implementation is primarily intended for use with
083 * connection pools, but is also suitable for cases in which standalone
084 * connections are created as long as there will not be any attempt to close the
085 * connections when they are re-established.  It is not suitable for use in
086 * connections that may be re-established one or more times after being closed.
087 * <BR><BR>
088 * <H2>Example</H2>
089 * The following example demonstrates the process for creating a fewest
090 * connections server set that may be used to establish connections to either of
091 * two servers.
092 * <PRE>
093 * // Create arrays with the addresses and ports of the directory server
094 * // instances.
095 * String[] addresses =
096 * {
097 *   server1Address,
098 *   server2Address
099 * };
100 * int[] ports =
101 * {
102 *   server1Port,
103 *   server2Port
104 * };
105 *
106 * // Create the server set using the address and port arrays.
107 * FewestConnectionsServerSet fewestConnectionsSet =
108 *      new FewestConnectionsServerSet(addresses, ports);
109 *
110 * // Verify that we can establish a single connection using the server set.
111 * LDAPConnection connection = fewestConnectionsSet.getConnection();
112 * RootDSE rootDSEFromConnection = connection.getRootDSE();
113 * connection.close();
114 *
115 * // Verify that we can establish a connection pool using the server set.
116 * SimpleBindRequest bindRequest =
117 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
118 * LDAPConnectionPool pool =
119 *      new LDAPConnectionPool(fewestConnectionsSet, bindRequest, 10);
120 * RootDSE rootDSEFromPool = pool.getRootDSE();
121 * pool.close();
122 * </PRE>
123 */
124@NotMutable()
125@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
126public final class FewestConnectionsServerSet
127       extends ServerSet
128{
129  /**
130   * The name of a system property that can be used to override the default
131   * blacklist check interval, in milliseconds.
132   */
133  @NotNull static final String
134       PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS =
135            FewestConnectionsServerSet.class.getName() +
136                 ".defaultBlacklistCheckIntervalMillis";
137
138
139
140  // The bind request to use to authenticate connections created by this
141  // server set.
142  @Nullable private final BindRequest bindRequest;
143
144  // The set of connection options to use for new connections.
145  @NotNull private final LDAPConnectionOptions connectionOptions;
146
147  // A map with the number of connections currently established for each server.
148  @NotNull private final Map<ObjectPair<String,Integer>,AtomicLong>
149       connectionCountsByServer;
150
151  // The post-connect processor to invoke against connections created by this
152  // server set.
153  @Nullable private final PostConnectProcessor postConnectProcessor;
154
155  // The blacklist manager for this server set.
156  @Nullable private final ServerSetBlacklistManager blacklistManager;
157
158  // The socket factory to use to establish connections.
159  @NotNull private final SocketFactory socketFactory;
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 default
166   * socket factory provided by the JVM 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 of
175   *                    elements in the {@code addresses} array must correspond
176   *                    to the order of elements in the {@code ports} array.
177   */
178  public FewestConnectionsServerSet(@NotNull final String[] addresses,
179                                    @NotNull final int[] ports)
180  {
181    this(addresses, ports, null, null);
182  }
183
184
185
186  /**
187   * Creates a new fewest connections server set with the specified set of
188   * directory server addresses and port numbers.  It will use the default
189   * socket factory provided by the JVM to create the underlying sockets.
190   *
191   * @param  addresses          The addresses of the directory servers to which
192   *                            the connections should be established.  It must
193   *                            not be {@code null} or empty.
194   * @param  ports              The ports of the directory servers to which the
195   *                            connections should be established.  It must not
196   *                            be {@code null}, and it must have the same
197   *                            number of elements as the {@code addresses}
198   *                            array.  The order of elements in the
199   *                            {@code addresses} array must correspond to the
200   *                            order of elements in the {@code ports} array.
201   * @param  connectionOptions  The set of connection options to use for the
202   *                            underlying connections.
203   */
204  public FewestConnectionsServerSet(@NotNull final String[] addresses,
205              @NotNull final int[] ports,
206              @Nullable final LDAPConnectionOptions connectionOptions)
207  {
208    this(addresses, ports, null, connectionOptions);
209  }
210
211
212
213  /**
214   * Creates a new fewest connections server set with the specified set of
215   * directory server addresses and port numbers.  It will use the provided
216   * socket factory to create the underlying sockets.
217   *
218   * @param  addresses      The addresses of the directory servers to which the
219   *                        connections should be established.  It must not be
220   *                        {@code null} or empty.
221   * @param  ports          The ports of the directory servers to which the
222   *                        connections should be established.  It must not be
223   *                        {@code null}, and it must have the same number of
224   *                        elements as the {@code addresses} array.  The order
225   *                        of elements in the {@code addresses} array must
226   *                        correspond to the order of elements in the
227   *                        {@code ports} array.
228   * @param  socketFactory  The socket factory to use to create the underlying
229   *                        connections.
230   */
231  public FewestConnectionsServerSet(@NotNull final String[] addresses,
232                                    @NotNull final int[] ports,
233                                    @Nullable final SocketFactory socketFactory)
234  {
235    this(addresses, ports, socketFactory, null);
236  }
237
238
239
240  /**
241   * Creates a new fewest connections server set with the specified set of
242   * directory server addresses and port numbers.  It will use the provided
243   * socket factory to create the underlying sockets.
244   *
245   * @param  addresses          The addresses of the directory servers to which
246   *                            the connections should be established.  It must
247   *                            not be {@code null} or empty.
248   * @param  ports              The ports of the directory servers to which the
249   *                            connections should be established.  It must not
250   *                            be {@code null}, and it must have the same
251   *                            number of elements as the {@code addresses}
252   *                            array.  The order of elements in the
253   *                            {@code addresses} array must correspond to the
254   *                            order of elements in the {@code ports} array.
255   * @param  socketFactory      The socket factory to use to create the
256   *                            underlying connections.
257   * @param  connectionOptions  The set of connection options to use for the
258   *                            underlying connections.
259   */
260  public FewestConnectionsServerSet(@NotNull final String[] addresses,
261              @NotNull final int[] ports,
262              @Nullable final SocketFactory socketFactory,
263              @Nullable final LDAPConnectionOptions connectionOptions)
264  {
265    this(addresses, ports, socketFactory, connectionOptions, null, null);
266  }
267
268
269
270  /**
271   * Creates a new fewest connections server set with the specified set of
272   * directory server addresses and port numbers.  It will use the provided
273   * socket factory to create the underlying sockets.
274   *
275   * @param  addresses             The addresses of the directory servers to
276   *                               which the connections should be established.
277   *                               It must not be {@code null} or empty.
278   * @param  ports                 The ports of the directory servers to which
279   *                               the connections should be established.  It
280   *                               must not be {@code null}, and it must have
281   *                               the same number of elements as the
282   *                               {@code addresses} array.  The order of
283   *                               elements in the {@code addresses} array must
284   *                               correspond to the order of elements in the
285   *                               {@code ports} array.
286   * @param  socketFactory         The socket factory to use to create the
287   *                               underlying connections.
288   * @param  connectionOptions     The set of connection options to use for the
289   *                               underlying connections.
290   * @param  bindRequest           The bind request that should be used to
291   *                               authenticate newly established connections.
292   *                               It may be {@code null} if this server set
293   *                               should not perform any authentication.
294   * @param  postConnectProcessor  The post-connect processor that should be
295   *                               invoked on newly established connections.  It
296   *                               may be {@code null} if this server set should
297   *                               not perform any post-connect processing.
298   */
299  public FewestConnectionsServerSet(@NotNull final String[] addresses,
300              @NotNull final int[] ports,
301              @Nullable final SocketFactory socketFactory,
302              @Nullable final LDAPConnectionOptions connectionOptions,
303              @Nullable final BindRequest bindRequest,
304              @Nullable final PostConnectProcessor postConnectProcessor)
305  {
306    this(addresses, ports, socketFactory, connectionOptions, bindRequest,
307         postConnectProcessor, getDefaultBlacklistCheckIntervalMillis());
308  }
309
310
311
312  /**
313   * Creates a new fewest connections server set with the specified set of
314   * directory server addresses and port numbers.  It will use the provided
315   * socket factory to create the underlying sockets.
316   *
317   * @param  addresses                     The addresses of the directory
318   *                                       servers to which the connections
319   *                                       should be established.  It must not
320   *                                       be {@code null} or empty.
321   * @param  ports                         The ports of the directory servers to
322   *                                       which the connections should be
323   *                                       established.  It must not be
324   *                                       {@code null}, and it must have the
325   *                                       same number of elements as the
326   *                                       {@code addresses} array.  The order
327   *                                       of elements in the {@code addresses}
328   *                                       array must correspond to the order of
329   *                                       elements in the {@code ports} array.
330   * @param  socketFactory                 The socket factory to use to create
331   *                                       the underlying connections.
332   * @param  connectionOptions             The set of connection options to use
333   *                                       for the underlying connections.
334   * @param  bindRequest                   The bind request that should be used
335   *                                       to authenticate newly established
336   *                                       connections. It may be {@code null}
337   *                                       if this server set should not perform
338   *                                       any authentication.
339   * @param  postConnectProcessor          The post-connect processor that
340   *                                       should be invoked on newly
341   *                                       established connections.  It may be
342   *                                       {@code null} if this server set
343   *                                       should not perform any post-connect
344   *                                       processing.
345   * @param  blacklistCheckIntervalMillis  The length of time in milliseconds
346   *                                       between checks of servers on the
347   *                                       blacklist to determine whether they
348   *                                       are once again suitable for use.  A
349   *                                       value that is less than or equal to
350   *                                       zero indicates that no blacklist
351   *                                       should be maintained.
352   */
353  public FewestConnectionsServerSet(@NotNull final String[] addresses,
354              @NotNull final int[] ports,
355              @Nullable final SocketFactory socketFactory,
356              @Nullable final LDAPConnectionOptions connectionOptions,
357              @Nullable final BindRequest bindRequest,
358              @Nullable final PostConnectProcessor postConnectProcessor,
359              final long blacklistCheckIntervalMillis)
360  {
361    Validator.ensureNotNull(addresses, ports);
362    Validator.ensureTrue(addresses.length > 0,
363         "FewestConnectionsServerSet.addresses must not be empty.");
364    Validator.ensureTrue(addresses.length == ports.length,
365         "FewestConnectionsServerSet addresses and ports arrays must be " +
366              "the same size.");
367
368    final LinkedHashMap<ObjectPair<String,Integer>,AtomicLong> m =
369         new LinkedHashMap<>(StaticUtils.computeMapCapacity(ports.length));
370    for (int i=0; i < addresses.length; i++)
371    {
372      m.put(new ObjectPair<>(addresses[i], ports[i]), new AtomicLong(0L));
373    }
374
375    connectionCountsByServer = Collections.unmodifiableMap(m);
376
377    this.bindRequest = bindRequest;
378    this.postConnectProcessor = postConnectProcessor;
379
380    if (socketFactory == null)
381    {
382      this.socketFactory = SocketFactory.getDefault();
383    }
384    else
385    {
386      this.socketFactory = socketFactory;
387    }
388
389    if (connectionOptions == null)
390    {
391      this.connectionOptions = new LDAPConnectionOptions();
392    }
393    else
394    {
395      this.connectionOptions = connectionOptions;
396    }
397
398    if (blacklistCheckIntervalMillis > 0L)
399    {
400      blacklistManager = new ServerSetBlacklistManager(this, socketFactory,
401           connectionOptions, bindRequest, postConnectProcessor,
402           blacklistCheckIntervalMillis);
403    }
404    else
405    {
406      blacklistManager = null;
407    }
408  }
409
410
411
412  /**
413   * Retrieves the default blacklist check interval (in milliseconds that should
414   * be used if it is not specified.
415   *
416   * @return  The default blacklist check interval (in milliseconds that should
417   *          be used if it is not specified.
418   */
419  private static long getDefaultBlacklistCheckIntervalMillis()
420  {
421    return PropertyManager.getLong(
422         PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS, 30_000L);
423  }
424
425
426
427  /**
428   * Retrieves the addresses of the directory servers to which the connections
429   * should be established.
430   *
431   * @return  The addresses of the directory servers to which the connections
432   *          should be established.
433   */
434  @NotNull()
435  public String[] getAddresses()
436  {
437    int i = 0;
438    final String[] addresses = new String[connectionCountsByServer.size()];
439    for (final ObjectPair<String,Integer> hostPort :
440         connectionCountsByServer.keySet())
441    {
442      addresses[i++] = hostPort.getFirst();
443    }
444
445    return addresses;
446  }
447
448
449
450  /**
451   * Retrieves the ports of the directory servers to which the connections
452   * should be established.
453   *
454   * @return  The ports of the directory servers to which the connections should
455   *          be established.
456   */
457  @NotNull()
458  public int[] getPorts()
459  {
460    int i = 0;
461    final int[] ports = new int[connectionCountsByServer.size()];
462    for (final ObjectPair<String,Integer> hostPort :
463         connectionCountsByServer.keySet())
464    {
465      ports[i++] = hostPort.getSecond();
466    }
467
468    return ports;
469  }
470
471
472
473  /**
474   * Retrieves the socket factory that will be used to establish connections.
475   *
476   * @return  The socket factory that will be used to establish connections.
477   */
478  @NotNull()
479  public SocketFactory getSocketFactory()
480  {
481    return socketFactory;
482  }
483
484
485
486  /**
487   * Retrieves the set of connection options that will be used for underlying
488   * connections.
489   *
490   * @return  The set of connection options that will be used for underlying
491   *          connections.
492   */
493  @NotNull()
494  public LDAPConnectionOptions getConnectionOptions()
495  {
496    return connectionOptions;
497  }
498
499
500
501  /**
502   * {@inheritDoc}
503   */
504  @Override()
505  public boolean includesAuthentication()
506  {
507    return (bindRequest != null);
508  }
509
510
511
512  /**
513   * {@inheritDoc}
514   */
515  @Override()
516  public boolean includesPostConnectProcessing()
517  {
518    return (postConnectProcessor != null);
519  }
520
521
522
523  /**
524   * {@inheritDoc}
525   */
526  @Override()
527  @NotNull()
528  public LDAPConnection getConnection()
529         throws LDAPException
530  {
531    return getConnection(null);
532  }
533
534
535
536  /**
537   * {@inheritDoc}
538   */
539  @Override()
540  @NotNull()
541  public LDAPConnection getConnection(
542              @Nullable final LDAPConnectionPoolHealthCheck healthCheck)
543         throws LDAPException
544  {
545    // Organize the servers int lists by increasing numbers of connections.
546    final TreeMap<Long,List<ObjectPair<String,Integer>>> serversByCount =
547         new TreeMap<>();
548    for (final Map.Entry<ObjectPair<String,Integer>,AtomicLong> e :
549        connectionCountsByServer.entrySet())
550    {
551      final ObjectPair<String,Integer> hostPort = e.getKey();
552      final long count = e.getValue().get();
553
554      List<ObjectPair<String,Integer>> l = serversByCount.get(count);
555      if (l == null)
556      {
557        l = new ArrayList<>(connectionCountsByServer.size());
558        serversByCount.put(count, l);
559      }
560      l.add(hostPort);
561    }
562
563
564    // Try the servers in order of fewest connections to most.  If there are
565    // multiple servers with the same number of connections, then randomize the
566    // order of servers in that list to better spread the load across all of
567    // the servers.
568    LDAPException lastException = null;
569    List<ObjectPair<String,Integer>> blacklistedServers = null;
570    for (final List<ObjectPair<String,Integer>> l : serversByCount.values())
571    {
572      if (l.size() > 1)
573      {
574        Collections.shuffle(l);
575      }
576
577      for (final ObjectPair<String,Integer> hostPort : l)
578      {
579        if ((blacklistManager != null) &&
580             blacklistManager.isBlacklisted(hostPort))
581        {
582          if (blacklistedServers == null)
583          {
584            blacklistedServers =
585                 new ArrayList<>(connectionCountsByServer.size());
586          }
587          blacklistedServers.add(hostPort);
588          continue;
589        }
590
591        try
592        {
593          final LDAPConnection conn = new LDAPConnection(socketFactory,
594               connectionOptions, hostPort.getFirst(), hostPort.getSecond());
595          doBindPostConnectAndHealthCheckProcessing(conn, bindRequest,
596               postConnectProcessor, healthCheck);
597          connectionCountsByServer.get(hostPort).incrementAndGet();
598          associateConnectionWithThisServerSet(conn);
599          return conn;
600        }
601        catch (final LDAPException le)
602        {
603          Debug.debugException(le);
604          lastException = le;
605          if (blacklistManager != null)
606          {
607            blacklistManager.addToBlacklist(hostPort, healthCheck);
608          }
609        }
610      }
611    }
612
613
614    // If we've gotten here, then we couldn't get a connection from a
615    // non-blacklisted server.  If there were any blacklisted servers, then try
616    // them as a last resort.
617    if (blacklistedServers != null)
618    {
619      for (final ObjectPair<String,Integer> hostPort : blacklistedServers)
620      {
621        try
622        {
623          final LDAPConnection c = new LDAPConnection(socketFactory,
624               connectionOptions, hostPort.getFirst(), hostPort.getSecond());
625          doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
626               postConnectProcessor, healthCheck);
627          associateConnectionWithThisServerSet(c);
628          blacklistManager.removeFromBlacklist(hostPort);
629          return c;
630        }
631        catch (final LDAPException e)
632        {
633          Debug.debugException(e);
634          lastException = e;
635        }
636      }
637    }
638
639
640    // If we've gotten here, then we've tried all servers without any success,
641    // so throw the last exception that was encountered.
642    throw lastException;
643  }
644
645
646
647  /**
648   * {@inheritDoc}
649   */
650  @Override()
651  protected void handleConnectionClosed(
652                      @NotNull final LDAPConnection connection,
653                      @NotNull final String host, final int port,
654                      @NotNull final DisconnectType disconnectType,
655                      @Nullable final String message,
656                      @Nullable final Throwable cause)
657  {
658    final ObjectPair<String,Integer> hostPort = new ObjectPair<>(host, port);
659    final AtomicLong counter = connectionCountsByServer.get(hostPort);
660    if (counter != null)
661    {
662      final long remainingCount = counter.decrementAndGet();
663      if (remainingCount < 0L)
664      {
665        // This shouldn't happen.  If it does, reset it back to zero.
666        counter.compareAndSet(remainingCount, 0L);
667      }
668    }
669  }
670
671
672
673  /**
674   * Retrieves the blacklist manager for this server set.
675   *
676   * @return  The blacklist manager for this server set, or {@code null} if no
677   *          blacklist will be maintained.
678   */
679  @Nullable()
680  public ServerSetBlacklistManager getBlacklistManager()
681  {
682    return blacklistManager;
683  }
684
685
686
687  /**
688   * {@inheritDoc}
689   */
690  @Override()
691  public void shutDown()
692  {
693    if (blacklistManager != null)
694    {
695      blacklistManager.shutDown();
696    }
697  }
698
699
700
701  /**
702   * {@inheritDoc}
703   */
704  @Override()
705  public void toString(@NotNull final StringBuilder buffer)
706  {
707    buffer.append("FewestConnectionsServerSet(servers={");
708
709    final Iterator<Map.Entry<ObjectPair<String,Integer>,AtomicLong>>
710         cbsIterator = connectionCountsByServer.entrySet().iterator();
711    while (cbsIterator.hasNext())
712    {
713      final Map.Entry<ObjectPair<String,Integer>,AtomicLong> e =
714           cbsIterator.next();
715      final ObjectPair<String,Integer> hostPort = e.getKey();
716      final long count = e.getValue().get();
717
718      buffer.append('\'');
719      buffer.append(hostPort.getFirst());
720      buffer.append(':');
721      buffer.append(hostPort.getSecond());
722      buffer.append("':");
723      buffer.append(count);
724
725      if (cbsIterator.hasNext())
726      {
727        buffer.append(", ");
728      }
729    }
730
731    buffer.append("}, includesAuthentication=");
732    buffer.append(bindRequest != null);
733    buffer.append(", includesPostConnectProcessing=");
734    buffer.append(postConnectProcessor != null);
735    buffer.append(')');
736  }
737}