001    /*
002     * Copyright 2014-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2014-2015 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.net.InetAddress;
026    import java.net.UnknownHostException;
027    import java.util.ArrayList;
028    import java.util.Arrays;
029    import java.util.Collections;
030    import java.util.Hashtable;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Properties;
034    import java.util.StringTokenizer;
035    import java.util.concurrent.atomic.AtomicLong;
036    import java.util.concurrent.atomic.AtomicReference;
037    import javax.naming.Context;
038    import javax.naming.NamingEnumeration;
039    import javax.naming.directory.Attribute;
040    import javax.naming.directory.Attributes;
041    import javax.naming.directory.InitialDirContext;
042    import javax.net.SocketFactory;
043    
044    import com.unboundid.util.Debug;
045    import com.unboundid.util.NotMutable;
046    import com.unboundid.util.ObjectPair;
047    import com.unboundid.util.ThreadLocalRandom;
048    import com.unboundid.util.ThreadSafety;
049    import com.unboundid.util.ThreadSafetyLevel;
050    import com.unboundid.util.Validator;
051    
052    import 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)
102    public 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 properties that will be used to initialize the JNDI context, if any.
159      private final Hashtable<String,String> jndiProperties;
160    
161      // The port number for the target server.
162      private final int port;
163    
164      // The set of connection options to use for new connections.
165      private final LDAPConnectionOptions connectionOptions;
166    
167      // The maximum length of time, in milliseconds, to cache resolved addresses.
168      private final long cacheTimeoutMillis;
169    
170      // The socket factory to use to establish connections.
171      private final SocketFactory socketFactory;
172    
173      // The hostname to be resolved.
174      private final String hostname;
175    
176      // The provider URL to use to resolve names, if any.
177      private final String providerURL;
178    
179      // The DNS record types that will be used to obtain the IP addresses for the
180      // specified hostname.
181      private final String[] dnsRecordTypes;
182    
183    
184    
185      /**
186       * Creates a new round-robin DNS server set with the provided information.
187       *
188       * @param  hostname            The hostname to be resolved to one or more
189       *                             addresses.  It must not be {@code null}.
190       * @param  port                The port to use to connect to the server.  Note
191       *                             that even if the provided hostname resolves to
192       *                             multiple addresses, the same port must be used
193       *                             for all addresses.
194       * @param  selectionMode       The selection mode that should be used if the
195       *                             hostname resolves to multiple addresses.  It
196       *                             must not be {@code null}.
197       * @param  cacheTimeoutMillis  The maximum length of time in milliseconds to
198       *                             cache addresses resolved from the provided
199       *                             hostname.  Caching resolved addresses can
200       *                             result in better performance and can reduce the
201       *                             number of requests to the name service.  A
202       *                             that is less than or equal to zero indicates
203       *                             that no caching should be used.
204       * @param  providerURL         The JNDI provider URL that should be used when
205       *                             communicating with the DNS server.  If this is
206       *                             {@code null}, then the underlying system's
207       *                             name service mechanism will be used (which may
208       *                             make use of other services instead of or in
209       *                             addition to DNS).  If this is non-{@code null},
210       *                             then only DNS will be used to perform the name
211       *                             resolution.  A value of "dns:" indicates that
212       *                             the underlying system's DNS configuration
213       *                             should be used.
214       * @param  socketFactory       The socket factory to use to establish the
215       *                             connections.  It may be {@code null} if the
216       *                             JVM-default socket factory should be used.
217       * @param  connectionOptions   The set of connection options that should be
218       *                             used for the connections.  It may be
219       *                             {@code null} if a default set of connection
220       *                             options should be used.
221       */
222      public RoundRobinDNSServerSet(final String hostname, final int port,
223                                    final AddressSelectionMode selectionMode,
224                                    final long cacheTimeoutMillis,
225                                    final String providerURL,
226                                    final SocketFactory socketFactory,
227                                    final LDAPConnectionOptions connectionOptions)
228      {
229        this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL,
230             null, null, socketFactory, connectionOptions);
231      }
232    
233    
234    
235      /**
236       * Creates a new round-robin DNS server set with the provided information.
237       *
238       * @param  hostname            The hostname to be resolved to one or more
239       *                             addresses.  It must not be {@code null}.
240       * @param  port                The port to use to connect to the server.  Note
241       *                             that even if the provided hostname resolves to
242       *                             multiple addresses, the same port must be used
243       *                             for all addresses.
244       * @param  selectionMode       The selection mode that should be used if the
245       *                             hostname resolves to multiple addresses.  It
246       *                             must not be {@code null}.
247       * @param  cacheTimeoutMillis  The maximum length of time in milliseconds to
248       *                             cache addresses resolved from the provided
249       *                             hostname.  Caching resolved addresses can
250       *                             result in better performance and can reduce the
251       *                             number of requests to the name service.  A
252       *                             that is less than or equal to zero indicates
253       *                             that no caching should be used.
254       * @param  providerURL         The JNDI provider URL that should be used when
255       *                             communicating with the DNS server.If both
256       *                             {@code providerURL} and {@code jndiProperties}
257       *                             are {@code null}, then then JNDI will not be
258       *                             used to interact with DNS and the hostname
259       *                             resolution will be performed via the underlying
260       *                             system's name service mechanism (which may make
261       *                             use of other services instead of or in addition
262       *                             to DNS)..  If this is non-{@code null}, then
263       *                             only DNS will be used to perform the name
264       *                             resolution.  A value of "dns:" indicates that
265       *                             the underlying system's DNS configuration
266       *                             should be used.
267       * @param  jndiProperties      A set of JNDI-related properties that should be
268       *                             be used when initializing the context for
269       *                             interacting with the DNS server via JNDI.  If
270       *                             both {@code providerURL} and
271       *                             {@code jndiProperties} are {@code null}, then
272       *                             then JNDI will not be used to interact with
273       *                             DNS and the hostname resolution will be
274       *                             performed via the underlying system's name
275       *                             service mechanism (which may make use of other
276       *                             services instead of or in addition to DNS).  If
277       *                             {@code providerURL} is {@code null} and
278       *                             {@code jndiProperties} is non-{@code null},
279       *                             then the provided properties must specify the
280       *                             URL.
281       * @param  dnsRecordTypes      Specifies the types of DNS records that will be
282       *                             used to obtain the addresses for the specified
283       *                             hostname.  This will only be used if at least
284       *                             one of {@code providerURL} and
285       *                             {@code jndiProperties} is non-{@code null}.  If
286       *                             this is {@code null} or empty, then a default
287       *                             record type of "A" (indicating IPv4 addresses)
288       *                             will be used.
289       * @param  socketFactory       The socket factory to use to establish the
290       *                             connections.  It may be {@code null} if the
291       *                             JVM-default socket factory should be used.
292       * @param  connectionOptions   The set of connection options that should be
293       *                             used for the connections.  It may be
294       *                             {@code null} if a default set of connection
295       *                             options should be used.
296       */
297      public RoundRobinDNSServerSet(final String hostname, final int port,
298                                    final AddressSelectionMode selectionMode,
299                                    final long cacheTimeoutMillis,
300                                    final String providerURL,
301                                    final Properties jndiProperties,
302                                    final String[] dnsRecordTypes,
303                                    final SocketFactory socketFactory,
304                                    final LDAPConnectionOptions connectionOptions)
305      {
306        Validator.ensureNotNull(hostname);
307        Validator.ensureTrue((port >= 1) && (port <= 65535));
308        Validator.ensureNotNull(selectionMode);
309    
310        this.hostname      = hostname;
311        this.port          = port;
312        this.selectionMode = selectionMode;
313        this.providerURL   = providerURL;
314    
315        if (jndiProperties == null)
316        {
317          if (providerURL == null)
318          {
319            this.jndiProperties = null;
320          }
321          else
322          {
323            this.jndiProperties = new Hashtable<String,String>(2);
324            this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
325                 "com.sun.jndi.dns.DnsContextFactory");
326            this.jndiProperties.put(Context.PROVIDER_URL, providerURL);
327          }
328        }
329        else
330        {
331          this.jndiProperties =
332               new Hashtable<String,String>(jndiProperties.size()+2);
333          for (final Map.Entry<Object,Object> e : jndiProperties.entrySet())
334          {
335            this.jndiProperties.put(String.valueOf(e.getKey()),
336                 String.valueOf(e.getValue()));
337          }
338    
339          if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY))
340          {
341            this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,
342                 "com.sun.jndi.dns.DnsContextFactory");
343          }
344    
345          if ((! this.jndiProperties.containsKey(Context.PROVIDER_URL)) &&
346             (providerURL != null))
347          {
348            this.jndiProperties.put(Context.PROVIDER_URL, providerURL);
349          }
350        }
351    
352        if (dnsRecordTypes == null)
353        {
354          this.dnsRecordTypes = new String[] { "A" };
355        }
356        else
357        {
358          this.dnsRecordTypes = dnsRecordTypes;
359        }
360    
361        if (cacheTimeoutMillis > 0L)
362        {
363          this.cacheTimeoutMillis = cacheTimeoutMillis;
364        }
365        else
366        {
367          this.cacheTimeoutMillis = 0L;
368        }
369    
370        if (socketFactory == null)
371        {
372          this.socketFactory = SocketFactory.getDefault();
373        }
374        else
375        {
376          this.socketFactory = socketFactory;
377        }
378    
379        if (connectionOptions == null)
380        {
381          this.connectionOptions = new LDAPConnectionOptions();
382        }
383        else
384        {
385          this.connectionOptions = connectionOptions;
386        }
387    
388        roundRobinCounter = new AtomicLong(0L);
389        resolvedAddressesWithTimeout =
390             new AtomicReference<ObjectPair<InetAddress[],Long>>();
391      }
392    
393    
394    
395      /**
396       * Retrieves the hostname to be resolved.
397       *
398       * @return  The hostname to be resolved.
399       */
400      public String getHostname()
401      {
402        return hostname;
403      }
404    
405    
406    
407      /**
408       * Retrieves the port to use to connect to the server.
409       *
410       * @return  The port to use to connect to the server.
411       */
412      public int getPort()
413      {
414        return port;
415      }
416    
417    
418    
419      /**
420       * Retrieves the address selection mode that should be used if the provided
421       * hostname resolves to multiple addresses.
422       *
423       * @return  The address selection
424       */
425      public AddressSelectionMode getAddressSelectionMode()
426      {
427        return selectionMode;
428      }
429    
430    
431    
432      /**
433       * Retrieves the length of time in milliseconds that resolved addresses may be
434       * cached.
435       *
436       * @return  The length of time in milliseconds that resolved addresses may be
437       *          cached, or zero if no caching should be performed.
438       */
439      public long getCacheTimeoutMillis()
440      {
441        return cacheTimeoutMillis;
442      }
443    
444    
445    
446      /**
447       * Retrieves the provider URL that should be used when interacting with DNS to
448       * resolve the hostname to its corresponding addresses.
449       *
450       * @return  The provider URL that should be used when interacting with DNS to
451       *          resolve the hostname to its corresponding addresses, or
452       *          {@code null} if the system's configured naming service should be
453       *          used.
454       */
455      public String getProviderURL()
456      {
457        return providerURL;
458      }
459    
460    
461    
462      /**
463       * Retrieves an unmodifiable map of properties that will be used to initialize
464       * the JNDI context used to interact with DNS.  Note that the map returned
465       * will reflect the actual properties that will be used, and may not exactly
466       * match the properties provided when creating this server set.
467       *
468       * @return  An unmodifiable map of properties that will be used to initialize
469       *          the JNDI context used to interact with DNS, or {@code null} if
470       *          JNDI will nto be used to interact with DNS.
471       */
472      public Map<String,String> getJNDIProperties()
473      {
474        if (jndiProperties == null)
475        {
476          return null;
477        }
478        else
479        {
480          return Collections.unmodifiableMap(jndiProperties);
481        }
482      }
483    
484    
485    
486      /**
487       * Retrieves an array of record types that will be requested if JNDI will be
488       * used to interact with DNS.
489       *
490       * @return  An array of record types that will be requested if JNDI will be
491       *          used to interact with DNS.
492       */
493      public String[] getDNSRecordTypes()
494      {
495        return dnsRecordTypes;
496      }
497    
498    
499    
500      /**
501       * Retrieves the socket factory that will be used to establish connections.
502       * This will not be {@code null}, even if no socket factory was provided when
503       * the server set was created.
504       *
505       * @return  The socket factory that will be used to establish connections.
506       */
507      public SocketFactory getSocketFactory()
508      {
509        return socketFactory;
510      }
511    
512    
513    
514      /**
515       * Retrieves the set of connection options that will be used for underlying
516       * connections.  This will not be {@code null}, even if no connection options
517       * object was provided when the server set was created.
518       *
519       * @return  The set of connection options that will be used for underlying
520       *          connections.
521       */
522      public LDAPConnectionOptions getConnectionOptions()
523      {
524        return connectionOptions;
525      }
526    
527    
528    
529      /**
530       * {@inheritDoc}
531       */
532      @Override()
533      public LDAPConnection getConnection()
534             throws LDAPException
535      {
536        return getConnection(null);
537      }
538    
539    
540    
541      /**
542       * {@inheritDoc}
543       */
544      @Override()
545      public synchronized LDAPConnection getConnection(
546                               final LDAPConnectionPoolHealthCheck healthCheck)
547             throws LDAPException
548      {
549        LDAPException firstException = null;
550    
551        final LDAPConnection conn =
552             new LDAPConnection(socketFactory, connectionOptions);
553        for (final InetAddress a : orderAddresses(resolveHostname()))
554        {
555          boolean close = true;
556          try
557          {
558            conn.connect(hostname, a, port,
559                 connectionOptions.getConnectTimeoutMillis());
560            if (healthCheck != null)
561            {
562              healthCheck.ensureNewConnectionValid(conn);
563            }
564            close = false;
565            return conn;
566          }
567          catch (final LDAPException le)
568          {
569            Debug.debugException(le);
570            if (firstException == null)
571            {
572              firstException = le;
573            }
574          }
575          finally
576          {
577            if (close)
578            {
579              conn.close();
580            }
581          }
582        }
583    
584        throw firstException;
585      }
586    
587    
588    
589      /**
590       * Resolve the hostname to its corresponding addresses.
591       *
592       * @return  The addresses resolved from the hostname.
593       *
594       * @throws  LDAPException  If
595       */
596      InetAddress[] resolveHostname()
597              throws LDAPException
598      {
599        // First, see if we can use the cached addresses.
600        final ObjectPair<InetAddress[],Long> pair =
601             resolvedAddressesWithTimeout.get();
602        if (pair != null)
603        {
604          if (pair.getSecond() <= System.currentTimeMillis())
605          {
606            return pair.getFirst();
607          }
608        }
609    
610    
611        // Try to resolve the address.
612        InetAddress[] addresses = null;
613        try
614        {
615          if (jndiProperties == null)
616          {
617            addresses = InetAddress.getAllByName(hostname);
618          }
619          else
620          {
621            Attributes attributes = null;
622            final InitialDirContext context = new InitialDirContext(jndiProperties);
623            try
624            {
625              attributes = context.getAttributes(hostname, dnsRecordTypes);
626            }
627            finally
628            {
629              context.close();
630            }
631    
632            if (attributes != null)
633            {
634              final ArrayList<InetAddress> addressList =
635                   new ArrayList<InetAddress>(10);
636              for (final String recordType : dnsRecordTypes)
637              {
638                final Attribute a = attributes.get(recordType);
639                if (a != null)
640                {
641                  final NamingEnumeration<?> values = a.getAll();
642                  while (values.hasMore())
643                  {
644                    final Object value = values.next();
645                    addressList.add(getInetAddressForIP(String.valueOf(value)));
646                  }
647                }
648              }
649    
650              if (! addressList.isEmpty())
651              {
652                addresses = new InetAddress[addressList.size()];
653                addressList.toArray(addresses);
654              }
655            }
656          }
657        }
658        catch (final Exception e)
659        {
660          Debug.debugException(e);
661          addresses = getDefaultAddresses();
662        }
663    
664    
665        // If we were able to resolve the hostname, then cache and return the
666        // resolved addresses.
667        if ((addresses != null) && (addresses.length > 0))
668        {
669          final long timeoutTime;
670          if (cacheTimeoutMillis > 0L)
671          {
672            timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis;
673          }
674          else
675          {
676            timeoutTime = System.currentTimeMillis() - 1L;
677          }
678    
679          resolvedAddressesWithTimeout.set(new ObjectPair<InetAddress[],Long>(
680               addresses, timeoutTime));
681          return addresses;
682        }
683    
684    
685        // If we've gotten here, then we couldn't resolve the hostname.  If we have
686        // cached addresses, then use them even though the timeout has expired
687        // because that's better than nothing.
688        if (pair != null)
689        {
690          return pair.getFirst();
691        }
692    
693        throw new LDAPException(ResultCode.CONNECT_ERROR,
694             ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname));
695      }
696    
697    
698    
699      /**
700       * Orders the provided array of InetAddress objects to reflect the order in
701       * which the addresses should be used to try to create a new connection.
702       *
703       * @param  addresses  The array of addresses to be ordered.
704       *
705       * @return  A list containing the ordered addresses.
706       */
707      List<InetAddress> orderAddresses(final InetAddress[] addresses)
708      {
709        final ArrayList<InetAddress> l =
710             new ArrayList<InetAddress>(addresses.length);
711    
712        switch (selectionMode)
713        {
714          case RANDOM:
715            l.addAll(Arrays.asList(addresses));
716            Collections.shuffle(l, ThreadLocalRandom.get());
717            break;
718    
719          case ROUND_ROBIN:
720            final int index =
721                 (int) (roundRobinCounter.getAndIncrement() % addresses.length);
722            for (int i=index; i < addresses.length; i++)
723            {
724              l.add(addresses[i]);
725            }
726            for (int i=0; i < index; i++)
727            {
728              l.add(addresses[i]);
729            }
730            break;
731    
732          case FAILOVER:
733          default:
734            // We'll use the addresses in the same order we originally got them.
735            l.addAll(Arrays.asList(addresses));
736            break;
737        }
738    
739        return l;
740      }
741    
742    
743    
744      /**
745       * Retrieves a default set of addresses that may be used for testing.
746       *
747       * @return  A default set of addresses that may be used for testing.
748       */
749      InetAddress[] getDefaultAddresses()
750      {
751        final String defaultAddrsStr =
752             System.getProperty(PROPERTY_DEFAULT_ADDRESSES);
753        if (defaultAddrsStr == null)
754        {
755          return null;
756        }
757    
758        final StringTokenizer tokenizer =
759             new StringTokenizer(defaultAddrsStr, " ,");
760        final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()];
761        for (int i=0; i < addresses.length; i++)
762        {
763          try
764          {
765            addresses[i] = getInetAddressForIP(tokenizer.nextToken());
766          }
767          catch (final Exception e)
768          {
769            Debug.debugException(e);
770            return null;
771          }
772        }
773    
774        return addresses;
775      }
776    
777    
778    
779      /**
780       * Retrieves an InetAddress object with the configured hostname and the
781       * provided IP address.
782       *
783       * @param  ipAddress  The string representation of the IP address to use in
784       *                    the returned InetAddress.
785       *
786       * @return  The created InetAddress.
787       *
788       * @throws  UnknownHostException  If the provided string does not represent a
789       *                                valid IPv4 or IPv6 address.
790       */
791      private InetAddress getInetAddressForIP(final String ipAddress)
792              throws UnknownHostException
793      {
794        // We want to create an InetAddress that has the provided hostname and the
795        // specified IP address.  To do that, we need to use
796        // InetAddress.getByAddress.  But that requires the IP address to be
797        // specified as a byte array, and the easiest way to convert an IP address
798        // string to a byte array is to use InetAddress.getByName.
799        final InetAddress byName = InetAddress.getByName(String.valueOf(ipAddress));
800        return InetAddress.getByAddress(hostname, byName.getAddress());
801      }
802    
803    
804    
805      /**
806       * {@inheritDoc}
807       */
808      @Override()
809      public void toString(final StringBuilder buffer)
810      {
811        buffer.append("RoundRobinDNSServerSet(hostname='");
812        buffer.append(hostname);
813        buffer.append("', port=");
814        buffer.append(port);
815        buffer.append(", addressSelectionMode=");
816        buffer.append(selectionMode.name());
817        buffer.append(", cacheTimeoutMillis=");
818        buffer.append(cacheTimeoutMillis);
819    
820        if (providerURL != null)
821        {
822          buffer.append(", providerURL='");
823          buffer.append(providerURL);
824          buffer.append('\'');
825        }
826    
827        buffer.append(')');
828      }
829    }