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