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