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 <= 65535));
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<String,String>(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 =
460           new Hashtable<String,String>(jndiProperties.size()+2);
461      for (final Map.Entry<Object,Object> e : jndiProperties.entrySet())
462      {
463        this.jndiProperties.put(String.valueOf(e.getKey()),
464             String.valueOf(e.getValue()));
465      }
466
467      if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY))
468      {
469        this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
470             "com.sun.jndi.dns.DnsContextFactory");
471      }
472
473      if ((! this.jndiProperties.containsKey(Context.PROVIDER_URL)) &&
474         (providerURL != null))
475      {
476        this.jndiProperties.put(Context.PROVIDER_URL, providerURL);
477      }
478    }
479
480    if (dnsRecordTypes == null)
481    {
482      this.dnsRecordTypes = new String[] { "A" };
483    }
484    else
485    {
486      this.dnsRecordTypes = dnsRecordTypes;
487    }
488
489    if (cacheTimeoutMillis > 0L)
490    {
491      this.cacheTimeoutMillis = cacheTimeoutMillis;
492    }
493    else
494    {
495      this.cacheTimeoutMillis = 0L;
496    }
497
498    if (socketFactory == null)
499    {
500      this.socketFactory = SocketFactory.getDefault();
501    }
502    else
503    {
504      this.socketFactory = socketFactory;
505    }
506
507    if (connectionOptions == null)
508    {
509      this.connectionOptions = new LDAPConnectionOptions();
510    }
511    else
512    {
513      this.connectionOptions = connectionOptions;
514    }
515
516    roundRobinCounter = new AtomicLong(0L);
517    resolvedAddressesWithTimeout =
518         new AtomicReference<ObjectPair<InetAddress[],Long>>();
519  }
520
521
522
523  /**
524   * Retrieves the hostname to be resolved.
525   *
526   * @return  The hostname to be resolved.
527   */
528  public String getHostname()
529  {
530    return hostname;
531  }
532
533
534
535  /**
536   * Retrieves the port to use to connect to the server.
537   *
538   * @return  The port to use to connect to the server.
539   */
540  public int getPort()
541  {
542    return port;
543  }
544
545
546
547  /**
548   * Retrieves the address selection mode that should be used if the provided
549   * hostname resolves to multiple addresses.
550   *
551   * @return  The address selection
552   */
553  public AddressSelectionMode getAddressSelectionMode()
554  {
555    return selectionMode;
556  }
557
558
559
560  /**
561   * Retrieves the length of time in milliseconds that resolved addresses may be
562   * cached.
563   *
564   * @return  The length of time in milliseconds that resolved addresses may be
565   *          cached, or zero if no caching should be performed.
566   */
567  public long getCacheTimeoutMillis()
568  {
569    return cacheTimeoutMillis;
570  }
571
572
573
574  /**
575   * Retrieves the provider URL that should be used when interacting with DNS to
576   * resolve the hostname to its corresponding addresses.
577   *
578   * @return  The provider URL that should be used when interacting with DNS to
579   *          resolve the hostname to its corresponding addresses, or
580   *          {@code null} if the system's configured naming service should be
581   *          used.
582   */
583  public String getProviderURL()
584  {
585    return providerURL;
586  }
587
588
589
590  /**
591   * Retrieves an unmodifiable map of properties that will be used to initialize
592   * the JNDI context used to interact with DNS.  Note that the map returned
593   * will reflect the actual properties that will be used, and may not exactly
594   * match the properties provided when creating this server set.
595   *
596   * @return  An unmodifiable map of properties that will be used to initialize
597   *          the JNDI context used to interact with DNS, or {@code null} if
598   *          JNDI will nto be used to interact with DNS.
599   */
600  public Map<String,String> getJNDIProperties()
601  {
602    if (jndiProperties == null)
603    {
604      return null;
605    }
606    else
607    {
608      return Collections.unmodifiableMap(jndiProperties);
609    }
610  }
611
612
613
614  /**
615   * Retrieves an array of record types that will be requested if JNDI will be
616   * used to interact with DNS.
617   *
618   * @return  An array of record types that will be requested if JNDI will be
619   *          used to interact with DNS.
620   */
621  public String[] getDNSRecordTypes()
622  {
623    return dnsRecordTypes;
624  }
625
626
627
628  /**
629   * Retrieves the socket factory that will be used to establish connections.
630   * This will not be {@code null}, even if no socket factory was provided when
631   * the server set was created.
632   *
633   * @return  The socket factory that will be used to establish connections.
634   */
635  public SocketFactory getSocketFactory()
636  {
637    return socketFactory;
638  }
639
640
641
642  /**
643   * Retrieves the set of connection options that will be used for underlying
644   * connections.  This will not be {@code null}, even if no connection options
645   * object was provided when the server set was created.
646   *
647   * @return  The set of connection options that will be used for underlying
648   *          connections.
649   */
650  public LDAPConnectionOptions getConnectionOptions()
651  {
652    return connectionOptions;
653  }
654
655
656
657  /**
658   * {@inheritDoc}
659   */
660  @Override()
661  public boolean includesAuthentication()
662  {
663    return (bindRequest != null);
664  }
665
666
667
668  /**
669   * {@inheritDoc}
670   */
671  @Override()
672  public boolean includesPostConnectProcessing()
673  {
674    return (postConnectProcessor != null);
675  }
676
677
678
679  /**
680   * {@inheritDoc}
681   */
682  @Override()
683  public LDAPConnection getConnection()
684         throws LDAPException
685  {
686    return getConnection(null);
687  }
688
689
690
691  /**
692   * {@inheritDoc}
693   */
694  @Override()
695  public synchronized LDAPConnection getConnection(
696                           final LDAPConnectionPoolHealthCheck healthCheck)
697         throws LDAPException
698  {
699    LDAPException firstException = null;
700
701    final LDAPConnection conn =
702         new LDAPConnection(socketFactory, connectionOptions);
703    for (final InetAddress a : orderAddresses(resolveHostname()))
704    {
705      boolean close = true;
706      try
707      {
708        conn.connect(hostname, a, port,
709             connectionOptions.getConnectTimeoutMillis());
710        doBindPostConnectAndHealthCheckProcessing(conn, bindRequest,
711             postConnectProcessor, healthCheck);
712        close = false;
713        return conn;
714      }
715      catch (final LDAPException le)
716      {
717        Debug.debugException(le);
718        if (firstException == null)
719        {
720          firstException = le;
721        }
722      }
723      finally
724      {
725        if (close)
726        {
727          conn.close();
728        }
729      }
730    }
731
732    throw firstException;
733  }
734
735
736
737  /**
738   * Resolve the hostname to its corresponding addresses.
739   *
740   * @return  The addresses resolved from the hostname.
741   *
742   * @throws  LDAPException  If
743   */
744  InetAddress[] resolveHostname()
745          throws LDAPException
746  {
747    // First, see if we can use the cached addresses.
748    final ObjectPair<InetAddress[],Long> pair =
749         resolvedAddressesWithTimeout.get();
750    if (pair != null)
751    {
752      if (pair.getSecond() <= System.currentTimeMillis())
753      {
754        return pair.getFirst();
755      }
756    }
757
758
759    // Try to resolve the address.
760    InetAddress[] addresses = null;
761    try
762    {
763      if (jndiProperties == null)
764      {
765        addresses = InetAddress.getAllByName(hostname);
766      }
767      else
768      {
769        Attributes attributes = null;
770        final InitialDirContext context = new InitialDirContext(jndiProperties);
771        try
772        {
773          attributes = context.getAttributes(hostname, dnsRecordTypes);
774        }
775        finally
776        {
777          context.close();
778        }
779
780        if (attributes != null)
781        {
782          final ArrayList<InetAddress> addressList =
783               new ArrayList<InetAddress>(10);
784          for (final String recordType : dnsRecordTypes)
785          {
786            final Attribute a = attributes.get(recordType);
787            if (a != null)
788            {
789              final NamingEnumeration<?> values = a.getAll();
790              while (values.hasMore())
791              {
792                final Object value = values.next();
793                addressList.add(getInetAddressForIP(String.valueOf(value)));
794              }
795            }
796          }
797
798          if (! addressList.isEmpty())
799          {
800            addresses = new InetAddress[addressList.size()];
801            addressList.toArray(addresses);
802          }
803        }
804      }
805    }
806    catch (final Exception e)
807    {
808      Debug.debugException(e);
809      addresses = getDefaultAddresses();
810    }
811
812
813    // If we were able to resolve the hostname, then cache and return the
814    // resolved addresses.
815    if ((addresses != null) && (addresses.length > 0))
816    {
817      final long timeoutTime;
818      if (cacheTimeoutMillis > 0L)
819      {
820        timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis;
821      }
822      else
823      {
824        timeoutTime = System.currentTimeMillis() - 1L;
825      }
826
827      resolvedAddressesWithTimeout.set(new ObjectPair<InetAddress[],Long>(
828           addresses, timeoutTime));
829      return addresses;
830    }
831
832
833    // If we've gotten here, then we couldn't resolve the hostname.  If we have
834    // cached addresses, then use them even though the timeout has expired
835    // because that's better than nothing.
836    if (pair != null)
837    {
838      return pair.getFirst();
839    }
840
841    throw new LDAPException(ResultCode.CONNECT_ERROR,
842         ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname));
843  }
844
845
846
847  /**
848   * Orders the provided array of InetAddress objects to reflect the order in
849   * which the addresses should be used to try to create a new connection.
850   *
851   * @param  addresses  The array of addresses to be ordered.
852   *
853   * @return  A list containing the ordered addresses.
854   */
855  List<InetAddress> orderAddresses(final InetAddress[] addresses)
856  {
857    final ArrayList<InetAddress> l =
858         new ArrayList<InetAddress>(addresses.length);
859
860    switch (selectionMode)
861    {
862      case RANDOM:
863        l.addAll(Arrays.asList(addresses));
864        Collections.shuffle(l, ThreadLocalRandom.get());
865        break;
866
867      case ROUND_ROBIN:
868        final int index =
869             (int) (roundRobinCounter.getAndIncrement() % addresses.length);
870        for (int i=index; i < addresses.length; i++)
871        {
872          l.add(addresses[i]);
873        }
874        for (int i=0; i < index; i++)
875        {
876          l.add(addresses[i]);
877        }
878        break;
879
880      case FAILOVER:
881      default:
882        // We'll use the addresses in the same order we originally got them.
883        l.addAll(Arrays.asList(addresses));
884        break;
885    }
886
887    return l;
888  }
889
890
891
892  /**
893   * Retrieves a default set of addresses that may be used for testing.
894   *
895   * @return  A default set of addresses that may be used for testing.
896   */
897  InetAddress[] getDefaultAddresses()
898  {
899    final String defaultAddrsStr =
900         System.getProperty(PROPERTY_DEFAULT_ADDRESSES);
901    if (defaultAddrsStr == null)
902    {
903      return null;
904    }
905
906    final StringTokenizer tokenizer =
907         new StringTokenizer(defaultAddrsStr, " ,");
908    final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()];
909    for (int i=0; i < addresses.length; i++)
910    {
911      try
912      {
913        addresses[i] = getInetAddressForIP(tokenizer.nextToken());
914      }
915      catch (final Exception e)
916      {
917        Debug.debugException(e);
918        return null;
919      }
920    }
921
922    return addresses;
923  }
924
925
926
927  /**
928   * Retrieves an InetAddress object with the configured hostname and the
929   * provided IP address.
930   *
931   * @param  ipAddress  The string representation of the IP address to use in
932   *                    the returned InetAddress.
933   *
934   * @return  The created InetAddress.
935   *
936   * @throws  UnknownHostException  If the provided string does not represent a
937   *                                valid IPv4 or IPv6 address.
938   */
939  private InetAddress getInetAddressForIP(final String ipAddress)
940          throws UnknownHostException
941  {
942    // We want to create an InetAddress that has the provided hostname and the
943    // specified IP address.  To do that, we need to use
944    // InetAddress.getByAddress.  But that requires the IP address to be
945    // specified as a byte array, and the easiest way to convert an IP address
946    // string to a byte array is to use InetAddress.getByName.
947    final InetAddress byName = InetAddress.getByName(String.valueOf(ipAddress));
948    return InetAddress.getByAddress(hostname, byName.getAddress());
949  }
950
951
952
953  /**
954   * {@inheritDoc}
955   */
956  @Override()
957  public void toString(final StringBuilder buffer)
958  {
959    buffer.append("RoundRobinDNSServerSet(hostname='");
960    buffer.append(hostname);
961    buffer.append("', port=");
962    buffer.append(port);
963    buffer.append(", addressSelectionMode=");
964    buffer.append(selectionMode.name());
965    buffer.append(", cacheTimeoutMillis=");
966    buffer.append(cacheTimeoutMillis);
967
968    if (providerURL != null)
969    {
970      buffer.append(", providerURL='");
971      buffer.append(providerURL);
972      buffer.append('\'');
973    }
974
975    buffer.append(", includesAuthentication=");
976    buffer.append(bindRequest != null);
977    buffer.append(", includesPostConnectProcessing=");
978    buffer.append(postConnectProcessor != null);
979    buffer.append(')');
980  }
981}