001/*
002 * Copyright 2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2014 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 */
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 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}