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