001/* 002 * Copyright 2014-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2014-2018 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 return conn; 712 } 713 catch (final LDAPException le) 714 { 715 Debug.debugException(le); 716 if (firstException == null) 717 { 718 firstException = le; 719 } 720 } 721 finally 722 { 723 if (close) 724 { 725 conn.close(); 726 } 727 } 728 } 729 730 throw firstException; 731 } 732 733 734 735 /** 736 * Resolve the hostname to its corresponding addresses. 737 * 738 * @return The addresses resolved from the hostname. 739 * 740 * @throws LDAPException If 741 */ 742 InetAddress[] resolveHostname() 743 throws LDAPException 744 { 745 // First, see if we can use the cached addresses. 746 final ObjectPair<InetAddress[],Long> pair = 747 resolvedAddressesWithTimeout.get(); 748 if (pair != null) 749 { 750 if (pair.getSecond() <= System.currentTimeMillis()) 751 { 752 return pair.getFirst(); 753 } 754 } 755 756 757 // Try to resolve the address. 758 InetAddress[] addresses = null; 759 try 760 { 761 if (jndiProperties == null) 762 { 763 addresses = InetAddress.getAllByName(hostname); 764 } 765 else 766 { 767 final Attributes attributes; 768 final InitialDirContext context = new InitialDirContext(jndiProperties); 769 try 770 { 771 attributes = context.getAttributes(hostname, dnsRecordTypes); 772 } 773 finally 774 { 775 context.close(); 776 } 777 778 if (attributes != null) 779 { 780 final ArrayList<InetAddress> addressList = new ArrayList<>(10); 781 for (final String recordType : dnsRecordTypes) 782 { 783 final Attribute a = attributes.get(recordType); 784 if (a != null) 785 { 786 final NamingEnumeration<?> values = a.getAll(); 787 while (values.hasMore()) 788 { 789 final Object value = values.next(); 790 addressList.add(getInetAddressForIP(String.valueOf(value))); 791 } 792 } 793 } 794 795 if (! addressList.isEmpty()) 796 { 797 addresses = new InetAddress[addressList.size()]; 798 addressList.toArray(addresses); 799 } 800 } 801 } 802 } 803 catch (final Exception e) 804 { 805 Debug.debugException(e); 806 addresses = getDefaultAddresses(); 807 } 808 809 810 // If we were able to resolve the hostname, then cache and return the 811 // resolved addresses. 812 if ((addresses != null) && (addresses.length > 0)) 813 { 814 final long timeoutTime; 815 if (cacheTimeoutMillis > 0L) 816 { 817 timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis; 818 } 819 else 820 { 821 timeoutTime = System.currentTimeMillis() - 1L; 822 } 823 824 resolvedAddressesWithTimeout.set( 825 new ObjectPair<>(addresses, timeoutTime)); 826 return addresses; 827 } 828 829 830 // If we've gotten here, then we couldn't resolve the hostname. If we have 831 // cached addresses, then use them even though the timeout has expired 832 // because that's better than nothing. 833 if (pair != null) 834 { 835 return pair.getFirst(); 836 } 837 838 throw new LDAPException(ResultCode.CONNECT_ERROR, 839 ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname)); 840 } 841 842 843 844 /** 845 * Orders the provided array of InetAddress objects to reflect the order in 846 * which the addresses should be used to try to create a new connection. 847 * 848 * @param addresses The array of addresses to be ordered. 849 * 850 * @return A list containing the ordered addresses. 851 */ 852 List<InetAddress> orderAddresses(final InetAddress[] addresses) 853 { 854 final ArrayList<InetAddress> l = new ArrayList<>(addresses.length); 855 856 switch (selectionMode) 857 { 858 case RANDOM: 859 l.addAll(Arrays.asList(addresses)); 860 Collections.shuffle(l, ThreadLocalRandom.get()); 861 break; 862 863 case ROUND_ROBIN: 864 final int index = 865 (int) (roundRobinCounter.getAndIncrement() % addresses.length); 866 for (int i=index; i < addresses.length; i++) 867 { 868 l.add(addresses[i]); 869 } 870 for (int i=0; i < index; i++) 871 { 872 l.add(addresses[i]); 873 } 874 break; 875 876 case FAILOVER: 877 default: 878 // We'll use the addresses in the same order we originally got them. 879 l.addAll(Arrays.asList(addresses)); 880 break; 881 } 882 883 return l; 884 } 885 886 887 888 /** 889 * Retrieves a default set of addresses that may be used for testing. 890 * 891 * @return A default set of addresses that may be used for testing. 892 */ 893 InetAddress[] getDefaultAddresses() 894 { 895 final String defaultAddrsStr = 896 System.getProperty(PROPERTY_DEFAULT_ADDRESSES); 897 if (defaultAddrsStr == null) 898 { 899 return null; 900 } 901 902 final StringTokenizer tokenizer = 903 new StringTokenizer(defaultAddrsStr, " ,"); 904 final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()]; 905 for (int i=0; i < addresses.length; i++) 906 { 907 try 908 { 909 addresses[i] = getInetAddressForIP(tokenizer.nextToken()); 910 } 911 catch (final Exception e) 912 { 913 Debug.debugException(e); 914 return null; 915 } 916 } 917 918 return addresses; 919 } 920 921 922 923 /** 924 * Retrieves an InetAddress object with the configured hostname and the 925 * provided IP address. 926 * 927 * @param ipAddress The string representation of the IP address to use in 928 * the returned InetAddress. 929 * 930 * @return The created InetAddress. 931 * 932 * @throws UnknownHostException If the provided string does not represent a 933 * valid IPv4 or IPv6 address. 934 */ 935 private InetAddress getInetAddressForIP(final String ipAddress) 936 throws UnknownHostException 937 { 938 // We want to create an InetAddress that has the provided hostname and the 939 // specified IP address. To do that, we need to use 940 // InetAddress.getByAddress. But that requires the IP address to be 941 // specified as a byte array, and the easiest way to convert an IP address 942 // string to a byte array is to use InetAddress.getByName. 943 final InetAddress byName = InetAddress.getByName(String.valueOf(ipAddress)); 944 return InetAddress.getByAddress(hostname, byName.getAddress()); 945 } 946 947 948 949 /** 950 * {@inheritDoc} 951 */ 952 @Override() 953 public void toString(final StringBuilder buffer) 954 { 955 buffer.append("RoundRobinDNSServerSet(hostname='"); 956 buffer.append(hostname); 957 buffer.append("', port="); 958 buffer.append(port); 959 buffer.append(", addressSelectionMode="); 960 buffer.append(selectionMode.name()); 961 buffer.append(", cacheTimeoutMillis="); 962 buffer.append(cacheTimeoutMillis); 963 964 if (providerURL != null) 965 { 966 buffer.append(", providerURL='"); 967 buffer.append(providerURL); 968 buffer.append('\''); 969 } 970 971 buffer.append(", includesAuthentication="); 972 buffer.append(bindRequest != null); 973 buffer.append(", includesPostConnectProcessing="); 974 buffer.append(postConnectProcessor != null); 975 buffer.append(')'); 976 } 977}