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