View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver.dns;
17  
18  import io.netty.channel.ChannelFactory;
19  import io.netty.channel.EventLoop;
20  import io.netty.channel.ReflectiveChannelFactory;
21  import io.netty.channel.socket.DatagramChannel;
22  import io.netty.channel.socket.InternetProtocolFamily;
23  import io.netty.channel.socket.SocketChannel;
24  import io.netty.channel.socket.SocketProtocolFamily;
25  import io.netty.resolver.HostsFileEntriesResolver;
26  import io.netty.resolver.ResolvedAddressTypes;
27  import io.netty.util.concurrent.Future;
28  import io.netty.util.internal.EmptyArrays;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  import java.net.InetSocketAddress;
34  import java.net.SocketAddress;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.List;
38  
39  import static io.netty.util.internal.ObjectUtil.checkNotNull;
40  import static io.netty.util.internal.ObjectUtil.intValue;
41  
42  /**
43   * A {@link DnsNameResolver} builder.
44   */
45  public final class DnsNameResolverBuilder {
46  
47      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolverBuilder.class);
48      static final SocketAddress DEFAULT_LOCAL_ADDRESS = new InetSocketAddress(0);
49  
50      volatile EventLoop eventLoop;
51      private ChannelFactory<? extends DatagramChannel> datagramChannelFactory;
52      private ChannelFactory<? extends SocketChannel> socketChannelFactory;
53      private boolean retryOnTimeout;
54  
55      private DnsCache resolveCache;
56      private DnsCnameCache cnameCache;
57      private AuthoritativeDnsServerCache authoritativeDnsServerCache;
58      private SocketAddress localAddress = DEFAULT_LOCAL_ADDRESS;
59      private Integer minTtl;
60      private Integer maxTtl;
61      private Integer negativeTtl;
62      private long queryTimeoutMillis = -1;
63      private ResolvedAddressTypes resolvedAddressTypes = DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
64      private boolean completeOncePreferredResolved;
65      private boolean recursionDesired = true;
66      private int maxQueriesPerResolve = -1;
67      private boolean traceEnabled;
68      private int maxPayloadSize = 4096;
69      private boolean optResourceEnabled = true;
70      private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
71      private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
72              DnsServerAddressStreamProviders.platformDefault();
73      private DnsServerAddressStream queryDnsServerAddressStream;
74      private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
75              NoopDnsQueryLifecycleObserverFactory.INSTANCE;
76      private String[] searchDomains;
77      private int ndots = -1;
78      private boolean decodeIdn = true;
79  
80      private int maxNumConsolidation;
81      private DnsNameResolverChannelStrategy datagramChannelStrategy = DnsNameResolverChannelStrategy.ChannelPerResolver;
82  
83      /**
84       * Creates a new builder.
85       */
86      public DnsNameResolverBuilder() {
87      }
88  
89      /**
90       * Creates a new builder.
91       *
92       * @param eventLoop the {@link EventLoop} which will perform the communication with the DNS
93       * servers.
94       */
95      public DnsNameResolverBuilder(EventLoop eventLoop) {
96          eventLoop(eventLoop);
97      }
98  
99      /**
100      * Sets the {@link EventLoop} which will perform the communication with the DNS servers.
101      *
102      * @param eventLoop the {@link EventLoop}
103      * @return {@code this}
104      */
105     public DnsNameResolverBuilder eventLoop(EventLoop eventLoop) {
106         this.eventLoop = eventLoop;
107         return this;
108     }
109 
110     @Deprecated
111     protected ChannelFactory<? extends DatagramChannel> channelFactory() {
112         return this.datagramChannelFactory;
113     }
114 
115     ChannelFactory<? extends DatagramChannel> datagramChannelFactory() {
116         return this.datagramChannelFactory;
117     }
118 
119     /**
120      * Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
121      * <p>
122      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
123      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
124      *
125      * @param datagramChannelFactory the {@link ChannelFactory}
126      * @return {@code this}
127      * @deprecated use {@link #datagramChannelFactory(ChannelFactory)}
128      */
129     @Deprecated
130     public DnsNameResolverBuilder channelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
131         datagramChannelFactory(datagramChannelFactory);
132         return this;
133     }
134 
135     /**
136      * Sets the {@link ChannelFactory} that will create a {@link DatagramChannel}.
137      * <p>
138      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
139      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
140      *
141      * @param datagramChannelFactory the {@link ChannelFactory}
142      * @return {@code this}
143      */
144     public DnsNameResolverBuilder datagramChannelFactory(
145             ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
146         this.datagramChannelFactory = datagramChannelFactory;
147         return this;
148     }
149 
150     /**
151      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
152      * Use as an alternative to {@link #channelFactory(ChannelFactory)}.
153      * <p>
154      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
155      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
156      *
157      * @param channelType the type
158      * @return {@code this}
159      * @deprecated use {@link #datagramChannelType(Class)}
160      */
161     @Deprecated
162     public DnsNameResolverBuilder channelType(Class<? extends DatagramChannel> channelType) {
163         return datagramChannelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
164     }
165 
166     /**
167      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type.
168      * Use as an alternative to {@link #datagramChannelFactory(ChannelFactory)}.
169      * <p>
170      * If <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should be supported as well it is required
171      * to call the {@link #socketChannelFactory(ChannelFactory) or {@link #socketChannelType(Class)}} method.
172      *
173      * @param channelType the type
174      * @return {@code this}
175      */
176     public DnsNameResolverBuilder datagramChannelType(Class<? extends DatagramChannel> channelType) {
177         return datagramChannelFactory(new ReflectiveChannelFactory<DatagramChannel>(channelType));
178     }
179 
180     /**
181      * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
182      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
183      * <p>
184      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
185      * {@link ChannelFactory} for this method.
186      *
187      * @param channelFactory the {@link ChannelFactory} or {@code null}
188      *                       if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
189      *                       By default, TCP fallback is not enabled.
190      * @return {@code this}
191      */
192     public DnsNameResolverBuilder socketChannelFactory(ChannelFactory<? extends SocketChannel> channelFactory) {
193         return socketChannelFactory(channelFactory, false);
194     }
195 
196     /**
197      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
198      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
199      * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
200      * <p>
201      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
202      * {@code channelType} for this method.
203      *
204      * @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
205      *                    should not be supported. By default, TCP fallback is not enabled.
206      * @return {@code this}
207      */
208     public DnsNameResolverBuilder socketChannelType(Class<? extends SocketChannel> channelType) {
209         return socketChannelType(channelType, false);
210     }
211 
212     /**
213      * Sets the {@link ChannelFactory} that will create a {@link SocketChannel} for
214      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
215      * <p>
216      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
217      * {@link ChannelFactory} for this method.
218      *
219      * @param channelFactory the {@link ChannelFactory} or {@code null}
220      *                       if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> should not be supported.
221      *                       By default, TCP fallback is not enabled.
222      * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
223      *                       was detected, if {@code false} it will only try to use TCP if the response was marked
224      *                       as truncated.
225      * @return {@code this}
226      */
227     public DnsNameResolverBuilder socketChannelFactory(
228             ChannelFactory<? extends SocketChannel> channelFactory, boolean retryOnTimeout) {
229         this.socketChannelFactory = channelFactory;
230         this.retryOnTimeout = retryOnTimeout;
231         return this;
232     }
233 
234     /**
235      * Sets the {@link ChannelFactory} as a {@link ReflectiveChannelFactory} of this type for
236      * <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a> if needed.
237      * Use as an alternative to {@link #socketChannelFactory(ChannelFactory)}.
238      * <p>
239      * TCP fallback is <strong>not</strong> enabled by default and must be enabled by providing a non-null
240      * {@code channelType} for this method.
241      *
242      * @param channelType the type or {@code null} if <a href="https://tools.ietf.org/html/rfc7766">TCP fallback</a>
243      *                    should not be supported. By default, TCP fallback is not enabled.
244      * @param retryOnTimeout if {@code true} the {@link DnsNameResolver} will also fallback to TCP if a timeout
245      *                       was detected, if {@code false} it will only try to use TCP if the response was marked
246      *                       as truncated.
247      * @return {@code this}
248      */
249     public DnsNameResolverBuilder socketChannelType(
250             Class<? extends SocketChannel> channelType, boolean retryOnTimeout) {
251         if (channelType == null) {
252             return socketChannelFactory(null, retryOnTimeout);
253         }
254         return socketChannelFactory(new ReflectiveChannelFactory<SocketChannel>(channelType), retryOnTimeout);
255     }
256 
257     /**
258      * Sets the cache for resolution results.
259      *
260      * @param resolveCache the DNS resolution results cache
261      * @return {@code this}
262      */
263     public DnsNameResolverBuilder resolveCache(DnsCache resolveCache) {
264         this.resolveCache  = resolveCache;
265         return this;
266     }
267 
268     /**
269      * Sets the cache for {@code CNAME} mappings.
270      *
271      * @param cnameCache the cache used to cache {@code CNAME} mappings for a domain.
272      * @return {@code this}
273      */
274     public DnsNameResolverBuilder cnameCache(DnsCnameCache cnameCache) {
275         this.cnameCache  = cnameCache;
276         return this;
277     }
278 
279     /**
280      * Set the factory used to generate objects which can observe individual DNS queries.
281      * @param lifecycleObserverFactory the factory used to generate objects which can observe individual DNS queries.
282      * @return {@code this}
283      */
284     public DnsNameResolverBuilder dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory
285                                                                            lifecycleObserverFactory) {
286         this.dnsQueryLifecycleObserverFactory = checkNotNull(lifecycleObserverFactory, "lifecycleObserverFactory");
287         return this;
288     }
289 
290     /**
291      * Sets the cache for authoritative NS servers
292      *
293      * @param authoritativeDnsServerCache the authoritative NS servers cache
294      * @return {@code this}
295      * @deprecated Use {@link #authoritativeDnsServerCache(AuthoritativeDnsServerCache)}
296      */
297     @Deprecated
298     public DnsNameResolverBuilder authoritativeDnsServerCache(DnsCache authoritativeDnsServerCache) {
299         this.authoritativeDnsServerCache = new AuthoritativeDnsServerCacheAdapter(authoritativeDnsServerCache);
300         return this;
301     }
302 
303     /**
304      * Sets the cache for authoritative NS servers
305      *
306      * @param authoritativeDnsServerCache the authoritative NS servers cache
307      * @return {@code this}
308      */
309     public DnsNameResolverBuilder authoritativeDnsServerCache(AuthoritativeDnsServerCache authoritativeDnsServerCache) {
310         this.authoritativeDnsServerCache = authoritativeDnsServerCache;
311         return this;
312     }
313 
314     /**
315      * Configure the address that will be used to bind too. If {@code null} the default will be used.
316      * @param localAddress the bind address
317      * @return {@code this}
318      */
319     public DnsNameResolverBuilder localAddress(SocketAddress localAddress) {
320         this.localAddress = localAddress;
321         return this;
322     }
323 
324     /**
325      * Sets the minimum and maximum TTL of the cached DNS resource records (in seconds). If the TTL of the DNS
326      * resource record returned by the DNS server is less than the minimum TTL or greater than the maximum TTL,
327      * this resolver will ignore the TTL from the DNS server and use the minimum TTL or the maximum TTL instead
328      * respectively.
329      * The default value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
330      * respect the TTL from the DNS server.
331      *
332      * @param minTtl the minimum TTL
333      * @param maxTtl the maximum TTL
334      * @return {@code this}
335      */
336     public DnsNameResolverBuilder ttl(int minTtl, int maxTtl) {
337         this.maxTtl = maxTtl;
338         this.minTtl = minTtl;
339         return this;
340     }
341 
342     /**
343      * Sets the TTL of the cache for the failed DNS queries (in seconds).
344      *
345      * @param negativeTtl the TTL for failed cached queries
346      * @return {@code this}
347      */
348     public DnsNameResolverBuilder negativeTtl(int negativeTtl) {
349         this.negativeTtl = negativeTtl;
350         return this;
351     }
352 
353     /**
354      * Sets the timeout of each DNS query performed by this resolver (in milliseconds).
355      * {@code 0} disables the timeout. If not set or a negative number is set, the default timeout is used.
356      *
357      * @param queryTimeoutMillis the query timeout
358      * @return {@code this}
359      */
360     public DnsNameResolverBuilder queryTimeoutMillis(long queryTimeoutMillis) {
361         this.queryTimeoutMillis = queryTimeoutMillis;
362         return this;
363     }
364 
365     /**
366      * Compute a {@link ResolvedAddressTypes} from some {@link InternetProtocolFamily}s.
367      * An empty input will return the default value, based on "java.net" System properties.
368      * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
369      *
370      * @param internetProtocolFamilies a valid sequence of {@link InternetProtocolFamily}s
371      * @return a {@link ResolvedAddressTypes}
372      * @deprecated use {@link #toResolvedAddressTypes(SocketProtocolFamily...)}
373      */
374     @Deprecated
375     public static ResolvedAddressTypes computeResolvedAddressTypes(InternetProtocolFamily... internetProtocolFamilies) {
376         if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
377             return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
378         }
379         if (internetProtocolFamilies.length > 2) {
380             throw new IllegalArgumentException("No more than 2 InternetProtocolFamilies");
381         }
382         return toResolvedAddressTypes(toSocketProtocolFamilies(internetProtocolFamilies));
383     }
384 
385     private static SocketProtocolFamily[] toSocketProtocolFamilies(InternetProtocolFamily... internetProtocolFamilies) {
386         if (internetProtocolFamilies == null || internetProtocolFamilies.length == 0) {
387             return null;
388         }
389         SocketProtocolFamily[] socketProtocolFamilies = new SocketProtocolFamily[internetProtocolFamilies.length];
390         for (int i = 0; i < internetProtocolFamilies.length; i++) {
391             socketProtocolFamilies[i] = internetProtocolFamilies[i].toSocketProtocolFamily();
392         }
393         return socketProtocolFamilies;
394     }
395 
396     /**
397      * Compute a {@link ResolvedAddressTypes} from some {@link SocketProtocolFamily}s.
398      * An empty input will return the default value, based on "java.net" System properties.
399      * Valid inputs are (), (IPv4), (IPv6), (Ipv4, IPv6) and (IPv6, IPv4).
400      * @param socketProtocolFamilies a valid sequence of {@link SocketProtocolFamily}s
401      * @return a {@link ResolvedAddressTypes}
402      */
403     public static ResolvedAddressTypes toResolvedAddressTypes(SocketProtocolFamily... socketProtocolFamilies) {
404         if (socketProtocolFamilies == null || socketProtocolFamilies.length == 0) {
405             return DnsNameResolver.DEFAULT_RESOLVE_ADDRESS_TYPES;
406         }
407         if (socketProtocolFamilies.length > 2) {
408             throw new IllegalArgumentException("No more than 2 socketProtocolFamilies");
409         }
410 
411         switch(socketProtocolFamilies[0]) {
412             case INET:
413                 return (socketProtocolFamilies.length >= 2
414                         && socketProtocolFamilies[1] == SocketProtocolFamily.INET6) ?
415                         ResolvedAddressTypes.IPV4_PREFERRED: ResolvedAddressTypes.IPV4_ONLY;
416             case INET6:
417                 return (socketProtocolFamilies.length >= 2
418                         && socketProtocolFamilies[1] == SocketProtocolFamily.INET) ?
419                         ResolvedAddressTypes.IPV6_PREFERRED: ResolvedAddressTypes.IPV6_ONLY;
420             default:
421                 throw new IllegalArgumentException(
422                         "Couldn't resolve ResolvedAddressTypes from InternetProtocolFamily array");
423         }
424     }
425 
426     /**
427      * Sets the list of the protocol families of the address resolved.
428      * You can use {@link DnsNameResolverBuilder#computeResolvedAddressTypes(InternetProtocolFamily...)}
429      * to get a {@link ResolvedAddressTypes} out of some {@link InternetProtocolFamily}s.
430      *
431      * @param resolvedAddressTypes the address types
432      * @return {@code this}
433      */
434     public DnsNameResolverBuilder resolvedAddressTypes(ResolvedAddressTypes resolvedAddressTypes) {
435         this.resolvedAddressTypes = resolvedAddressTypes;
436         return this;
437     }
438 
439     /**
440      * If {@code true} {@link DnsNameResolver#resolveAll(String)} will notify the returned {@link Future} as
441      * soon as all queries for the preferred address-type are complete.
442      *
443      * @param completeOncePreferredResolved {@code true} to enable, {@code false} to disable.
444      * @return {@code this}
445      */
446     public DnsNameResolverBuilder completeOncePreferredResolved(boolean completeOncePreferredResolved) {
447         this.completeOncePreferredResolved = completeOncePreferredResolved;
448         return this;
449     }
450 
451     /**
452      * Sets if this resolver has to send a DNS query with the RD (recursion desired) flag set.
453      *
454      * @param recursionDesired true if recursion is desired
455      * @return {@code this}
456      */
457     public DnsNameResolverBuilder recursionDesired(boolean recursionDesired) {
458         this.recursionDesired = recursionDesired;
459         return this;
460     }
461 
462     /**
463      * Sets the maximum allowed number of DNS queries to send when resolving a host name.
464      *
465      * @param maxQueriesPerResolve the max number of queries
466      * @return {@code this}
467      */
468     public DnsNameResolverBuilder maxQueriesPerResolve(int maxQueriesPerResolve) {
469         this.maxQueriesPerResolve = maxQueriesPerResolve;
470         return this;
471     }
472 
473     /**
474      * Sets if this resolver should generate the detailed trace information in an exception message so that
475      * it is easier to understand the cause of resolution failure.
476      *
477      * @param traceEnabled true if trace is enabled
478      * @return {@code this}
479      * @deprecated Prefer to {@linkplain #dnsQueryLifecycleObserverFactory(DnsQueryLifecycleObserverFactory) configure}
480      * a {@link LoggingDnsQueryLifeCycleObserverFactory} instead.
481      */
482     @Deprecated
483     public DnsNameResolverBuilder traceEnabled(boolean traceEnabled) {
484         this.traceEnabled = traceEnabled;
485         return this;
486     }
487 
488     /**
489      * Sets the capacity of the datagram packet buffer (in bytes).  The default value is {@code 4096} bytes.
490      *
491      * @param maxPayloadSize the capacity of the datagram packet buffer
492      * @return {@code this}
493      */
494     public DnsNameResolverBuilder maxPayloadSize(int maxPayloadSize) {
495         this.maxPayloadSize = maxPayloadSize;
496         return this;
497     }
498 
499     /**
500      * Enable the automatic inclusion of a optional records that tries to give the remote DNS server a hint about
501      * how much data the resolver can read per response. Some DNSServer may not support this and so fail to answer
502      * queries. If you find problems you may want to disable this.
503      *
504      * @param optResourceEnabled if optional records inclusion is enabled
505      * @return {@code this}
506      */
507     public DnsNameResolverBuilder optResourceEnabled(boolean optResourceEnabled) {
508         this.optResourceEnabled = optResourceEnabled;
509         return this;
510     }
511 
512     /**
513      * @param hostsFileEntriesResolver the {@link HostsFileEntriesResolver} used to first check
514      *                                 if the hostname is locally aliased.
515      * @return {@code this}
516      */
517     public DnsNameResolverBuilder hostsFileEntriesResolver(HostsFileEntriesResolver hostsFileEntriesResolver) {
518         this.hostsFileEntriesResolver = hostsFileEntriesResolver;
519         return this;
520     }
521 
522     protected DnsServerAddressStreamProvider nameServerProvider() {
523         return this.dnsServerAddressStreamProvider;
524     }
525 
526     /**
527      * Set the {@link DnsServerAddressStreamProvider} which is used to determine which DNS server is used to resolve
528      * each hostname.
529      * @return {@code this}.
530      */
531     public DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
532         this.dnsServerAddressStreamProvider =
533                 checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
534         return this;
535     }
536 
537     protected DnsServerAddressStream queryServerAddressStream() {
538         return this.queryDnsServerAddressStream;
539     }
540 
541     /**
542      * Set the {@link DnsServerAddressStream} which provides the server address for DNS queries.
543      * @return {@code this}.
544      */
545     public DnsNameResolverBuilder queryServerAddressStream(DnsServerAddressStream queryServerAddressStream) {
546         this.queryDnsServerAddressStream =
547                 checkNotNull(queryServerAddressStream, "queryServerAddressStream");
548         return this;
549     }
550 
551     /**
552      * Set the list of search domains of the resolver.
553      *
554      * @param searchDomains the search domains
555      * @return {@code this}
556      */
557     public DnsNameResolverBuilder searchDomains(Iterable<String> searchDomains) {
558         checkNotNull(searchDomains, "searchDomains");
559 
560         final List<String> list = new ArrayList<String>(4);
561 
562         for (String f : searchDomains) {
563             if (f == null) {
564                 break;
565             }
566 
567             // Avoid duplicate entries.
568             if (list.contains(f)) {
569                 continue;
570             }
571 
572             list.add(f);
573         }
574 
575         this.searchDomains = list.toArray(EmptyArrays.EMPTY_STRINGS);
576         return this;
577     }
578 
579   /**
580    * Set the number of dots which must appear in a name before an initial absolute query is made.
581    * The default value is {@code 1}.
582    *
583    * @param ndots the ndots value
584    * @return {@code this}
585    */
586     public DnsNameResolverBuilder ndots(int ndots) {
587         this.ndots = ndots;
588         return this;
589     }
590 
591    DnsCache getOrNewCache() {
592         if (this.resolveCache != null) {
593             return this.resolveCache;
594         }
595         return new DefaultDnsCache(intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE), intValue(negativeTtl, 0));
596     }
597 
598    AuthoritativeDnsServerCache getOrNewAuthoritativeDnsServerCache() {
599         if (this.authoritativeDnsServerCache != null) {
600             return this.authoritativeDnsServerCache;
601         }
602         return new DefaultAuthoritativeDnsServerCache(
603                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE),
604                 // Let us use the sane ordering as DnsNameResolver will be used when returning
605                 // nameservers from the cache.
606                 new NameServerComparator(DnsNameResolver.addressType(
607                         DnsNameResolver.preferredAddressType(resolvedAddressTypes))));
608     }
609 
610     private DnsServerAddressStream newQueryServerAddressStream(
611             DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
612         return new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider);
613     }
614 
615    DnsCnameCache getOrNewCnameCache() {
616         if (this.cnameCache != null) {
617             return this.cnameCache;
618         }
619         return new DefaultDnsCnameCache(
620                 intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE));
621     }
622 
623     /**
624      * Set if domain / host names should be decoded to unicode when received.
625      * See <a href="https://tools.ietf.org/html/rfc3492">rfc3492</a>.
626      *
627      * @param decodeIdn if should get decoded
628      * @return {@code this}
629      */
630     public DnsNameResolverBuilder decodeIdn(boolean decodeIdn) {
631         this.decodeIdn = decodeIdn;
632         return this;
633     }
634 
635     /**
636      * Set the maximum size of the cache that is used to consolidate lookups for different hostnames when in-flight.
637      * This means if multiple lookups are done for the same hostname and still in-flight only one actual query will
638      * be made and the result will be cascaded to the others.
639      *
640      * @param maxNumConsolidation the maximum lookups to consolidate (different hostnames), or {@code 0} if
641      *                            no consolidation should be performed.
642      * @return {@code this}
643      */
644     public DnsNameResolverBuilder consolidateCacheSize(int maxNumConsolidation) {
645         this.maxNumConsolidation = ObjectUtil.checkPositiveOrZero(maxNumConsolidation, "maxNumConsolidation");
646         return this;
647     }
648 
649     /**
650      * Set the strategy that is used to determine how a {@link DatagramChannel} is used by the resolver for sending
651      * queries over UDP protocol.
652      *
653      * @param datagramChannelStrategy  the {@link DnsNameResolverChannelStrategy} to use when doing queries over
654      *                                 UDP protocol.
655      * @return {@code this}
656      */
657     public DnsNameResolverBuilder datagramChannelStrategy(DnsNameResolverChannelStrategy datagramChannelStrategy) {
658         this.datagramChannelStrategy = ObjectUtil.checkNotNull(datagramChannelStrategy, "datagramChannelStrategy");
659         return this;
660     }
661 
662     /**
663      * Returns a new {@link DnsNameResolver} instance.
664      *
665      * @return a {@link DnsNameResolver}
666      */
667     public DnsNameResolver build() {
668         if (eventLoop == null) {
669             throw new IllegalStateException("eventLoop should be specified to build a DnsNameResolver.");
670         }
671 
672         if (resolveCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
673             logger.debug("resolveCache and TTLs are mutually exclusive. TTLs are ignored.");
674         }
675 
676         if (cnameCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
677             logger.debug("cnameCache and TTLs are mutually exclusive. TTLs are ignored.");
678         }
679 
680         if (authoritativeDnsServerCache != null && (minTtl != null || maxTtl != null || negativeTtl != null)) {
681             logger.debug("authoritativeDnsServerCache and TTLs are mutually exclusive. TTLs are ignored.");
682         }
683 
684         DnsCache resolveCache = getOrNewCache();
685         DnsCnameCache cnameCache = getOrNewCnameCache();
686         AuthoritativeDnsServerCache authoritativeDnsServerCache = getOrNewAuthoritativeDnsServerCache();
687 
688         DnsServerAddressStream queryDnsServerAddressStream = this.queryDnsServerAddressStream != null ?
689                 this.queryDnsServerAddressStream : newQueryServerAddressStream(dnsServerAddressStreamProvider);
690 
691         return new DnsNameResolver(
692                 eventLoop,
693                 datagramChannelFactory,
694                 socketChannelFactory,
695                 retryOnTimeout,
696                 resolveCache,
697                 cnameCache,
698                 authoritativeDnsServerCache,
699                 localAddress,
700                 dnsQueryLifecycleObserverFactory,
701                 queryTimeoutMillis,
702                 resolvedAddressTypes,
703                 recursionDesired,
704                 maxQueriesPerResolve,
705                 traceEnabled,
706                 maxPayloadSize,
707                 optResourceEnabled,
708                 hostsFileEntriesResolver,
709                 dnsServerAddressStreamProvider,
710                 queryDnsServerAddressStream,
711                 searchDomains,
712                 ndots,
713                 decodeIdn,
714                 completeOncePreferredResolved,
715                 maxNumConsolidation,
716                 datagramChannelStrategy);
717     }
718 
719     /**
720      * Creates a copy of this {@link DnsNameResolverBuilder}
721      *
722      * @return {@link DnsNameResolverBuilder}
723      */
724     public DnsNameResolverBuilder copy() {
725         DnsNameResolverBuilder copiedBuilder = new DnsNameResolverBuilder();
726 
727         if (eventLoop != null) {
728             copiedBuilder.eventLoop(eventLoop);
729         }
730 
731         if (datagramChannelFactory != null) {
732             copiedBuilder.datagramChannelFactory(datagramChannelFactory);
733         }
734 
735         copiedBuilder.socketChannelFactory(socketChannelFactory, retryOnTimeout);
736 
737         if (resolveCache != null) {
738             copiedBuilder.resolveCache(resolveCache);
739         }
740 
741         if (cnameCache != null) {
742             copiedBuilder.cnameCache(cnameCache);
743         }
744         if (maxTtl != null && minTtl != null) {
745             copiedBuilder.ttl(minTtl, maxTtl);
746         }
747 
748         if (negativeTtl != null) {
749             copiedBuilder.negativeTtl(negativeTtl);
750         }
751 
752         if (authoritativeDnsServerCache != null) {
753             copiedBuilder.authoritativeDnsServerCache(authoritativeDnsServerCache);
754         }
755 
756         if (dnsQueryLifecycleObserverFactory != null) {
757             copiedBuilder.dnsQueryLifecycleObserverFactory(dnsQueryLifecycleObserverFactory);
758         }
759 
760         copiedBuilder.queryTimeoutMillis(queryTimeoutMillis);
761         copiedBuilder.resolvedAddressTypes(resolvedAddressTypes);
762         copiedBuilder.recursionDesired(recursionDesired);
763         copiedBuilder.maxQueriesPerResolve(maxQueriesPerResolve);
764         copiedBuilder.traceEnabled(traceEnabled);
765         copiedBuilder.maxPayloadSize(maxPayloadSize);
766         copiedBuilder.optResourceEnabled(optResourceEnabled);
767         copiedBuilder.hostsFileEntriesResolver(hostsFileEntriesResolver);
768 
769         if (dnsServerAddressStreamProvider != null) {
770             copiedBuilder.nameServerProvider(dnsServerAddressStreamProvider);
771         }
772 
773         if (queryDnsServerAddressStream != null) {
774             copiedBuilder.queryServerAddressStream(queryDnsServerAddressStream);
775         }
776 
777         if (searchDomains != null) {
778             copiedBuilder.searchDomains(Arrays.asList(searchDomains));
779         }
780 
781         copiedBuilder.ndots(ndots);
782         copiedBuilder.decodeIdn(decodeIdn);
783         copiedBuilder.completeOncePreferredResolved(completeOncePreferredResolved);
784         copiedBuilder.localAddress(localAddress);
785         copiedBuilder.consolidateCacheSize(maxNumConsolidation);
786         copiedBuilder.datagramChannelStrategy(datagramChannelStrategy);
787         return copiedBuilder;
788     }
789 }