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