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