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