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