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