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 <= 65535)); 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<String,String>(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 = 460 new Hashtable<String,String>(jndiProperties.size()+2); 461 for (final Map.Entry<Object,Object> e : jndiProperties.entrySet()) 462 { 463 this.jndiProperties.put(String.valueOf(e.getKey()), 464 String.valueOf(e.getValue())); 465 } 466 467 if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY)) 468 { 469 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, 470 "com.sun.jndi.dns.DnsContextFactory"); 471 } 472 473 if ((! this.jndiProperties.containsKey(Context.PROVIDER_URL)) && 474 (providerURL != null)) 475 { 476 this.jndiProperties.put(Context.PROVIDER_URL, providerURL); 477 } 478 } 479 480 if (dnsRecordTypes == null) 481 { 482 this.dnsRecordTypes = new String[] { "A" }; 483 } 484 else 485 { 486 this.dnsRecordTypes = dnsRecordTypes; 487 } 488 489 if (cacheTimeoutMillis > 0L) 490 { 491 this.cacheTimeoutMillis = cacheTimeoutMillis; 492 } 493 else 494 { 495 this.cacheTimeoutMillis = 0L; 496 } 497 498 if (socketFactory == null) 499 { 500 this.socketFactory = SocketFactory.getDefault(); 501 } 502 else 503 { 504 this.socketFactory = socketFactory; 505 } 506 507 if (connectionOptions == null) 508 { 509 this.connectionOptions = new LDAPConnectionOptions(); 510 } 511 else 512 { 513 this.connectionOptions = connectionOptions; 514 } 515 516 roundRobinCounter = new AtomicLong(0L); 517 resolvedAddressesWithTimeout = 518 new AtomicReference<ObjectPair<InetAddress[],Long>>(); 519 } 520 521 522 523 /** 524 * Retrieves the hostname to be resolved. 525 * 526 * @return The hostname to be resolved. 527 */ 528 public String getHostname() 529 { 530 return hostname; 531 } 532 533 534 535 /** 536 * Retrieves the port to use to connect to the server. 537 * 538 * @return The port to use to connect to the server. 539 */ 540 public int getPort() 541 { 542 return port; 543 } 544 545 546 547 /** 548 * Retrieves the address selection mode that should be used if the provided 549 * hostname resolves to multiple addresses. 550 * 551 * @return The address selection 552 */ 553 public AddressSelectionMode getAddressSelectionMode() 554 { 555 return selectionMode; 556 } 557 558 559 560 /** 561 * Retrieves the length of time in milliseconds that resolved addresses may be 562 * cached. 563 * 564 * @return The length of time in milliseconds that resolved addresses may be 565 * cached, or zero if no caching should be performed. 566 */ 567 public long getCacheTimeoutMillis() 568 { 569 return cacheTimeoutMillis; 570 } 571 572 573 574 /** 575 * Retrieves the provider URL that should be used when interacting with DNS to 576 * resolve the hostname to its corresponding addresses. 577 * 578 * @return The provider URL that should be used when interacting with DNS to 579 * resolve the hostname to its corresponding addresses, or 580 * {@code null} if the system's configured naming service should be 581 * used. 582 */ 583 public String getProviderURL() 584 { 585 return providerURL; 586 } 587 588 589 590 /** 591 * Retrieves an unmodifiable map of properties that will be used to initialize 592 * the JNDI context used to interact with DNS. Note that the map returned 593 * will reflect the actual properties that will be used, and may not exactly 594 * match the properties provided when creating this server set. 595 * 596 * @return An unmodifiable map of properties that will be used to initialize 597 * the JNDI context used to interact with DNS, or {@code null} if 598 * JNDI will nto be used to interact with DNS. 599 */ 600 public Map<String,String> getJNDIProperties() 601 { 602 if (jndiProperties == null) 603 { 604 return null; 605 } 606 else 607 { 608 return Collections.unmodifiableMap(jndiProperties); 609 } 610 } 611 612 613 614 /** 615 * Retrieves an array of record types that will be requested if JNDI will be 616 * used to interact with DNS. 617 * 618 * @return An array of record types that will be requested if JNDI will be 619 * used to interact with DNS. 620 */ 621 public String[] getDNSRecordTypes() 622 { 623 return dnsRecordTypes; 624 } 625 626 627 628 /** 629 * Retrieves the socket factory that will be used to establish connections. 630 * This will not be {@code null}, even if no socket factory was provided when 631 * the server set was created. 632 * 633 * @return The socket factory that will be used to establish connections. 634 */ 635 public SocketFactory getSocketFactory() 636 { 637 return socketFactory; 638 } 639 640 641 642 /** 643 * Retrieves the set of connection options that will be used for underlying 644 * connections. This will not be {@code null}, even if no connection options 645 * object was provided when the server set was created. 646 * 647 * @return The set of connection options that will be used for underlying 648 * connections. 649 */ 650 public LDAPConnectionOptions getConnectionOptions() 651 { 652 return connectionOptions; 653 } 654 655 656 657 /** 658 * {@inheritDoc} 659 */ 660 @Override() 661 public boolean includesAuthentication() 662 { 663 return (bindRequest != null); 664 } 665 666 667 668 /** 669 * {@inheritDoc} 670 */ 671 @Override() 672 public boolean includesPostConnectProcessing() 673 { 674 return (postConnectProcessor != null); 675 } 676 677 678 679 /** 680 * {@inheritDoc} 681 */ 682 @Override() 683 public LDAPConnection getConnection() 684 throws LDAPException 685 { 686 return getConnection(null); 687 } 688 689 690 691 /** 692 * {@inheritDoc} 693 */ 694 @Override() 695 public synchronized LDAPConnection getConnection( 696 final LDAPConnectionPoolHealthCheck healthCheck) 697 throws LDAPException 698 { 699 LDAPException firstException = null; 700 701 final LDAPConnection conn = 702 new LDAPConnection(socketFactory, connectionOptions); 703 for (final InetAddress a : orderAddresses(resolveHostname())) 704 { 705 boolean close = true; 706 try 707 { 708 conn.connect(hostname, a, port, 709 connectionOptions.getConnectTimeoutMillis()); 710 doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, 711 postConnectProcessor, healthCheck); 712 close = false; 713 return conn; 714 } 715 catch (final LDAPException le) 716 { 717 Debug.debugException(le); 718 if (firstException == null) 719 { 720 firstException = le; 721 } 722 } 723 finally 724 { 725 if (close) 726 { 727 conn.close(); 728 } 729 } 730 } 731 732 throw firstException; 733 } 734 735 736 737 /** 738 * Resolve the hostname to its corresponding addresses. 739 * 740 * @return The addresses resolved from the hostname. 741 * 742 * @throws LDAPException If 743 */ 744 InetAddress[] resolveHostname() 745 throws LDAPException 746 { 747 // First, see if we can use the cached addresses. 748 final ObjectPair<InetAddress[],Long> pair = 749 resolvedAddressesWithTimeout.get(); 750 if (pair != null) 751 { 752 if (pair.getSecond() <= System.currentTimeMillis()) 753 { 754 return pair.getFirst(); 755 } 756 } 757 758 759 // Try to resolve the address. 760 InetAddress[] addresses = null; 761 try 762 { 763 if (jndiProperties == null) 764 { 765 addresses = InetAddress.getAllByName(hostname); 766 } 767 else 768 { 769 Attributes attributes = null; 770 final InitialDirContext context = new InitialDirContext(jndiProperties); 771 try 772 { 773 attributes = context.getAttributes(hostname, dnsRecordTypes); 774 } 775 finally 776 { 777 context.close(); 778 } 779 780 if (attributes != null) 781 { 782 final ArrayList<InetAddress> addressList = 783 new ArrayList<InetAddress>(10); 784 for (final String recordType : dnsRecordTypes) 785 { 786 final Attribute a = attributes.get(recordType); 787 if (a != null) 788 { 789 final NamingEnumeration<?> values = a.getAll(); 790 while (values.hasMore()) 791 { 792 final Object value = values.next(); 793 addressList.add(getInetAddressForIP(String.valueOf(value))); 794 } 795 } 796 } 797 798 if (! addressList.isEmpty()) 799 { 800 addresses = new InetAddress[addressList.size()]; 801 addressList.toArray(addresses); 802 } 803 } 804 } 805 } 806 catch (final Exception e) 807 { 808 Debug.debugException(e); 809 addresses = getDefaultAddresses(); 810 } 811 812 813 // If we were able to resolve the hostname, then cache and return the 814 // resolved addresses. 815 if ((addresses != null) && (addresses.length > 0)) 816 { 817 final long timeoutTime; 818 if (cacheTimeoutMillis > 0L) 819 { 820 timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis; 821 } 822 else 823 { 824 timeoutTime = System.currentTimeMillis() - 1L; 825 } 826 827 resolvedAddressesWithTimeout.set(new ObjectPair<InetAddress[],Long>( 828 addresses, timeoutTime)); 829 return addresses; 830 } 831 832 833 // If we've gotten here, then we couldn't resolve the hostname. If we have 834 // cached addresses, then use them even though the timeout has expired 835 // because that's better than nothing. 836 if (pair != null) 837 { 838 return pair.getFirst(); 839 } 840 841 throw new LDAPException(ResultCode.CONNECT_ERROR, 842 ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname)); 843 } 844 845 846 847 /** 848 * Orders the provided array of InetAddress objects to reflect the order in 849 * which the addresses should be used to try to create a new connection. 850 * 851 * @param addresses The array of addresses to be ordered. 852 * 853 * @return A list containing the ordered addresses. 854 */ 855 List<InetAddress> orderAddresses(final InetAddress[] addresses) 856 { 857 final ArrayList<InetAddress> l = 858 new ArrayList<InetAddress>(addresses.length); 859 860 switch (selectionMode) 861 { 862 case RANDOM: 863 l.addAll(Arrays.asList(addresses)); 864 Collections.shuffle(l, ThreadLocalRandom.get()); 865 break; 866 867 case ROUND_ROBIN: 868 final int index = 869 (int) (roundRobinCounter.getAndIncrement() % addresses.length); 870 for (int i=index; i < addresses.length; i++) 871 { 872 l.add(addresses[i]); 873 } 874 for (int i=0; i < index; i++) 875 { 876 l.add(addresses[i]); 877 } 878 break; 879 880 case FAILOVER: 881 default: 882 // We'll use the addresses in the same order we originally got them. 883 l.addAll(Arrays.asList(addresses)); 884 break; 885 } 886 887 return l; 888 } 889 890 891 892 /** 893 * Retrieves a default set of addresses that may be used for testing. 894 * 895 * @return A default set of addresses that may be used for testing. 896 */ 897 InetAddress[] getDefaultAddresses() 898 { 899 final String defaultAddrsStr = 900 System.getProperty(PROPERTY_DEFAULT_ADDRESSES); 901 if (defaultAddrsStr == null) 902 { 903 return null; 904 } 905 906 final StringTokenizer tokenizer = 907 new StringTokenizer(defaultAddrsStr, " ,"); 908 final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()]; 909 for (int i=0; i < addresses.length; i++) 910 { 911 try 912 { 913 addresses[i] = getInetAddressForIP(tokenizer.nextToken()); 914 } 915 catch (final Exception e) 916 { 917 Debug.debugException(e); 918 return null; 919 } 920 } 921 922 return addresses; 923 } 924 925 926 927 /** 928 * Retrieves an InetAddress object with the configured hostname and the 929 * provided IP address. 930 * 931 * @param ipAddress The string representation of the IP address to use in 932 * the returned InetAddress. 933 * 934 * @return The created InetAddress. 935 * 936 * @throws UnknownHostException If the provided string does not represent a 937 * valid IPv4 or IPv6 address. 938 */ 939 private InetAddress getInetAddressForIP(final String ipAddress) 940 throws UnknownHostException 941 { 942 // We want to create an InetAddress that has the provided hostname and the 943 // specified IP address. To do that, we need to use 944 // InetAddress.getByAddress. But that requires the IP address to be 945 // specified as a byte array, and the easiest way to convert an IP address 946 // string to a byte array is to use InetAddress.getByName. 947 final InetAddress byName = InetAddress.getByName(String.valueOf(ipAddress)); 948 return InetAddress.getByAddress(hostname, byName.getAddress()); 949 } 950 951 952 953 /** 954 * {@inheritDoc} 955 */ 956 @Override() 957 public void toString(final StringBuilder buffer) 958 { 959 buffer.append("RoundRobinDNSServerSet(hostname='"); 960 buffer.append(hostname); 961 buffer.append("', port="); 962 buffer.append(port); 963 buffer.append(", addressSelectionMode="); 964 buffer.append(selectionMode.name()); 965 buffer.append(", cacheTimeoutMillis="); 966 buffer.append(cacheTimeoutMillis); 967 968 if (providerURL != null) 969 { 970 buffer.append(", providerURL='"); 971 buffer.append(providerURL); 972 buffer.append('\''); 973 } 974 975 buffer.append(", includesAuthentication="); 976 buffer.append(bindRequest != null); 977 buffer.append(", includesPostConnectProcessing="); 978 buffer.append(postConnectProcessor != null); 979 buffer.append(')'); 980 } 981}