001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.List; 026import java.util.concurrent.atomic.AtomicBoolean; 027import javax.net.SocketFactory; 028 029import com.unboundid.util.NotMutable; 030import com.unboundid.util.ThreadSafety; 031import com.unboundid.util.ThreadSafetyLevel; 032 033import static com.unboundid.util.Debug.*; 034import static com.unboundid.util.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 ensureNotNull(addresses, ports); 258 ensureTrue(addresses.length > 0, 259 "FailoverServerSet.addresses must not be empty."); 260 ensureTrue(addresses.length == ports.length, 261 "FailoverServerSet addresses and ports arrays must be the same size."); 262 263 reOrderOnFailover = new AtomicBoolean(false); 264 maxFailoverConnectionAge = null; 265 266 final SocketFactory sf; 267 if (socketFactory == null) 268 { 269 sf = SocketFactory.getDefault(); 270 } 271 else 272 { 273 sf = socketFactory; 274 } 275 276 final LDAPConnectionOptions co; 277 if (connectionOptions == null) 278 { 279 co = new LDAPConnectionOptions(); 280 } 281 else 282 { 283 co = connectionOptions; 284 } 285 286 serverSets = new ServerSet[addresses.length]; 287 for (int i=0; i < serverSets.length; i++) 288 { 289 serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co); 290 } 291 } 292 293 294 295 /** 296 * Creates a new failover server set that will fail over between the provided 297 * server sets. 298 * 299 * @param serverSets The server sets between which failover should occur. 300 * It must not be {@code null} or empty. 301 */ 302 public FailoverServerSet(final ServerSet... serverSets) 303 { 304 ensureNotNull(serverSets); 305 ensureFalse(serverSets.length == 0, 306 "FailoverServerSet.serverSets must not be empty."); 307 308 this.serverSets = serverSets; 309 310 reOrderOnFailover = new AtomicBoolean(false); 311 maxFailoverConnectionAge = null; 312 } 313 314 315 316 /** 317 * Creates a new failover server set that will fail over between the provided 318 * server sets. 319 * 320 * @param serverSets The server sets between which failover should occur. 321 * It must not be {@code null} or empty. 322 */ 323 public FailoverServerSet(final List<ServerSet> serverSets) 324 { 325 ensureNotNull(serverSets); 326 ensureFalse(serverSets.isEmpty(), 327 "FailoverServerSet.serverSets must not be empty."); 328 329 this.serverSets = new ServerSet[serverSets.size()]; 330 serverSets.toArray(this.serverSets); 331 332 reOrderOnFailover = new AtomicBoolean(false); 333 maxFailoverConnectionAge = null; 334 } 335 336 337 338 /** 339 * Retrieves the server sets over which failover will occur. If this failover 340 * server set was created from individual servers rather than server sets, 341 * then the elements contained in the returned array will be 342 * {@code SingleServerSet} instances. 343 * 344 * @return The server sets over which failover will occur. 345 */ 346 public ServerSet[] getServerSets() 347 { 348 return serverSets; 349 } 350 351 352 353 /** 354 * Indicates whether the list of servers or server sets used by this failover 355 * server set should be re-ordered in the event that a failure is encountered 356 * while attempting to establish a connection. If {@code true}, then any 357 * failed attempt to establish a connection to a server set at the beginning 358 * of the list may cause that server/set to be moved to the end of the list so 359 * that it will be the last one tried on the next attempt. 360 * 361 * @return {@code true} if the order of elements in the associated list of 362 * servers or server sets should be updated if a failure occurs while 363 * attempting to establish a connection, or {@code false} if the 364 * original order should be preserved. 365 */ 366 public boolean reOrderOnFailover() 367 { 368 return reOrderOnFailover.get(); 369 } 370 371 372 373 /** 374 * Specifies whether the list of servers or server sets used by this failover 375 * server set should be re-ordered in the event that a failure is encountered 376 * while attempting to establish a connection. By default, the original 377 * order will be preserved, but if this method is called with a value of 378 * {@code true}, then a failed attempt to establish a connection to the server 379 * or server set at the beginning of the list may cause that server to be 380 * moved to the end of the list so that it will be the last server/set tried 381 * on the next attempt. 382 * 383 * @param reOrderOnFailover Indicates whether the list of servers or server 384 * sets should be re-ordered in the event that a 385 * failure is encountered while attempting to 386 * establish a connection. 387 */ 388 public void setReOrderOnFailover(final boolean reOrderOnFailover) 389 { 390 this.reOrderOnFailover.set(reOrderOnFailover); 391 } 392 393 394 395 /** 396 * Retrieves the maximum connection age that should be used for "failover" 397 * connections (i.e., connections that are established to any server other 398 * than the most-preferred server, or established using any server set other 399 * than the most-preferred set). This will only be used if this failover 400 * server set is used to create an {@link LDAPConnectionPool}, for connections 401 * within that pool. 402 * 403 * @return The maximum connection age that should be used for failover 404 * connections, a value of zero to indicate that no maximum age 405 * should apply to those connections, or {@code null} if the maximum 406 * connection age should be determined by the associated connection 407 * pool. 408 */ 409 public Long getMaxFailoverConnectionAgeMillis() 410 { 411 return maxFailoverConnectionAge; 412 } 413 414 415 416 /** 417 * Specifies the maximum connection age that should be used for "failover" 418 * connections (i.e., connections that are established to any server other 419 * than the most-preferred server, or established using any server set other 420 * than the most-preferred set). This will only be used if this failover 421 * server set is used to create an {@link LDAPConnectionPool}, for connections 422 * within that pool. 423 * 424 * @param maxFailoverConnectionAge The maximum connection age that should be 425 * used for failover connections. It may be 426 * less than or equal to zero to indicate 427 * that no maximum age should apply to such 428 * connections, or {@code null} to indicate 429 * that the maximum connection age should be 430 * determined by the associated connection 431 * pool. 432 */ 433 public void setMaxFailoverConnectionAgeMillis( 434 final Long maxFailoverConnectionAge) 435 { 436 if (maxFailoverConnectionAge == null) 437 { 438 this.maxFailoverConnectionAge = null; 439 } 440 else if (maxFailoverConnectionAge > 0L) 441 { 442 this.maxFailoverConnectionAge = maxFailoverConnectionAge; 443 } 444 else 445 { 446 this.maxFailoverConnectionAge = 0L; 447 } 448 } 449 450 451 452 /** 453 * {@inheritDoc} 454 */ 455 @Override() 456 public LDAPConnection getConnection() 457 throws LDAPException 458 { 459 return getConnection(null); 460 } 461 462 463 464 /** 465 * {@inheritDoc} 466 */ 467 @Override() 468 public LDAPConnection getConnection( 469 final LDAPConnectionPoolHealthCheck healthCheck) 470 throws LDAPException 471 { 472 if (reOrderOnFailover.get() && (serverSets.length > 1)) 473 { 474 synchronized (this) 475 { 476 // First, try to get a connection using the first set in the list. If 477 // this succeeds, then we don't need to go any further. 478 try 479 { 480 return serverSets[0].getConnection(healthCheck); 481 } 482 catch (final LDAPException le) 483 { 484 debugException(le); 485 } 486 487 // If we've gotten here, then we will need to re-order the list unless 488 // all other attempts fail. 489 int successfulPos = -1; 490 LDAPConnection conn = null; 491 LDAPException lastException = null; 492 for (int i=1; i < serverSets.length; i++) 493 { 494 try 495 { 496 conn = serverSets[i].getConnection(healthCheck); 497 successfulPos = i; 498 break; 499 } 500 catch (final LDAPException le) 501 { 502 debugException(le); 503 lastException = le; 504 } 505 } 506 507 if (successfulPos > 0) 508 { 509 int pos = 0; 510 final ServerSet[] setCopy = new ServerSet[serverSets.length]; 511 for (int i=successfulPos; i < serverSets.length; i++) 512 { 513 setCopy[pos++] = serverSets[i]; 514 } 515 516 for (int i=0; i < successfulPos; i++) 517 { 518 setCopy[pos++] = serverSets[i]; 519 } 520 521 System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length); 522 if (maxFailoverConnectionAge != null) 523 { 524 conn.setAttachment( 525 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE, 526 maxFailoverConnectionAge); 527 } 528 return conn; 529 } 530 else 531 { 532 throw lastException; 533 } 534 } 535 } 536 else 537 { 538 LDAPException lastException = null; 539 540 boolean first = true; 541 for (final ServerSet s : serverSets) 542 { 543 try 544 { 545 final LDAPConnection conn = s.getConnection(healthCheck); 546 if ((! first) && (maxFailoverConnectionAge != null)) 547 { 548 conn.setAttachment( 549 LDAPConnectionPool.ATTACHMENT_NAME_MAX_CONNECTION_AGE, 550 maxFailoverConnectionAge); 551 } 552 return conn; 553 } 554 catch (LDAPException le) 555 { 556 first = false; 557 debugException(le); 558 lastException = le; 559 } 560 } 561 562 throw lastException; 563 } 564 } 565 566 567 568 /** 569 * {@inheritDoc} 570 */ 571 @Override() 572 public void toString(final StringBuilder buffer) 573 { 574 buffer.append("FailoverServerSet(serverSets={"); 575 576 for (int i=0; i < serverSets.length; i++) 577 { 578 if (i > 0) 579 { 580 buffer.append(", "); 581 } 582 583 serverSets[i].toString(buffer); 584 } 585 586 buffer.append("})"); 587 } 588}