001/*
002 * Copyright 2014-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2014-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.net.InetAddress;
026import java.net.UnknownHostException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.Hashtable;
031import java.util.List;
032import java.util.Map;
033import java.util.Properties;
034import java.util.StringTokenizer;
035import java.util.concurrent.atomic.AtomicLong;
036import java.util.concurrent.atomic.AtomicReference;
037import javax.naming.Context;
038import javax.naming.NamingEnumeration;
039import javax.naming.directory.Attribute;
040import javax.naming.directory.Attributes;
041import javax.naming.directory.InitialDirContext;
042import javax.net.SocketFactory;
043
044import com.unboundid.util.Debug;
045import com.unboundid.util.NotMutable;
046import com.unboundid.util.ObjectPair;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadLocalRandom;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.Validator;
052
053import static com.unboundid.ldap.sdk.LDAPMessages.*;
054
055
056
057/**
058 * This class provides a server set implementation that handles the case in
059 * which a given host name may resolve to multiple IP addresses.  Note that
060 * while a setup like this is typically referred to as "round-robin DNS", this
061 * server set implementation does not strictly require DNS (as names may be
062 * resolved through alternate mechanisms like a hosts file or an alternate name
063 * service), and it does not strictly require round-robin use of those addresses
064 * (as alternate ordering mechanisms, like randomized or failover, may be used).
065 * <BR><BR>
066 * <H2>Example</H2>
067 * The following example demonstrates the process for creating a round-robin DNS
068 * server set for the case in which the hostname "directory.example.com" may be
069 * associated with multiple IP addresses, and the LDAP SDK should attempt to use
070 * them in a round robin manner.
071 * <PRE>
072 *   // Define a number of variables that will be used by the server set.
073 *   String                hostname           = "directory.example.com";
074 *   int                   port               = 389;
075 *   AddressSelectionMode  selectionMode      =
076 *        AddressSelectionMode.ROUND_ROBIN;
077 *   long                  cacheTimeoutMillis = 3600000L; // 1 hour
078 *   String                providerURL        = "dns:"; // Default DNS config.
079 *   SocketFactory         socketFactory      = null; // Default socket factory.
080 *   LDAPConnectionOptions connectionOptions  = null; // Default options.
081 *
082 *   // Create the server set using the settings defined above.
083 *   RoundRobinDNSServerSet serverSet = new RoundRobinDNSServerSet(hostname,
084 *        port, selectionMode, cacheTimeoutMillis, providerURL, socketFactory,
085 *        connectionOptions);
086 *
087 *   // Verify that we can establish a single connection using the server set.
088 *   LDAPConnection connection = serverSet.getConnection();
089 *   RootDSE rootDSEFromConnection = connection.getRootDSE();
090 *   connection.close();
091 *
092 *   // Verify that we can establish a connection pool using the server set.
093 *   SimpleBindRequest bindRequest =
094 *        new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
095 *   LDAPConnectionPool pool =
096 *        new LDAPConnectionPool(serverSet, bindRequest, 10);
097 *   RootDSE rootDSEFromPool = pool.getRootDSE();
098 *   pool.close();
099 * </PRE>
100 */
101@NotMutable()
102@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
103public final class RoundRobinDNSServerSet
104       extends ServerSet
105{
106  /**
107   * The name of a system property that can be used to specify a comma-delimited
108   * list of IP addresses to use if resolution fails.  This is intended
109   * primarily for testing purposes.
110   */
111  static final String PROPERTY_DEFAULT_ADDRESSES =
112       RoundRobinDNSServerSet.class.getName() + ".defaultAddresses";
113
114
115
116  /**
117   * An enum that defines the modes that may be used to select the order in
118   * which addresses should be used in attempts to establish connections.
119   */
120  public enum AddressSelectionMode
121  {
122    /**
123     * The address selection mode that will cause addresses to be consistently
124     * attempted in the order they are retrieved from the name service.
125     */
126    FAILOVER,
127
128
129
130    /**
131     * The address selection mode that will cause the order of addresses to be
132     * randomized for each attempt.
133     */
134    RANDOM,
135
136
137
138    /**
139     * The address selection mode that will cause connection attempts to be made
140     * in a round-robin order.
141     */
142    ROUND_ROBIN;
143
144
145
146    /**
147     * Retrieves the address selection mode with the specified name.
148     *
149     * @param  name  The name of the address selection mode to retrieve.  It
150     *              must not be {@code null}.
151     *
152     * @return  The requested address selection mode, or {@code null} if no such
153     *          change mode is defined.
154     */
155    public static AddressSelectionMode forName(final String name)
156    {
157      switch (StaticUtils.toLowerCase(name))
158      {
159        case "failover":
160          return FAILOVER;
161        case "random":
162          return RANDOM;
163        case "roundrobin":
164        case "round-robin":
165        case "round_robin":
166          return ROUND_ROBIN;
167        default:
168          return null;
169      }
170    }
171  }
172
173
174
175  // The address selection mode that should be used if the provided hostname
176  // resolves to multiple addresses.
177  private final AddressSelectionMode selectionMode;
178
179  // A counter that will be used to handle round-robin ordering.
180  private final AtomicLong roundRobinCounter;
181
182  // A reference to an object that combines the resolved addresses with a
183  // timestamp indicating when the value should no longer be trusted.
184  private final AtomicReference<ObjectPair<InetAddress[],Long>>
185       resolvedAddressesWithTimeout;
186
187  // The bind request to use to authenticate connections created by this
188  // server set.
189  private final BindRequest bindRequest;
190
191  // The properties that will be used to initialize the JNDI context, if any.
192  private final Hashtable<String,String> jndiProperties;
193
194  // The port number for the target server.
195  private final int port;
196
197  // The set of connection options to use for new connections.
198  private final LDAPConnectionOptions connectionOptions;
199
200  // The maximum length of time, in milliseconds, to cache resolved addresses.
201  private final long cacheTimeoutMillis;
202
203  // The post-connect processor to invoke against connections created by this
204  // server set.
205  private final PostConnectProcessor postConnectProcessor;
206
207  // The socket factory to use to establish connections.
208  private final SocketFactory socketFactory;
209
210  // The hostname to be resolved.
211  private final String hostname;
212
213  // The provider URL to use to resolve names, if any.
214  private final String providerURL;
215
216  // The DNS record types that will be used to obtain the IP addresses for the
217  // specified hostname.
218  private final String[] dnsRecordTypes;
219
220
221
222  /**
223   * Creates a new round-robin DNS server set with the provided information.
224   *
225   * @param  hostname            The hostname to be resolved to one or more
226   *                             addresses.  It must not be {@code null}.
227   * @param  port                The port to use to connect to the server.  Note
228   *                             that even if the provided hostname resolves to
229   *                             multiple addresses, the same port must be used
230   *                             for all addresses.
231   * @param  selectionMode       The selection mode that should be used if the
232   *                             hostname resolves to multiple addresses.  It
233   *                             must not be {@code null}.
234   * @param  cacheTimeoutMillis  The maximum length of time in milliseconds to
235   *                             cache addresses resolved from the provided
236   *                             hostname.  Caching resolved addresses can
237   *                             result in better performance and can reduce the
238   *                             number of requests to the name service.  A
239   *                             that is less than or equal to zero indicates
240   *                             that no caching should be used.
241   * @param  providerURL         The JNDI provider URL that should be used when
242   *                             communicating with the DNS server.  If this is
243   *                             {@code null}, then the underlying system's
244   *                             name service mechanism will be used (which may
245   *                             make use of other services instead of or in
246   *                             addition to DNS).  If this is non-{@code null},
247   *                             then only DNS will be used to perform the name
248   *                             resolution.  A value of "dns:" indicates that
249   *                             the underlying system's DNS configuration
250   *                             should be used.
251   * @param  socketFactory       The socket factory to use to establish the
252   *                             connections.  It may be {@code null} if the
253   *                             JVM-default socket factory should be used.
254   * @param  connectionOptions   The set of connection options that should be
255   *                             used for the connections.  It may be
256   *                             {@code null} if a default set of connection
257   *                             options should be used.
258   */
259  public RoundRobinDNSServerSet(final String hostname, final int port,
260                                final AddressSelectionMode selectionMode,
261                                final long cacheTimeoutMillis,
262                                final String providerURL,
263                                final SocketFactory socketFactory,
264                                final LDAPConnectionOptions connectionOptions)
265  {
266    this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL,
267         null, null, socketFactory, connectionOptions);
268  }
269
270
271
272  /**
273   * Creates a new round-robin DNS server set with the provided information.
274   *
275   * @param  hostname            The hostname to be resolved to one or more
276   *                             addresses.  It must not be {@code null}.
277   * @param  port                The port to use to connect to the server.  Note
278   *                             that even if the provided hostname resolves to
279   *                             multiple addresses, the same port must be used
280   *                             for all addresses.
281   * @param  selectionMode       The selection mode that should be used if the
282   *                             hostname resolves to multiple addresses.  It
283   *                             must not be {@code null}.
284   * @param  cacheTimeoutMillis  The maximum length of time in milliseconds to
285   *                             cache addresses resolved from the provided
286   *                             hostname.  Caching resolved addresses can
287   *                             result in better performance and can reduce the
288   *                             number of requests to the name service.  A
289   *                             that is less than or equal to zero indicates
290   *                             that no caching should be used.
291   * @param  providerURL         The JNDI provider URL that should be used when
292   *                             communicating with the DNS server.If both
293   *                             {@code providerURL} and {@code jndiProperties}
294   *                             are {@code null}, then then JNDI will not be
295   *                             used to interact with DNS and the hostname
296   *                             resolution will be performed via the underlying
297   *                             system's name service mechanism (which may make
298   *                             use of other services instead of or in addition
299   *                             to DNS)..  If this is non-{@code null}, then
300   *                             only DNS will be used to perform the name
301   *                             resolution.  A value of "dns:" indicates that
302   *                             the underlying system's DNS configuration
303   *                             should be used.
304   * @param  jndiProperties      A set of JNDI-related properties that should be
305   *                             be used when initializing the context for
306   *                             interacting with the DNS server via JNDI.  If
307   *                             both {@code providerURL} and
308   *                             {@code jndiProperties} are {@code null}, then
309   *                             then JNDI will not be used to interact with
310   *                             DNS and the hostname resolution will be
311   *                             performed via the underlying system's name
312   *                             service mechanism (which may make use of other
313   *                             services instead of or in addition to DNS).  If
314   *                             {@code providerURL} is {@code null} and
315   *                             {@code jndiProperties} is non-{@code null},
316   *                             then the provided properties must specify the
317   *                             URL.
318   * @param  dnsRecordTypes      Specifies the types of DNS records that will be
319   *                             used to obtain the addresses for the specified
320   *                             hostname.  This will only be used if at least
321   *                             one of {@code providerURL} and
322   *                             {@code jndiProperties} is non-{@code null}.  If
323   *                             this is {@code null} or empty, then a default
324   *                             record type of "A" (indicating IPv4 addresses)
325   *                             will be used.
326   * @param  socketFactory       The socket factory to use to establish the
327   *                             connections.  It may be {@code null} if the
328   *                             JVM-default socket factory should be used.
329   * @param  connectionOptions   The set of connection options that should be
330   *                             used for the connections.  It may be
331   *                             {@code null} if a default set of connection
332   *                             options should be used.
333   */
334  public RoundRobinDNSServerSet(final String hostname, final int port,
335                                final AddressSelectionMode selectionMode,
336                                final long cacheTimeoutMillis,
337                                final String providerURL,
338                                final Properties jndiProperties,
339                                final String[] dnsRecordTypes,
340                                final SocketFactory socketFactory,
341                                final LDAPConnectionOptions connectionOptions)
342  {
343    this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL,
344         jndiProperties, dnsRecordTypes, socketFactory, connectionOptions, null,
345         null);
346  }
347
348
349
350  /**
351   * Creates a new round-robin DNS server set with the provided information.
352   *
353   * @param  hostname              The hostname to be resolved to one or more
354   *                               addresses.  It must not be {@code null}.
355   * @param  port                  The port to use to connect to the server.
356   *                               Note that even if the provided hostname
357   *                               resolves to multiple addresses, the same
358   *                               port must be used for all addresses.
359   * @param  selectionMode         The selection mode that should be used if the
360   *                               hostname resolves to multiple addresses.  It
361   *                               must not be {@code null}.
362   * @param  cacheTimeoutMillis    The maximum length of time in milliseconds to
363   *                               cache addresses resolved from the provided
364   *                               hostname.  Caching resolved addresses can
365   *                               result in better performance and can reduce
366   *                               the number of requests to the name service.
367   *                               A that is less than or equal to zero
368   *                               indicates that no caching should be used.
369   * @param  providerURL           The JNDI provider URL that should be used
370   *                               when communicating with the DNS server.  If
371   *                               both {@code providerURL} and
372   *                               {@code jndiProperties} are {@code null},
373   *                               then then JNDI will not be used to interact
374   *                               with DNS and the hostname resolution will be
375   *                               performed via the underlying system's name
376   *                               service mechanism (which may make use of
377   *                               other services instead of or in addition to
378   *                               DNS).  If this is non-{@code null}, then only
379   *                               DNS will be used to perform the name
380   *                               resolution.  A value of "dns:" indicates that
381   *                               the underlying system's DNS configuration
382   *                               should be used.
383   * @param  jndiProperties        A set of JNDI-related properties that should
384   *                               be used when initializing the context for
385   *                               interacting with the DNS server via JNDI.  If
386   *                               both {@code providerURL} and
387   *                               {@code jndiProperties} are {@code null}, then
388   *                               JNDI will not be used to interact with DNS
389   *                               and the hostname resolution will be
390   *                               performed via the underlying system's name
391   *                               service mechanism (which may make use of
392   *                               other services instead of or in addition to
393   *                               DNS).  If {@code providerURL} is
394   *                               {@code null} and {@code jndiProperties} is
395   *                               non-{@code null}, then the provided
396   *                               properties must specify the URL.
397   * @param  dnsRecordTypes        Specifies the types of DNS records that will
398   *                               be used to obtain the addresses for the
399   *                               specified hostname.  This will only be used
400   *                               if at least one of {@code providerURL} and
401   *                               {@code jndiProperties} is non-{@code null}.
402   *                               If this is {@code null} or empty, then a
403   *                               default record type of "A" (indicating IPv4
404   *                               addresses) will be used.
405   * @param  socketFactory         The socket factory to use to establish the
406   *                               connections.  It may be {@code null} if the
407   *                               JVM-default socket factory should be used.
408   * @param  connectionOptions     The set of connection options that should be
409   *                               used for the connections.  It may be
410   *                               {@code null} if a default set of connection
411   *                               options should be used.
412   * @param  bindRequest           The bind request that should be used to
413   *                               authenticate newly-established connections.
414   *                               It may be {@code null} if this server set
415   *                               should not perform any authentication.
416   * @param  postConnectProcessor  The post-connect processor that should be
417   *                               invoked on newly-established connections.  It
418   *                               may be {@code null} if this server set should
419   *                               not perform any post-connect processing.
420   */
421  public RoundRobinDNSServerSet(final String hostname, final int port,
422                                final AddressSelectionMode selectionMode,
423                                final long cacheTimeoutMillis,
424                                final String providerURL,
425                                final Properties jndiProperties,
426                                final String[] dnsRecordTypes,
427                                final SocketFactory socketFactory,
428                                final LDAPConnectionOptions connectionOptions,
429                                final BindRequest bindRequest,
430                                final PostConnectProcessor postConnectProcessor)
431  {
432    Validator.ensureNotNull(hostname);
433    Validator.ensureTrue((port >= 1) && (port <= 65_535));
434    Validator.ensureNotNull(selectionMode);
435
436    this.hostname = hostname;
437    this.port = port;
438    this.selectionMode = selectionMode;
439    this.providerURL = providerURL;
440    this.bindRequest = bindRequest;
441    this.postConnectProcessor = postConnectProcessor;
442
443    if (jndiProperties == null)
444    {
445      if (providerURL == null)
446      {
447        this.jndiProperties = null;
448      }
449      else
450      {
451        this.jndiProperties = new Hashtable<>(2);
452        this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
453             "com.sun.jndi.dns.DnsContextFactory");
454        this.jndiProperties.put(Context.PROVIDER_URL, providerURL);
455      }
456    }
457    else
458    {
459      this.jndiProperties = new Hashtable<>(jndiProperties.size()+2);
460      for (final Map.Entry<Object,Object> e : jndiProperties.entrySet())
461      {
462        this.jndiProperties.put(String.valueOf(e.getKey()),
463             String.valueOf(e.getValue()));
464      }
465
466      if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY))
467      {
468        this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
469             "com.sun.jndi.dns.DnsContextFactory");
470      }
471
472      if ((! this.jndiProperties.containsKey(Context.PROVIDER_URL)) &&
473         (providerURL != null))
474      {
475        this.jndiProperties.put(Context.PROVIDER_URL, providerURL);
476      }
477    }
478
479    if (dnsRecordTypes == null)
480    {
481      this.dnsRecordTypes = new String[] { "A" };
482    }
483    else
484    {
485      this.dnsRecordTypes = dnsRecordTypes;
486    }
487
488    if (cacheTimeoutMillis > 0L)
489    {
490      this.cacheTimeoutMillis = cacheTimeoutMillis;
491    }
492    else
493    {
494      this.cacheTimeoutMillis = 0L;
495    }
496
497    if (socketFactory == null)
498    {
499      this.socketFactory = SocketFactory.getDefault();
500    }
501    else
502    {
503      this.socketFactory = socketFactory;
504    }
505
506    if (connectionOptions == null)
507    {
508      this.connectionOptions = new LDAPConnectionOptions();
509    }
510    else
511    {
512      this.connectionOptions = connectionOptions;
513    }
514
515    roundRobinCounter = new AtomicLong(0L);
516    resolvedAddressesWithTimeout = new AtomicReference<>();
517  }
518
519
520
521  /**
522   * Retrieves the hostname to be resolved.
523   *
524   * @return  The hostname to be resolved.
525   */
526  public String getHostname()
527  {
528    return hostname;
529  }
530
531
532
533  /**
534   * Retrieves the port to use to connect to the server.
535   *
536   * @return  The port to use to connect to the server.
537   */
538  public int getPort()
539  {
540    return port;
541  }
542
543
544
545  /**
546   * Retrieves the address selection mode that should be used if the provided
547   * hostname resolves to multiple addresses.
548   *
549   * @return  The address selection
550   */
551  public AddressSelectionMode getAddressSelectionMode()
552  {
553    return selectionMode;
554  }
555
556
557
558  /**
559   * Retrieves the length of time in milliseconds that resolved addresses may be
560   * cached.
561   *
562   * @return  The length of time in milliseconds that resolved addresses may be
563   *          cached, or zero if no caching should be performed.
564   */
565  public long getCacheTimeoutMillis()
566  {
567    return cacheTimeoutMillis;
568  }
569
570
571
572  /**
573   * Retrieves the provider URL that should be used when interacting with DNS to
574   * resolve the hostname to its corresponding addresses.
575   *
576   * @return  The provider URL that should be used when interacting with DNS to
577   *          resolve the hostname to its corresponding addresses, or
578   *          {@code null} if the system's configured naming service should be
579   *          used.
580   */
581  public String getProviderURL()
582  {
583    return providerURL;
584  }
585
586
587
588  /**
589   * Retrieves an unmodifiable map of properties that will be used to initialize
590   * the JNDI context used to interact with DNS.  Note that the map returned
591   * will reflect the actual properties that will be used, and may not exactly
592   * match the properties provided when creating this server set.
593   *
594   * @return  An unmodifiable map of properties that will be used to initialize
595   *          the JNDI context used to interact with DNS, or {@code null} if
596   *          JNDI will nto be used to interact with DNS.
597   */
598  public Map<String,String> getJNDIProperties()
599  {
600    if (jndiProperties == null)
601    {
602      return null;
603    }
604    else
605    {
606      return Collections.unmodifiableMap(jndiProperties);
607    }
608  }
609
610
611
612  /**
613   * Retrieves an array of record types that will be requested if JNDI will be
614   * used to interact with DNS.
615   *
616   * @return  An array of record types that will be requested if JNDI will be
617   *          used to interact with DNS.
618   */
619  public String[] getDNSRecordTypes()
620  {
621    return dnsRecordTypes;
622  }
623
624
625
626  /**
627   * Retrieves the socket factory that will be used to establish connections.
628   * This will not be {@code null}, even if no socket factory was provided when
629   * the server set was created.
630   *
631   * @return  The socket factory that will be used to establish connections.
632   */
633  public SocketFactory getSocketFactory()
634  {
635    return socketFactory;
636  }
637
638
639
640  /**
641   * Retrieves the set of connection options that will be used for underlying
642   * connections.  This will not be {@code null}, even if no connection options
643   * object was provided when the server set was created.
644   *
645   * @return  The set of connection options that will be used for underlying
646   *          connections.
647   */
648  public LDAPConnectionOptions getConnectionOptions()
649  {
650    return connectionOptions;
651  }
652
653
654
655  /**
656   * {@inheritDoc}
657   */
658  @Override()
659  public boolean includesAuthentication()
660  {
661    return (bindRequest != null);
662  }
663
664
665
666  /**
667   * {@inheritDoc}
668   */
669  @Override()
670  public boolean includesPostConnectProcessing()
671  {
672    return (postConnectProcessor != null);
673  }
674
675
676
677  /**
678   * {@inheritDoc}
679   */
680  @Override()
681  public LDAPConnection getConnection()
682         throws LDAPException
683  {
684    return getConnection(null);
685  }
686
687
688
689  /**
690   * {@inheritDoc}
691   */
692  @Override()
693  public synchronized LDAPConnection getConnection(
694                           final LDAPConnectionPoolHealthCheck healthCheck)
695         throws LDAPException
696  {
697    LDAPException firstException = null;
698
699    final LDAPConnection conn =
700         new LDAPConnection(socketFactory, connectionOptions);
701    for (final InetAddress a : orderAddresses(resolveHostname()))
702    {
703      boolean close = true;
704      try
705      {
706        conn.connect(hostname, a, port,
707             connectionOptions.getConnectTimeoutMillis());
708        doBindPostConnectAndHealthCheckProcessing(conn, bindRequest,
709             postConnectProcessor, healthCheck);
710        close = false;
711        return conn;
712      }
713      catch (final LDAPException le)
714      {
715        Debug.debugException(le);
716        if (firstException == null)
717        {
718          firstException = le;
719        }
720      }
721      finally
722      {
723        if (close)
724        {
725          conn.close();
726        }
727      }
728    }
729
730    throw firstException;
731  }
732
733
734
735  /**
736   * Resolve the hostname to its corresponding addresses.
737   *
738   * @return  The addresses resolved from the hostname.
739   *
740   * @throws  LDAPException  If
741   */
742  InetAddress[] resolveHostname()
743          throws LDAPException
744  {
745    // First, see if we can use the cached addresses.
746    final ObjectPair<InetAddress[],Long> pair =
747         resolvedAddressesWithTimeout.get();
748    if (pair != null)
749    {
750      if (pair.getSecond() <= System.currentTimeMillis())
751      {
752        return pair.getFirst();
753      }
754    }
755
756
757    // Try to resolve the address.
758    InetAddress[] addresses = null;
759    try
760    {
761      if (jndiProperties == null)
762      {
763        addresses = InetAddress.getAllByName(hostname);
764      }
765      else
766      {
767        final Attributes attributes;
768        final InitialDirContext context = new InitialDirContext(jndiProperties);
769        try
770        {
771          attributes = context.getAttributes(hostname, dnsRecordTypes);
772        }
773        finally
774        {
775          context.close();
776        }
777
778        if (attributes != null)
779        {
780          final ArrayList<InetAddress> addressList = new ArrayList<>(10);
781          for (final String recordType : dnsRecordTypes)
782          {
783            final Attribute a = attributes.get(recordType);
784            if (a != null)
785            {
786              final NamingEnumeration<?> values = a.getAll();
787              while (values.hasMore())
788              {
789                final Object value = values.next();
790                addressList.add(getInetAddressForIP(String.valueOf(value)));
791              }
792            }
793          }
794
795          if (! addressList.isEmpty())
796          {
797            addresses = new InetAddress[addressList.size()];
798            addressList.toArray(addresses);
799          }
800        }
801      }
802    }
803    catch (final Exception e)
804    {
805      Debug.debugException(e);
806      addresses = getDefaultAddresses();
807    }
808
809
810    // If we were able to resolve the hostname, then cache and return the
811    // resolved addresses.
812    if ((addresses != null) && (addresses.length > 0))
813    {
814      final long timeoutTime;
815      if (cacheTimeoutMillis > 0L)
816      {
817        timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis;
818      }
819      else
820      {
821        timeoutTime = System.currentTimeMillis() - 1L;
822      }
823
824      resolvedAddressesWithTimeout.set(
825           new ObjectPair<>(addresses, timeoutTime));
826      return addresses;
827    }
828
829
830    // If we've gotten here, then we couldn't resolve the hostname.  If we have
831    // cached addresses, then use them even though the timeout has expired
832    // because that's better than nothing.
833    if (pair != null)
834    {
835      return pair.getFirst();
836    }
837
838    throw new LDAPException(ResultCode.CONNECT_ERROR,
839         ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname));
840  }
841
842
843
844  /**
845   * Orders the provided array of InetAddress objects to reflect the order in
846   * which the addresses should be used to try to create a new connection.
847   *
848   * @param  addresses  The array of addresses to be ordered.
849   *
850   * @return  A list containing the ordered addresses.
851   */
852  List<InetAddress> orderAddresses(final InetAddress[] addresses)
853  {
854    final ArrayList<InetAddress> l = new ArrayList<>(addresses.length);
855
856    switch (selectionMode)
857    {
858      case RANDOM:
859        l.addAll(Arrays.asList(addresses));
860        Collections.shuffle(l, ThreadLocalRandom.get());
861        break;
862
863      case ROUND_ROBIN:
864        final int index =
865             (int) (roundRobinCounter.getAndIncrement() % addresses.length);
866        for (int i=index; i < addresses.length; i++)
867        {
868          l.add(addresses[i]);
869        }
870        for (int i=0; i < index; i++)
871        {
872          l.add(addresses[i]);
873        }
874        break;
875
876      case FAILOVER:
877      default:
878        // We'll use the addresses in the same order we originally got them.
879        l.addAll(Arrays.asList(addresses));
880        break;
881    }
882
883    return l;
884  }
885
886
887
888  /**
889   * Retrieves a default set of addresses that may be used for testing.
890   *
891   * @return  A default set of addresses that may be used for testing.
892   */
893  InetAddress[] getDefaultAddresses()
894  {
895    final String defaultAddrsStr =
896         System.getProperty(PROPERTY_DEFAULT_ADDRESSES);
897    if (defaultAddrsStr == null)
898    {
899      return null;
900    }
901
902    final StringTokenizer tokenizer =
903         new StringTokenizer(defaultAddrsStr, " ,");
904    final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()];
905    for (int i=0; i < addresses.length; i++)
906    {
907      try
908      {
909        addresses[i] = getInetAddressForIP(tokenizer.nextToken());
910      }
911      catch (final Exception e)
912      {
913        Debug.debugException(e);
914        return null;
915      }
916    }
917
918    return addresses;
919  }
920
921
922
923  /**
924   * Retrieves an InetAddress object with the configured hostname and the
925   * provided IP address.
926   *
927   * @param  ipAddress  The string representation of the IP address to use in
928   *                    the returned InetAddress.
929   *
930   * @return  The created InetAddress.
931   *
932   * @throws  UnknownHostException  If the provided string does not represent a
933   *                                valid IPv4 or IPv6 address.
934   */
935  private InetAddress getInetAddressForIP(final String ipAddress)
936          throws UnknownHostException
937  {
938    // We want to create an InetAddress that has the provided hostname and the
939    // specified IP address.  To do that, we need to use
940    // InetAddress.getByAddress.  But that requires the IP address to be
941    // specified as a byte array, and the easiest way to convert an IP address
942    // string to a byte array is to use InetAddress.getByName.
943    final InetAddress byName = InetAddress.getByName(String.valueOf(ipAddress));
944    return InetAddress.getByAddress(hostname, byName.getAddress());
945  }
946
947
948
949  /**
950   * {@inheritDoc}
951   */
952  @Override()
953  public void toString(final StringBuilder buffer)
954  {
955    buffer.append("RoundRobinDNSServerSet(hostname='");
956    buffer.append(hostname);
957    buffer.append("', port=");
958    buffer.append(port);
959    buffer.append(", addressSelectionMode=");
960    buffer.append(selectionMode.name());
961    buffer.append(", cacheTimeoutMillis=");
962    buffer.append(cacheTimeoutMillis);
963
964    if (providerURL != null)
965    {
966      buffer.append(", providerURL='");
967      buffer.append(providerURL);
968      buffer.append('\'');
969    }
970
971    buffer.append(", includesAuthentication=");
972    buffer.append(bindRequest != null);
973    buffer.append(", includesPostConnectProcessing=");
974    buffer.append(postConnectProcessor != null);
975    buffer.append(')');
976  }
977}