001/*
002 * Copyright 2008-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2020 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) 2008-2020 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.List;
042import java.util.concurrent.atomic.AtomicLong;
043import javax.net.SocketFactory;
044
045import com.unboundid.util.Debug;
046import com.unboundid.util.ObjectPair;
047import com.unboundid.util.NotMutable;
048import com.unboundid.util.StaticUtils;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.Validator;
052
053
054
055/**
056 * This class provides a server set implementation that will use a round-robin
057 * algorithm to select the server to which the connection should be established.
058 * Any number of servers may be included in this server set, and each request
059 * will attempt to retrieve a connection to the next server in the list,
060 * circling back to the beginning of the list as necessary.  If a server is
061 * unavailable when an attempt is made to establish a connection to it, then
062 * the connection will be established to the next available server in the set.
063 * <BR><BR>
064 * This server set implementation has the ability to maintain a temporary
065 * blacklist of servers that have been recently found to be unavailable or
066 * unsuitable for use.  If an attempt to establish or authenticate a
067 * connection fails, if post-connect processing fails for that connection, or if
068 * health checking indicates that the connection is not suitable, then that
069 * server may be placed on the blacklist so that it will only be tried as a last
070 * resort after all non-blacklisted servers have been attempted.  The blacklist
071 * will be checked at regular intervals to determine whether a server should be
072 * re-instated to availability.
073 * <BR><BR>
074 * <H2>Example</H2>
075 * The following example demonstrates the process for creating a round-robin
076 * server set that may be used to establish connections to either of two
077 * servers.  When using the server set to attempt to create a connection, it
078 * will first try one of the servers, but will fail over to the other if the
079 * first one attempted is not available:
080 * <PRE>
081 * // Create arrays with the addresses and ports of the directory server
082 * // instances.
083 * String[] addresses =
084 * {
085 *   server1Address,
086 *   server2Address
087 * };
088 * int[] ports =
089 * {
090 *   server1Port,
091 *   server2Port
092 * };
093 *
094 * // Create the server set using the address and port arrays.
095 * RoundRobinServerSet roundRobinSet =
096 *      new RoundRobinServerSet(addresses, ports);
097 *
098 * // Verify that we can establish a single connection using the server set.
099 * LDAPConnection connection = roundRobinSet.getConnection();
100 * RootDSE rootDSEFromConnection = connection.getRootDSE();
101 * connection.close();
102 *
103 * // Verify that we can establish a connection pool using the server set.
104 * SimpleBindRequest bindRequest =
105 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
106 * LDAPConnectionPool pool =
107 *      new LDAPConnectionPool(roundRobinSet, bindRequest, 10);
108 * RootDSE rootDSEFromPool = pool.getRootDSE();
109 * pool.close();
110 * </PRE>
111 */
112@NotMutable()
113@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
114public final class RoundRobinServerSet
115       extends ServerSet
116{
117  /**
118   * The name of a system property that can be used to override the default
119   * blacklist check interval, in milliseconds.
120   */
121  static final String PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS =
122       RoundRobinServerSet.class.getName() +
123            ".defaultBlacklistCheckIntervalMillis";
124
125
126
127  // A counter used to determine the next slot that should be used.
128  private final AtomicLong nextSlot;
129
130  // The bind request to use to authenticate connections created by this
131  // server set.
132  private final BindRequest bindRequest;
133
134  // The port numbers of the target servers.
135  private final int[] ports;
136
137  // The set of connection options to use for new connections.
138  private final LDAPConnectionOptions connectionOptions;
139
140  // The post-connect processor to invoke against connections created by this
141  // server set.
142  private final PostConnectProcessor postConnectProcessor;
143
144  // The blacklist manager for this server set.
145  private final ServerSetBlacklistManager blacklistManager;
146
147  // The socket factory to use to establish connections.
148  private final SocketFactory socketFactory;
149
150  // The addresses of the target servers.
151  private final String[] addresses;
152
153
154
155  /**
156   * Creates a new round robin server set with the specified set of directory
157   * server addresses and port numbers.  It will use the default socket factory
158   * provided by the JVM to create the underlying sockets.
159   *
160   * @param  addresses  The addresses of the directory servers to which the
161   *                    connections should be established.  It must not be
162   *                    {@code null} or empty.
163   * @param  ports      The ports of the directory servers to which the
164   *                    connections should be established.  It must not be
165   *                    {@code null}, and it must have the same number of
166   *                    elements as the {@code addresses} array.  The order of
167   *                    elements in the {@code addresses} array must correspond
168   *                    to the order of elements in the {@code ports} array.
169   */
170  public RoundRobinServerSet(final String[] addresses, final int[] ports)
171  {
172    this(addresses, ports, null, null);
173  }
174
175
176
177  /**
178   * Creates a new round robin server set with the specified set of directory
179   * server addresses and port numbers.  It will use the default socket factory
180   * provided by the JVM to create the underlying sockets.
181   *
182   * @param  addresses          The addresses of the directory servers to which
183   *                            the connections should be established.  It must
184   *                            not be {@code null} or empty.
185   * @param  ports              The ports of the directory servers to which the
186   *                            connections should be established.  It must not
187   *                            be {@code null}, and it must have the same
188   *                            number of elements as the {@code addresses}
189   *                            array.  The order of elements in the
190   *                            {@code addresses} array must correspond to the
191   *                            order of elements in the {@code ports} array.
192   * @param  connectionOptions  The set of connection options to use for the
193   *                            underlying connections.
194   */
195  public RoundRobinServerSet(final String[] addresses, final int[] ports,
196                             final LDAPConnectionOptions connectionOptions)
197  {
198    this(addresses, ports, null, connectionOptions);
199  }
200
201
202
203  /**
204   * Creates a new round robin server set with the specified set of directory
205   * server addresses and port numbers.  It will use the provided socket factory
206   * to create the underlying sockets.
207   *
208   * @param  addresses      The addresses of the directory servers to which the
209   *                        connections should be established.  It must not be
210   *                        {@code null} or empty.
211   * @param  ports          The ports of the directory servers to which the
212   *                        connections should be established.  It must not be
213   *                        {@code null}, and it must have the same number of
214   *                        elements as the {@code addresses} array.  The order
215   *                        of elements in the {@code addresses} array must
216   *                        correspond to the order of elements in the
217   *                        {@code ports} array.
218   * @param  socketFactory  The socket factory to use to create the underlying
219   *                        connections.
220   */
221  public RoundRobinServerSet(final String[] addresses, final int[] ports,
222                             final SocketFactory socketFactory)
223  {
224    this(addresses, ports, socketFactory, null);
225  }
226
227
228
229  /**
230   * Creates a new round robin server set with the specified set of directory
231   * server addresses and port numbers.  It will use the provided socket factory
232   * to create the underlying sockets.
233   *
234   * @param  addresses          The addresses of the directory servers to which
235   *                            the connections should be established.  It must
236   *                            not be {@code null} or empty.
237   * @param  ports              The ports of the directory servers to which the
238   *                            connections should be established.  It must not
239   *                            be {@code null}, and it must have the same
240   *                            number of elements as the {@code addresses}
241   *                            array.  The order of elements in the
242   *                            {@code addresses} array must correspond to the
243   *                            order of elements in the {@code ports} array.
244   * @param  socketFactory      The socket factory to use to create the
245   *                            underlying connections.
246   * @param  connectionOptions  The set of connection options to use for the
247   *                            underlying connections.
248   */
249  public RoundRobinServerSet(final String[] addresses, final int[] ports,
250                             final SocketFactory socketFactory,
251                             final LDAPConnectionOptions connectionOptions)
252  {
253    this(addresses, ports, socketFactory, connectionOptions, null, null);
254  }
255
256
257
258  /**
259   * Creates a new round robin server set with the specified set of directory
260   * server addresses and port numbers.  It will use the provided socket factory
261   * to create the underlying sockets.
262   *
263   * @param  addresses             The addresses of the directory servers to
264   *                               which the connections should be established.
265   *                               It must not be {@code null} or empty.
266   * @param  ports                 The ports of the directory servers to which
267   *                               the connections should be established.  It
268   *                               must not be {@code null}, and it must have
269   *                               the same number of elements as the
270   *                               {@code addresses} array.  The order of
271   *                               elements in the {@code addresses} array must
272   *                               correspond to the order of elements in the
273   *                               {@code ports} array.
274   * @param  socketFactory         The socket factory to use to create the
275   *                               underlying connections.
276   * @param  connectionOptions     The set of connection options to use for the
277   *                               underlying connections.
278   * @param  bindRequest           The bind request that should be used to
279   *                               authenticate newly established connections.
280   *                               It may be {@code null} if this server set
281   *                               should not perform any authentication.
282   * @param  postConnectProcessor  The post-connect processor that should be
283   *                               invoked on newly established connections.  It
284   *                               may be {@code null} if this server set should
285   *                               not perform any post-connect processing.
286   */
287  public RoundRobinServerSet(final String[] addresses, final int[] ports,
288                             final SocketFactory socketFactory,
289                             final LDAPConnectionOptions connectionOptions,
290                             final BindRequest bindRequest,
291                             final PostConnectProcessor postConnectProcessor)
292  {
293    this(addresses, ports, socketFactory, connectionOptions, bindRequest,
294         postConnectProcessor, getDefaultBlacklistCheckIntervalMillis());
295  }
296
297
298
299  /**
300   * Creates a new round robin server set with the specified set of directory
301   * server addresses and port numbers.  It will use the provided socket factory
302   * to create the underlying sockets.
303   *
304   * @param  addresses                     The addresses of the directory
305   *                                       servers to which the connections
306   *                                       should be established.  It must not
307   *                                       be {@code null} or empty.
308   * @param  ports                         The ports of the directory servers to
309   *                                       which the connections should be
310   *                                       established.  It must not be
311   *                                       {@code null}, and it must have the
312   *                                       same number of elements as the
313   *                                       {@code addresses} array.  The order
314   *                                       of elements in the {@code addresses}
315   *                                       array must correspond to the order of
316   *                                       elements in the {@code ports} array.
317   * @param  socketFactory                 The socket factory to use to create
318   *                                       the underlying connections.
319   * @param  connectionOptions             The set of connection options to use
320   *                                       for the underlying connections.
321   * @param  bindRequest                   The bind request that should be used
322   *                                       to authenticate newly established
323   *                                       connections.  It may be {@code null}
324   *                                       if this server set should not perform
325   *                                       any authentication.
326   * @param  postConnectProcessor          The post-connect processor that
327   *                                       should be invoked on newly
328   *                                       established connections.  It may be
329   *                                       {@code null} if this server set
330   *                                       should not perform any post-connect
331   *                                       processing.
332   * @param  blacklistCheckIntervalMillis  The length of time in milliseconds
333   *                                       between checks of servers on the
334   *                                       blacklist to determine whether they
335   *                                       are once again suitable for use.  A
336   *                                       value that is less than or equal to
337   *                                       zero indicates that no blacklist
338   *                                       should be maintained.
339   */
340  public RoundRobinServerSet(final String[] addresses, final int[] ports,
341                             final SocketFactory socketFactory,
342                             final LDAPConnectionOptions connectionOptions,
343                             final BindRequest bindRequest,
344                             final PostConnectProcessor postConnectProcessor,
345                             final long blacklistCheckIntervalMillis)
346  {
347    Validator.ensureNotNull(addresses, ports);
348    Validator.ensureTrue(addresses.length > 0,
349         "RoundRobinServerSet.addresses must not be empty.");
350    Validator.ensureTrue(addresses.length == ports.length,
351         "RoundRobinServerSet addresses and ports arrays must be the same " +
352              "size.");
353
354    this.addresses = addresses;
355    this.ports = ports;
356    this.bindRequest = bindRequest;
357    this.postConnectProcessor = postConnectProcessor;
358
359    if (socketFactory == null)
360    {
361      this.socketFactory = SocketFactory.getDefault();
362    }
363    else
364    {
365      this.socketFactory = socketFactory;
366    }
367
368    if (connectionOptions == null)
369    {
370      this.connectionOptions = new LDAPConnectionOptions();
371    }
372    else
373    {
374      this.connectionOptions = connectionOptions;
375    }
376
377    nextSlot = new AtomicLong(0L);
378
379    if (blacklistCheckIntervalMillis > 0L)
380    {
381      blacklistManager = new ServerSetBlacklistManager(this, socketFactory,
382           connectionOptions, bindRequest, postConnectProcessor,
383           blacklistCheckIntervalMillis);
384    }
385    else
386    {
387      blacklistManager = null;
388    }
389  }
390
391
392
393  /**
394   * Retrieves the default blacklist check interval (in milliseconds that should
395   * be used if it is not specified.
396   *
397   * @return  The default blacklist check interval (in milliseconds that should
398   *          be used if it is not specified.
399   */
400  private static long getDefaultBlacklistCheckIntervalMillis()
401  {
402    final String propertyValue = StaticUtils.getSystemProperty(
403         PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS);
404    if (propertyValue != null)
405    {
406      try
407      {
408        return Long.parseLong(propertyValue);
409      }
410      catch (final Exception e)
411      {
412        Debug.debugException(e);
413      }
414    }
415
416    return 30_000L;
417  }
418
419
420
421  /**
422   * Retrieves the addresses of the directory servers to which the connections
423   * should be established.
424   *
425   * @return  The addresses of the directory servers to which the connections
426   *          should be established.
427   */
428  public String[] getAddresses()
429  {
430    return addresses;
431  }
432
433
434
435  /**
436   * Retrieves the ports of the directory servers to which the connections
437   * should be established.
438   *
439   * @return  The ports of the directory servers to which the connections should
440   *          be established.
441   */
442  public int[] getPorts()
443  {
444    return ports;
445  }
446
447
448
449  /**
450   * Retrieves the socket factory that will be used to establish connections.
451   *
452   * @return  The socket factory that will be used to establish connections.
453   */
454  public SocketFactory getSocketFactory()
455  {
456    return socketFactory;
457  }
458
459
460
461  /**
462   * Retrieves the set of connection options that will be used for underlying
463   * connections.
464   *
465   * @return  The set of connection options that will be used for underlying
466   *          connections.
467   */
468  public LDAPConnectionOptions getConnectionOptions()
469  {
470    return connectionOptions;
471  }
472
473
474
475  /**
476   * {@inheritDoc}
477   */
478  @Override()
479  public boolean includesAuthentication()
480  {
481    return (bindRequest != null);
482  }
483
484
485
486  /**
487   * {@inheritDoc}
488   */
489  @Override()
490  public boolean includesPostConnectProcessing()
491  {
492    return (postConnectProcessor != null);
493  }
494
495
496
497  /**
498   * {@inheritDoc}
499   */
500  @Override()
501  public LDAPConnection getConnection()
502         throws LDAPException
503  {
504    return getConnection(null);
505  }
506
507
508
509  /**
510   * {@inheritDoc}
511   */
512  @Override()
513  public LDAPConnection getConnection(
514                             final LDAPConnectionPoolHealthCheck healthCheck)
515         throws LDAPException
516  {
517    final int initialSlotNumber =
518         (int) (nextSlot.getAndIncrement() %  addresses.length);
519
520    LDAPException lastException = null;
521    List<ObjectPair<String,Integer>> blacklistedServers = null;
522    for (int i=0; i < addresses.length; i++)
523    {
524      final int slotNumber = ((initialSlotNumber + i) % addresses.length);
525      final String address = addresses[slotNumber];
526      final int port = ports[slotNumber];
527      if ((blacklistManager != null) &&
528           blacklistManager.isBlacklisted(address, port))
529      {
530        if (blacklistedServers == null)
531        {
532          blacklistedServers = new ArrayList<>(addresses.length);
533        }
534
535        blacklistedServers.add(new ObjectPair<>(address, port));
536        continue;
537      }
538
539      try
540      {
541        final LDAPConnection c = new LDAPConnection(socketFactory,
542             connectionOptions, addresses[slotNumber], ports[slotNumber]);
543        doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
544             postConnectProcessor, healthCheck);
545        associateConnectionWithThisServerSet(c);
546        return c;
547      }
548      catch (final LDAPException e)
549      {
550        Debug.debugException(e);
551        lastException = e;
552        if (blacklistManager != null)
553        {
554          blacklistManager.addToBlacklist(address, port, healthCheck);
555        }
556      }
557    }
558
559
560    // If we've gotten here, then we couldn't get a connection from a
561    // non-blacklisted server.  If there were any blacklisted servers, then try
562    // them as a last resort.
563    if (blacklistedServers != null)
564    {
565      for (final ObjectPair<String,Integer> hostPort : blacklistedServers)
566      {
567        try
568        {
569          final LDAPConnection c = new LDAPConnection(socketFactory,
570               connectionOptions, hostPort.getFirst(), hostPort.getSecond());
571          doBindPostConnectAndHealthCheckProcessing(c, bindRequest,
572               postConnectProcessor, healthCheck);
573          associateConnectionWithThisServerSet(c);
574          blacklistManager.removeFromBlacklist(hostPort);
575          return c;
576        }
577        catch (final LDAPException e)
578        {
579          Debug.debugException(e);
580          lastException = e;
581        }
582      }
583    }
584
585
586    // If we've gotten here, then we've failed to connect to any of the servers,
587    // so propagate the last exception to the caller.
588    throw lastException;
589  }
590
591
592
593  /**
594   * Retrieves the blacklist manager for this server set.
595   *
596   * @return  The blacklist manager for this server set, or {@code null} if no
597   *          blacklist will be maintained.
598   */
599  ServerSetBlacklistManager getBlacklistManager()
600  {
601    return blacklistManager;
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public void toString(final StringBuilder buffer)
611  {
612    buffer.append("RoundRobinServerSet(servers={");
613
614    for (int i=0; i < addresses.length; i++)
615    {
616      if (i > 0)
617      {
618        buffer.append(", ");
619      }
620
621      buffer.append(addresses[i]);
622      buffer.append(':');
623      buffer.append(ports[i]);
624    }
625
626    buffer.append("}, includesAuthentication=");
627    buffer.append(bindRequest != null);
628    buffer.append(", includesPostConnectProcessing=");
629    buffer.append(postConnectProcessor != null);
630    buffer.append(')');
631  }
632}