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