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