001/* 002 * Copyright 2008-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-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.util.List; 026import java.util.concurrent.atomic.AtomicBoolean; 027import javax.net.SocketFactory; 028 029import com.unboundid.util.NotMutable; 030import com.unboundid.util.ThreadSafety; 031import com.unboundid.util.ThreadSafetyLevel; 032 033import static com.unboundid.util.Debug.*; 034import static com.unboundid.util.StaticUtils.*; 035import static com.unboundid.util.Validator.*; 036 037 038 039/** 040 * This class provides a server set implementation that will attempt to 041 * establish connections to servers in the order they are provided. If the 042 * first server is unavailable, then it will attempt to connect to the second, 043 * then to the third, etc. Note that this implementation also makes it possible 044 * to use failover between distinct server sets, which means that it will first 045 * attempt to obtain a connection from the first server set and if all attempts 046 * fail, it will proceed to the second set, and so on. This can provide a 047 * significant degree of flexibility in complex environments (e.g., first use a 048 * round robin server set containing servers in the local data center, but if 049 * none of those are available then fail over to a server set with servers in a 050 * remote data center). 051 * <BR><BR> 052 * <H2>Example</H2> 053 * The following example demonstrates the process for creating a failover server 054 * set with information about individual servers. It will first try to connect 055 * to ds1.example.com:389, but if that fails then it will try connecting to 056 * ds2.example.com:389: 057 * <PRE> 058 * // Create arrays with the addresses and ports of the directory server 059 * // instances. 060 * String[] addresses = 061 * { 062 * server1Address, 063 * server2Address 064 * }; 065 * int[] ports = 066 * { 067 * server1Port, 068 * server2Port 069 * }; 070 * 071 * // Create the server set using the address and port arrays. 072 * FailoverServerSet failoverSet = new FailoverServerSet(addresses, ports); 073 * 074 * // Verify that we can establish a single connection using the server set. 075 * LDAPConnection connection = failoverSet.getConnection(); 076 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 077 * connection.close(); 078 * 079 * // Verify that we can establish a connection pool using the server set. 080 * SimpleBindRequest bindRequest = 081 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 082 * LDAPConnectionPool pool = 083 * new LDAPConnectionPool(failoverSet, bindRequest, 10); 084 * RootDSE rootDSEFromPool = pool.getRootDSE(); 085 * pool.close(); 086 * </PRE> 087 * This second example demonstrates the process for creating a failover server 088 * set which actually fails over between two different data centers (east and 089 * west), with each data center containing two servers that will be accessed in 090 * a round-robin manner. It will first try to connect to one of the servers in 091 * the east data center, and if that attempt fails then it will try to connect 092 * to the other server in the east data center. If both of them fail, then it 093 * will try to connect to one of the servers in the west data center, and 094 * finally as a last resort the other server in the west data center: 095 * <PRE> 096 * // Create a round-robin server set for the servers in the "east" data 097 * // center. 098 * String[] eastAddresses = 099 * { 100 * eastServer1Address, 101 * eastServer2Address 102 * }; 103 * int[] eastPorts = 104 * { 105 * eastServer1Port, 106 * eastServer2Port 107 * }; 108 * RoundRobinServerSet eastSet = 109 * new RoundRobinServerSet(eastAddresses, eastPorts); 110 * 111 * // Create a round-robin server set for the servers in the "west" data 112 * // center. 113 * String[] westAddresses = 114 * { 115 * westServer1Address, 116 * westServer2Address 117 * }; 118 * int[] westPorts = 119 * { 120 * westServer1Port, 121 * westServer2Port 122 * }; 123 * RoundRobinServerSet westSet = 124 * new RoundRobinServerSet(westAddresses, westPorts); 125 * 126 * // Create the failover server set across the east and west round-robin sets. 127 * FailoverServerSet failoverSet = new FailoverServerSet(eastSet, westSet); 128 * 129 * // Verify that we can establish a single connection using the server set. 130 * LDAPConnection connection = failoverSet.getConnection(); 131 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 132 * connection.close(); 133 * 134 * // Verify that we can establish a connection pool using the server set. 135 * SimpleBindRequest bindRequest = 136 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 137 * LDAPConnectionPool pool = 138 * new LDAPConnectionPool(failoverSet, bindRequest, 10); 139 * RootDSE rootDSEFromPool = pool.getRootDSE(); 140 * pool.close(); 141 * </PRE> 142 */ 143@NotMutable() 144@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 145public final class FailoverServerSet 146 extends ServerSet 147{ 148 // Indicates whether to re-order the server set list if failover occurs. 149 private final AtomicBoolean reOrderOnFailover; 150 151 // The maximum connection age that should be set for connections established 152 // using anything but the first server set. 153 private volatile Long maxFailoverConnectionAge; 154 155 // The server sets for which we will allow failover. 156 private final ServerSet[] serverSets; 157 158 159 160 /** 161 * Creates a new failover server set with the specified set of directory 162 * server addresses and port numbers. It will use the default socket factory 163 * provided by the JVM to create the underlying sockets. 164 * 165 * @param addresses The addresses of the directory servers to which the 166 * connections should be established. It must not be 167 * {@code null} or empty. 168 * @param ports The ports of the directory servers to which the 169 * connections should be established. It must not be 170 * {@code null}, and it must have the same number of 171 * elements as the {@code addresses} array. The order of 172 * elements in the {@code addresses} array must correspond 173 * to the order of elements in the {@code ports} array. 174 */ 175 public FailoverServerSet(final String[] addresses, final int[] ports) 176 { 177 this(addresses, ports, null, null); 178 } 179 180 181 182 /** 183 * Creates a new failover server set with the specified set of directory 184 * server addresses and port numbers. It will use the default socket factory 185 * provided by the JVM to create the underlying sockets. 186 * 187 * @param addresses The addresses of the directory servers to which 188 * the connections should be established. It must 189 * not be {@code null} or empty. 190 * @param ports The ports of the directory servers to which the 191 * connections should be established. It must not 192 * be {@code null}, and it must have the same 193 * number of elements as the {@code addresses} 194 * array. The order of elements in the 195 * {@code addresses} array must correspond to the 196 * order of elements in the {@code ports} array. 197 * @param connectionOptions The set of connection options to use for the 198 * underlying connections. 199 */ 200 public FailoverServerSet(final String[] addresses, final int[] ports, 201 final LDAPConnectionOptions connectionOptions) 202 { 203 this(addresses, ports, null, connectionOptions); 204 } 205 206 207 208 /** 209 * Creates a new failover server set with the specified set of directory 210 * server addresses and port numbers. It will use the provided socket factory 211 * to create the underlying sockets. 212 * 213 * @param addresses The addresses of the directory servers to which the 214 * connections should be established. It must not be 215 * {@code null} or empty. 216 * @param ports The ports of the directory servers to which the 217 * connections should be established. It must not be 218 * {@code null}, and it must have the same number of 219 * elements as the {@code addresses} array. The order 220 * of elements in the {@code addresses} array must 221 * correspond to the order of elements in the 222 * {@code ports} array. 223 * @param socketFactory The socket factory to use to create the underlying 224 * connections. 225 */ 226 public FailoverServerSet(final String[] addresses, final int[] ports, 227 final SocketFactory socketFactory) 228 { 229 this(addresses, ports, socketFactory, null); 230 } 231 232 233 234 /** 235 * Creates a new failover server set with the specified set of directory 236 * server addresses and port numbers. It will use the provided socket factory 237 * to create the underlying sockets. 238 * 239 * @param addresses The addresses of the directory servers to which 240 * the connections should be established. It must 241 * not be {@code null} or empty. 242 * @param ports The ports of the directory servers to which the 243 * connections should be established. It must not 244 * be {@code null}, and it must have the same 245 * number of elements as the {@code addresses} 246 * array. The order of elements in the 247 * {@code addresses} array must correspond to the 248 * order of elements in the {@code ports} array. 249 * @param socketFactory The socket factory to use to create the 250 * underlying connections. 251 * @param connectionOptions The set of connection options to use for the 252 * underlying connections. 253 */ 254 public FailoverServerSet(final String[] addresses, final int[] ports, 255 final SocketFactory socketFactory, 256 final LDAPConnectionOptions connectionOptions) 257 { 258 this(addresses, ports, socketFactory, connectionOptions, null, null); 259 } 260 261 262 263 /** 264 * Creates a new failover server set with the specified set of directory 265 * server addresses and port numbers. It will use the provided socket factory 266 * to create the underlying sockets. 267 * 268 * @param addresses The addresses of the directory servers to 269 * which the connections should be established. 270 * It must not be {@code null} or empty. 271 * @param ports The ports of the directory servers to which 272 * the connections should be established. It 273 * must not be {@code null}, and it must have 274 * the same number of elements as the 275 * {@code addresses} array. The order of 276 * elements in the {@code addresses} array must 277 * correspond to the order of elements in the 278 * {@code ports} array. 279 * @param socketFactory The socket factory to use to create the 280 * underlying connections. 281 * @param connectionOptions The set of connection options to use for the 282 * underlying connections. 283 * @param bindRequest The bind request that should be used to 284 * authenticate newly-established connections. 285 * It may be {@code null} if this server set 286 * should not perform any authentication. 287 * @param postConnectProcessor The post-connect processor that should be 288 * invoked on newly-established connections. It 289 * may be {@code null} if this server set should 290 * not perform any post-connect processing. 291 */ 292 public FailoverServerSet(final String[] addresses, final int[] ports, 293 final SocketFactory socketFactory, 294 final LDAPConnectionOptions connectionOptions, 295 final BindRequest bindRequest, 296 final PostConnectProcessor postConnectProcessor) 297 { 298 ensureNotNull(addresses, ports); 299 ensureTrue(addresses.length > 0, 300 "FailoverServerSet.addresses must not be empty."); 301 ensureTrue(addresses.length == ports.length, 302 "FailoverServerSet addresses and ports arrays must be the same size."); 303 304 reOrderOnFailover = new AtomicBoolean(false); 305 maxFailoverConnectionAge = null; 306 307 final SocketFactory sf; 308 if (socketFactory == null) 309 { 310 sf = SocketFactory.getDefault(); 311 } 312 else 313 { 314 sf = socketFactory; 315 } 316 317 final LDAPConnectionOptions co; 318 if (connectionOptions == null) 319 { 320 co = new LDAPConnectionOptions(); 321 } 322 else 323 { 324 co = connectionOptions; 325 } 326 327 serverSets = new ServerSet[addresses.length]; 328 for (int i=0; i < serverSets.length; i++) 329 { 330 serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co, 331 bindRequest, postConnectProcessor); 332 } 333 } 334 335 336 337 /** 338 * Creates a new failover server set that will fail over between the provided 339 * server sets. 340 * 341 * @param serverSets The server sets between which failover should occur. 342 * It must not be {@code null} or empty. All of the 343 * provided sets must have the same return value for their 344 * {@link #includesAuthentication()} method, and all of 345 * the provided sets must have the same return value for 346 * their {@link #includesPostConnectProcessing()} 347 * method. 348 */ 349 public FailoverServerSet(final ServerSet... serverSets) 350 { 351 this(toList(serverSets)); 352 } 353 354 355 356 /** 357 * Creates a new failover server set that will fail over between the provided 358 * server sets. 359 * 360 * @param serverSets The server sets between which failover should occur. 361 * It must not be {@code null} or empty. All of the 362 * provided sets must have the same return value for their 363 * {@link #includesAuthentication()} method, and all of 364 * the provided sets must have the same return value for 365 * their {@link #includesPostConnectProcessing()} 366 * method. 367 */ 368 public FailoverServerSet(final List<ServerSet> serverSets) 369 { 370 ensureNotNull(serverSets); 371 ensureFalse(serverSets.isEmpty(), 372 "FailoverServerSet.serverSets must not be empty."); 373 374 this.serverSets = new ServerSet[serverSets.size()]; 375 serverSets.toArray(this.serverSets); 376 377 boolean anySupportsAuthentication = false; 378 boolean allSupportAuthentication = true; 379 boolean anySupportsPostConnectProcessing = false; 380 boolean allSupportPostConnectProcessing = true; 381 for (final ServerSet serverSet : this.serverSets) 382 { 383 if (serverSet.includesAuthentication()) 384 { 385 anySupportsAuthentication = true; 386 } 387 else 388 { 389 allSupportAuthentication = false; 390 } 391 392 if (serverSet.includesPostConnectProcessing()) 393 { 394 anySupportsPostConnectProcessing = true; 395 } 396 else 397 { 398 allSupportPostConnectProcessing = false; 399 } 400 } 401 402 if (anySupportsAuthentication) 403 { 404 ensureTrue(allSupportAuthentication, 405 "When creating a FailoverServerSet from a collection of server " + 406 "sets, either all of those sets must include authentication, " + 407 "or none of those sets may include authentication."); 408 } 409 410 if (anySupportsPostConnectProcessing) 411 { 412 ensureTrue(allSupportPostConnectProcessing, 413 "When creating a FailoverServerSet from a collection of server " + 414 "sets, either all of those sets must include post-connect " + 415 "processing, or none of those sets may include post-connect " + 416 "processing."); 417 } 418 419 reOrderOnFailover = new AtomicBoolean(false); 420 maxFailoverConnectionAge = null; 421 } 422 423 424 425 /** 426 * Retrieves the server sets over which failover will occur. If this failover 427 * server set was created from individual servers rather than server sets, 428 * then the elements contained in the returned array will be 429 * {@code SingleServerSet} instances. 430 * 431 * @return The server sets over which failover will occur. 432 */ 433 public ServerSet[] getServerSets() 434 { 435 return serverSets; 436 } 437 438 439 440 /** 441 * Indicates whether the list of servers or server sets used by this failover 442 * server set should be re-ordered in the event that a failure is encountered 443 * while attempting to establish a connection. If {@code true}, then any 444 * failed attempt to establish a connection to a server set at the beginning 445 * of the list may cause that server/set to be moved to the end of the list so 446 * that it will be the last one tried on the next attempt. 447 * 448 * @return {@code true} if the order of elements in the associated list of 449 * servers or server sets should be updated if a failure occurs while 450 * attempting to establish a connection, or {@code false} if the 451 * original order should be preserved. 452 */ 453 public boolean reOrderOnFailover() 454 { 455 return reOrderOnFailover.get(); 456 } 457 458 459 460 /** 461 * Specifies whether the list of servers or server sets used by this failover 462 * server set should be re-ordered in the event that a failure is encountered 463 * while attempting to establish a connection. By default, the original 464 * order will be preserved, but if this method is called with a value of 465 * {@code true}, then a failed attempt to establish a connection to the server 466 * or server set at the beginning of the list may cause that server to be 467 * moved to the end of the list so that it will be the last server/set tried 468 * on the next attempt. 469 * 470 * @param reOrderOnFailover Indicates whether the list of servers or server 471 * sets should be re-ordered in the event that a 472 * failure is encountered while attempting to 473 * establish a connection. 474 */ 475 public void setReOrderOnFailover(final boolean reOrderOnFailover) 476 { 477 this.reOrderOnFailover.set(reOrderOnFailover); 478 } 479 480 481 482 /** 483 * Retrieves the maximum connection age that should be used for "failover" 484 * connections (i.e., connections that are established to any server other 485 * than the most-preferred server, or established using any server set other 486 * than the most-preferred set). This will only be used if this failover 487 * server set is used to create an {@link LDAPConnectionPool}, for connections 488 * within that pool. 489 * 490 * @return The maximum connection age that should be used for failover 491 * connections, a value of zero to indicate that no maximum age 492 * should apply to those connections, or {@code null} if the maximum 493 * connection age should be determined by the associated connection 494 * pool. 495 */ 496 public Long getMaxFailoverConnectionAgeMillis() 497 { 498 return maxFailoverConnectionAge; 499 } 500 501 502 503 /** 504 * Specifies the maximum connection age that should be used for "failover" 505 * connections (i.e., connections that are established to any server other 506 * than the most-preferred server, or established using any server set other 507 * than the most-preferred set). This will only be used if this failover 508 * server set is used to create an {@link LDAPConnectionPool}, for connections 509 * within that pool. 510 * 511 * @param maxFailoverConnectionAge The maximum connection age that should be 512 * used for failover connections. It may be 513 * less than or equal to zero to indicate 514 * that no maximum age should apply to such 515 * connections, or {@code null} to indicate 516 * that the maximum connection age should be 517 * determined by the associated connection 518 * pool. 519 */ 520 public void setMaxFailoverConnectionAgeMillis( 521 final Long maxFailoverConnectionAge) 522 { 523 if (maxFailoverConnectionAge == null) 524 { 525 this.maxFailoverConnectionAge = null; 526 } 527 else if (maxFailoverConnectionAge > 0L) 528 { 529 this.maxFailoverConnectionAge = maxFailoverConnectionAge; 530 } 531 else 532 { 533 this.maxFailoverConnectionAge = 0L; 534 } 535 } 536 537 538 539 /** 540 * {@inheritDoc} 541 */ 542 @Override() 543 public boolean includesAuthentication() 544 { 545 return serverSets[0].includesAuthentication(); 546 } 547 548 549 550 /** 551 * {@inheritDoc} 552 */ 553 @Override() 554 public boolean includesPostConnectProcessing() 555 { 556 return serverSets[0].includesPostConnectProcessing(); 557 } 558 559 560 561 /** 562 * {@inheritDoc} 563 */ 564 @Override() 565 public LDAPConnection getConnection() 566 throws LDAPException 567 { 568 return getConnection(null); 569 } 570 571 572 573 /** 574 * {@inheritDoc} 575 */ 576 @Override() 577 public LDAPConnection getConnection( 578 final LDAPConnectionPoolHealthCheck healthCheck) 579 throws LDAPException 580 { 581 if (reOrderOnFailover.get() && (serverSets.length > 1)) 582 { 583 synchronized (this) 584 { 585 // First, try to get a connection using the first set in the list. If 586 // this succeeds, then we don't need to go any further. 587 try 588 { 589 return serverSets[0].getConnection(healthCheck); 590 } 591 catch (final LDAPException le) 592 { 593 debugException(le); 594 } 595 596 // If we've gotten here, then we will need to re-order the list unless 597 // all other attempts fail. 598 int successfulPos = -1; 599 LDAPConnection conn = null; 600 LDAPException lastException = null; 601 for (int i=1; i < serverSets.length; i++) 602 { 603 try 604 { 605 conn = serverSets[i].getConnection(healthCheck); 606 successfulPos = i; 607 break; 608 } 609 catch (final LDAPException le) 610 { 611 debugException(le); 612 lastException = le; 613 } 614 } 615 616 if (successfulPos > 0) 617 { 618 int pos = 0; 619 final ServerSet[] setCopy = new ServerSet[serverSets.length]; 620 for (int i=successfulPos; i < serverSets.length; i++) 621 { 622 setCopy[pos++] = serverSets[i]; 623 } 624 625 for (int i=0; i < successfulPos; i++) 626 { 627 setCopy[pos++] = serverSets[i]; 628 } 629 630 System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length); 631 if (maxFailoverConnectionAge != null) 632 { 633 conn.setAttachment( 634 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE, 635 maxFailoverConnectionAge); 636 } 637 return conn; 638 } 639 else 640 { 641 throw lastException; 642 } 643 } 644 } 645 else 646 { 647 LDAPException lastException = null; 648 649 boolean first = true; 650 for (final ServerSet s : serverSets) 651 { 652 try 653 { 654 final LDAPConnection conn = s.getConnection(healthCheck); 655 if ((! first) && (maxFailoverConnectionAge != null)) 656 { 657 conn.setAttachment( 658 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE, 659 maxFailoverConnectionAge); 660 } 661 return conn; 662 } 663 catch (final LDAPException le) 664 { 665 first = false; 666 debugException(le); 667 lastException = le; 668 } 669 } 670 671 throw lastException; 672 } 673 } 674 675 676 677 /** 678 * {@inheritDoc} 679 */ 680 @Override() 681 public void toString(final StringBuilder buffer) 682 { 683 buffer.append("FailoverServerSet(serverSets={"); 684 685 for (int i=0; i < serverSets.length; i++) 686 { 687 if (i > 0) 688 { 689 buffer.append(", "); 690 } 691 692 serverSets[i].toString(buffer); 693 } 694 695 buffer.append("})"); 696 } 697}