001/*
002 * Copyright 2014-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2014-2019 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        associateConnectionWithThisServerSet(conn);
712        return conn;
713      }
714      catch (final LDAPException le)
715      {
716        Debug.debugException(le);
717        if (firstException == null)
718        {
719          firstException = le;
720        }
721      }
722      finally
723      {
724        if (close)
725        {
726          conn.close();
727        }
728      }
729    }
730
731    throw firstException;
732  }
733
734
735
736  /**
737   * Resolve the hostname to its corresponding addresses.
738   *
739   * @return  The addresses resolved from the hostname.
740   *
741   * @throws  LDAPException  If
742   */
743  InetAddress[] resolveHostname()
744          throws LDAPException
745  {
746    // First, see if we can use the cached addresses.
747    final ObjectPair<InetAddress[],Long> pair =
748         resolvedAddressesWithTimeout.get();
749    if (pair != null)
750    {
751      if (pair.getSecond() >= System.currentTimeMillis())
752      {
753        return pair.getFirst();
754      }
755    }
756
757
758    // Try to resolve the address.
759    InetAddress[] addresses = null;
760    try
761    {
762      if (jndiProperties == null)
763      {
764        addresses = connectionOptions.getNameResolver().getAllByName(hostname);
765      }
766      else
767      {
768        final Attributes attributes;
769        final InitialDirContext context = new InitialDirContext(jndiProperties);
770        try
771        {
772          attributes = context.getAttributes(hostname, dnsRecordTypes);
773        }
774        finally
775        {
776          context.close();
777        }
778
779        if (attributes != null)
780        {
781          final ArrayList<InetAddress> addressList = new ArrayList<>(10);
782          for (final String recordType : dnsRecordTypes)
783          {
784            final Attribute a = attributes.get(recordType);
785            if (a != null)
786            {
787              final NamingEnumeration<?> values = a.getAll();
788              while (values.hasMore())
789              {
790                final Object value = values.next();
791                addressList.add(getInetAddressForIP(String.valueOf(value)));
792              }
793            }
794          }
795
796          if (! addressList.isEmpty())
797          {
798            addresses = new InetAddress[addressList.size()];
799            addressList.toArray(addresses);
800          }
801        }
802      }
803    }
804    catch (final Exception e)
805    {
806      Debug.debugException(e);
807      addresses = getDefaultAddresses();
808    }
809
810
811    // If we were able to resolve the hostname, then cache and return the
812    // resolved addresses.
813    if ((addresses != null) && (addresses.length > 0))
814    {
815      final long timeoutTime;
816      if (cacheTimeoutMillis > 0L)
817      {
818        timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis;
819      }
820      else
821      {
822        timeoutTime = System.currentTimeMillis() - 1L;
823      }
824
825      resolvedAddressesWithTimeout.set(
826           new ObjectPair<>(addresses, timeoutTime));
827      return addresses;
828    }
829
830
831    // If we've gotten here, then we couldn't resolve the hostname.  If we have
832    // cached addresses, then use them even though the timeout has expired
833    // because that's better than nothing.
834    if (pair != null)
835    {
836      return pair.getFirst();
837    }
838
839    throw new LDAPException(ResultCode.CONNECT_ERROR,
840         ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname));
841  }
842
843
844
845  /**
846   * Orders the provided array of InetAddress objects to reflect the order in
847   * which the addresses should be used to try to create a new connection.
848   *
849   * @param  addresses  The array of addresses to be ordered.
850   *
851   * @return  A list containing the ordered addresses.
852   */
853  List<InetAddress> orderAddresses(final InetAddress[] addresses)
854  {
855    final ArrayList<InetAddress> l = new ArrayList<>(addresses.length);
856
857    switch (selectionMode)
858    {
859      case RANDOM:
860        l.addAll(Arrays.asList(addresses));
861        Collections.shuffle(l, ThreadLocalRandom.get());
862        break;
863
864      case ROUND_ROBIN:
865        final int index =
866             (int) (roundRobinCounter.getAndIncrement() % addresses.length);
867        for (int i=index; i < addresses.length; i++)
868        {
869          l.add(addresses[i]);
870        }
871        for (int i=0; i < index; i++)
872        {
873          l.add(addresses[i]);
874        }
875        break;
876
877      case FAILOVER:
878      default:
879        // We'll use the addresses in the same order we originally got them.
880        l.addAll(Arrays.asList(addresses));
881        break;
882    }
883
884    return l;
885  }
886
887
888
889  /**
890   * Retrieves a default set of addresses that may be used for testing.
891   *
892   * @return  A default set of addresses that may be used for testing.
893   */
894  InetAddress[] getDefaultAddresses()
895  {
896    final String defaultAddrsStr =
897         StaticUtils.getSystemProperty(PROPERTY_DEFAULT_ADDRESSES);
898    if (defaultAddrsStr == null)
899    {
900      return null;
901    }
902
903    final StringTokenizer tokenizer =
904         new StringTokenizer(defaultAddrsStr, " ,");
905    final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()];
906    for (int i=0; i < addresses.length; i++)
907    {
908      try
909      {
910        addresses[i] = getInetAddressForIP(tokenizer.nextToken());
911      }
912      catch (final Exception e)
913      {
914        Debug.debugException(e);
915        return null;
916      }
917    }
918
919    return addresses;
920  }
921
922
923
924  /**
925   * Retrieves an InetAddress object with the configured hostname and the
926   * provided IP address.
927   *
928   * @param  ipAddress  The string representation of the IP address to use in
929   *                    the returned InetAddress.
930   *
931   * @return  The created InetAddress.
932   *
933   * @throws  UnknownHostException  If the provided string does not represent a
934   *                                valid IPv4 or IPv6 address.
935   */
936  private InetAddress getInetAddressForIP(final String ipAddress)
937          throws UnknownHostException
938  {
939    // We want to create an InetAddress that has the provided hostname and the
940    // specified IP address.  To do that, we need to use
941    // InetAddress.getByAddress.  But that requires the IP address to be
942    // specified as a byte array, and the easiest way to convert an IP address
943    // string to a byte array is to use InetAddress.getByName.
944    final InetAddress byName = connectionOptions.getNameResolver().
945         getByName(String.valueOf(ipAddress));
946    return InetAddress.getByAddress(hostname, byName.getAddress());
947  }
948
949
950
951  /**
952   * {@inheritDoc}
953   */
954  @Override()
955  public void toString(final StringBuilder buffer)
956  {
957    buffer.append("RoundRobinDNSServerSet(hostname='");
958    buffer.append(hostname);
959    buffer.append("', port=");
960    buffer.append(port);
961    buffer.append(", addressSelectionMode=");
962    buffer.append(selectionMode.name());
963    buffer.append(", cacheTimeoutMillis=");
964    buffer.append(cacheTimeoutMillis);
965
966    if (providerURL != null)
967    {
968      buffer.append(", providerURL='");
969      buffer.append(providerURL);
970      buffer.append('\'');
971    }
972
973    buffer.append(", includesAuthentication=");
974    buffer.append(bindRequest != null);
975    buffer.append(", includesPostConnectProcessing=");
976    buffer.append(postConnectProcessor != null);
977    buffer.append(')');
978  }
979}