001/* 002 * Copyright 2013-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2018 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.ArrayList; 026import java.util.Iterator; 027import java.util.List; 028import java.util.TreeMap; 029import javax.net.SocketFactory; 030 031import com.unboundid.util.NotMutable; 032import com.unboundid.util.ObjectPair; 033import com.unboundid.util.ThreadSafety; 034import com.unboundid.util.ThreadSafetyLevel; 035 036import static com.unboundid.util.Debug.*; 037import static com.unboundid.util.Validator.*; 038 039 040 041/** 042 * This class provides a server set implementation that will establish a 043 * connection to the server with the fewest established connections previously 044 * created by the same server set instance. If there are multiple servers that 045 * share the fewest number of established connections, the first one in the list 046 * will be chosen. If a server is unavailable when an attempt is made to 047 * establish a connection to it, then the connection will be established to the 048 * available server with the next fewest number of established connections. 049 * <BR><BR> 050 * Note that this server set implementation is primarily intended for use with 051 * connection pools, but is also suitable for cases in which standalone 052 * connections are created as long as there will not be any attempt to close the 053 * connections when they are re-established. It is not suitable for use in 054 * connections that may be re-established one or more times after being closed. 055 * <BR><BR> 056 * <H2>Example</H2> 057 * The following example demonstrates the process for creating a fewest 058 * connections server set that may be used to establish connections to either of 059 * two servers. 060 * <PRE> 061 * // Create arrays with the addresses and ports of the directory server 062 * // instances. 063 * String[] addresses = 064 * { 065 * server1Address, 066 * server2Address 067 * }; 068 * int[] ports = 069 * { 070 * server1Port, 071 * server2Port 072 * }; 073 * 074 * // Create the server set using the address and port arrays. 075 * FewestConnectionsServerSet fewestConnectionsSet = 076 * new FewestConnectionsServerSet(addresses, ports); 077 * 078 * // Verify that we can establish a single connection using the server set. 079 * LDAPConnection connection = fewestConnectionsSet.getConnection(); 080 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 081 * connection.close(); 082 * 083 * // Verify that we can establish a connection pool using the server set. 084 * SimpleBindRequest bindRequest = 085 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 086 * LDAPConnectionPool pool = 087 * new LDAPConnectionPool(fewestConnectionsSet, bindRequest, 10); 088 * RootDSE rootDSEFromPool = pool.getRootDSE(); 089 * pool.close(); 090 * </PRE> 091 */ 092@NotMutable() 093@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 094public final class FewestConnectionsServerSet 095 extends ServerSet 096{ 097 // The bind request to use to authenticate connections created by this 098 // server set. 099 private final BindRequest bindRequest; 100 101 // The port numbers of the target servers. 102 private final int[] ports; 103 104 // The set of connection options to use for new connections. 105 private final LDAPConnectionOptions connectionOptions; 106 107 // A list of the potentially-established connections created by this server 108 // set. 109 private final List<LDAPConnection> establishedConnections; 110 111 // The post-connect processor to invoke against connections created by this 112 // server set. 113 private final PostConnectProcessor postConnectProcessor; 114 115 // The socket factory to use to establish connections. 116 private final SocketFactory socketFactory; 117 118 // The addresses of the target servers. 119 private final String[] addresses; 120 121 122 123 /** 124 * Creates a new fewest connections server set with the specified set of 125 * directory server addresses and port numbers. It will use the default 126 * socket factory provided by the JVM to create the underlying sockets. 127 * 128 * @param addresses The addresses of the directory servers to which the 129 * connections should be established. It must not be 130 * {@code null} or empty. 131 * @param ports The ports of the directory servers to which the 132 * connections should be established. It must not be 133 * {@code null}, and it must have the same number of 134 * elements as the {@code addresses} array. The order of 135 * elements in the {@code addresses} array must correspond 136 * to the order of elements in the {@code ports} array. 137 */ 138 public FewestConnectionsServerSet(final String[] addresses, final int[] ports) 139 { 140 this(addresses, ports, null, null); 141 } 142 143 144 145 /** 146 * Creates a new fewest connections server set with the specified set of 147 * directory server addresses and port numbers. It will use the default 148 * socket factory provided by the JVM to create the underlying sockets. 149 * 150 * @param addresses The addresses of the directory servers to which 151 * the connections should be established. It must 152 * not be {@code null} or empty. 153 * @param ports The ports of the directory servers to which the 154 * connections should be established. It must not 155 * be {@code null}, and it must have the same 156 * number of elements as the {@code addresses} 157 * array. The order of elements in the 158 * {@code addresses} array must correspond to the 159 * order of elements in the {@code ports} array. 160 * @param connectionOptions The set of connection options to use for the 161 * underlying connections. 162 */ 163 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 164 final LDAPConnectionOptions connectionOptions) 165 { 166 this(addresses, ports, null, connectionOptions); 167 } 168 169 170 171 /** 172 * Creates a new fewest connections server set with the specified set of 173 * directory server addresses and port numbers. It will use the provided 174 * socket factory to create the underlying sockets. 175 * 176 * @param addresses The addresses of the directory servers to which the 177 * connections should be established. It must not be 178 * {@code null} or empty. 179 * @param ports The ports of the directory servers to which the 180 * connections should be established. It must not be 181 * {@code null}, and it must have the same number of 182 * elements as the {@code addresses} array. The order 183 * of elements in the {@code addresses} array must 184 * correspond to the order of elements in the 185 * {@code ports} array. 186 * @param socketFactory The socket factory to use to create the underlying 187 * connections. 188 */ 189 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 190 final SocketFactory socketFactory) 191 { 192 this(addresses, ports, socketFactory, null); 193 } 194 195 196 197 /** 198 * Creates a new fewest connections server set with the specified set of 199 * directory server addresses and port numbers. It will use the provided 200 * socket factory to create the underlying sockets. 201 * 202 * @param addresses The addresses of the directory servers to which 203 * the connections should be established. It must 204 * not be {@code null} or empty. 205 * @param ports The ports of the directory servers to which the 206 * connections should be established. It must not 207 * be {@code null}, and it must have the same 208 * number of elements as the {@code addresses} 209 * array. The order of elements in the 210 * {@code addresses} array must correspond to the 211 * order of elements in the {@code ports} array. 212 * @param socketFactory The socket factory to use to create the 213 * underlying connections. 214 * @param connectionOptions The set of connection options to use for the 215 * underlying connections. 216 */ 217 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 218 final SocketFactory socketFactory, 219 final LDAPConnectionOptions connectionOptions) 220 { 221 this(addresses, ports, socketFactory, connectionOptions, null, null); 222 } 223 224 225 226 /** 227 * Creates a new fewest connections server set with the specified set of 228 * directory server addresses and port numbers. It will use the provided 229 * socket factory to create the underlying sockets. 230 * 231 * @param addresses The addresses of the directory servers to 232 * which the connections should be established. 233 * It must not be {@code null} or empty. 234 * @param ports The ports of the directory servers to which 235 * the connections should be established. It 236 * must not be {@code null}, and it must have 237 * the same number of elements as the 238 * {@code addresses} array. The order of 239 * elements in the {@code addresses} array must 240 * correspond to the order of elements in the 241 * {@code ports} array. 242 * @param socketFactory The socket factory to use to create the 243 * underlying connections. 244 * @param connectionOptions The set of connection options to use for the 245 * underlying connections. 246 * @param bindRequest The bind request that should be used to 247 * authenticate newly-established connections. 248 * It may be {@code null} if this server set 249 * should not perform any authentication. 250 * @param postConnectProcessor The post-connect processor that should be 251 * invoked on newly-established connections. It 252 * may be {@code null} if this server set should 253 * not perform any post-connect processing. 254 */ 255 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 256 final SocketFactory socketFactory, 257 final LDAPConnectionOptions connectionOptions, 258 final BindRequest bindRequest, 259 final PostConnectProcessor postConnectProcessor) 260 { 261 ensureNotNull(addresses, ports); 262 ensureTrue(addresses.length > 0, 263 "FewestConnectionsServerSet.addresses must not be empty."); 264 ensureTrue(addresses.length == ports.length, 265 "FewestConnectionsServerSet addresses and ports arrays must " + 266 "be the same size."); 267 268 this.addresses = addresses; 269 this.ports = ports; 270 this.bindRequest = bindRequest; 271 this.postConnectProcessor = postConnectProcessor; 272 273 establishedConnections = new ArrayList<LDAPConnection>(100); 274 275 if (socketFactory == null) 276 { 277 this.socketFactory = SocketFactory.getDefault(); 278 } 279 else 280 { 281 this.socketFactory = socketFactory; 282 } 283 284 if (connectionOptions == null) 285 { 286 this.connectionOptions = new LDAPConnectionOptions(); 287 } 288 else 289 { 290 this.connectionOptions = connectionOptions; 291 } 292 } 293 294 295 296 /** 297 * Retrieves the addresses of the directory servers to which the connections 298 * should be established. 299 * 300 * @return The addresses of the directory servers to which the connections 301 * should be established. 302 */ 303 public String[] getAddresses() 304 { 305 return addresses; 306 } 307 308 309 310 /** 311 * Retrieves the ports of the directory servers to which the connections 312 * should be established. 313 * 314 * @return The ports of the directory servers to which the connections should 315 * be established. 316 */ 317 public int[] getPorts() 318 { 319 return ports; 320 } 321 322 323 324 /** 325 * Retrieves the socket factory that will be used to establish connections. 326 * 327 * @return The socket factory that will be used to establish connections. 328 */ 329 public SocketFactory getSocketFactory() 330 { 331 return socketFactory; 332 } 333 334 335 336 /** 337 * Retrieves the set of connection options that will be used for underlying 338 * connections. 339 * 340 * @return The set of connection options that will be used for underlying 341 * connections. 342 */ 343 public LDAPConnectionOptions getConnectionOptions() 344 { 345 return connectionOptions; 346 } 347 348 349 350 /** 351 * {@inheritDoc} 352 */ 353 @Override() 354 public boolean includesAuthentication() 355 { 356 return (bindRequest != null); 357 } 358 359 360 361 /** 362 * {@inheritDoc} 363 */ 364 @Override() 365 public boolean includesPostConnectProcessing() 366 { 367 return (postConnectProcessor != null); 368 } 369 370 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override() 376 public LDAPConnection getConnection() 377 throws LDAPException 378 { 379 return getConnection(null); 380 } 381 382 383 384 /** 385 * {@inheritDoc} 386 */ 387 @Override() 388 public synchronized LDAPConnection getConnection( 389 final LDAPConnectionPoolHealthCheck healthCheck) 390 throws LDAPException 391 { 392 // Count the number of connections established to each server. 393 final int[] counts = new int[addresses.length]; 394 final Iterator<LDAPConnection> iterator = establishedConnections.iterator(); 395 while (iterator.hasNext()) 396 { 397 final LDAPConnection conn = iterator.next(); 398 if (! conn.isConnected()) 399 { 400 iterator.remove(); 401 continue; 402 } 403 404 int slot = -1; 405 for (int i=0; i < addresses.length; i++) 406 { 407 if (addresses[i].equals(conn.getConnectedAddress()) && 408 (ports[i] == conn.getConnectedPort())) 409 { 410 slot = i; 411 break; 412 } 413 } 414 415 if (slot < 0) 416 { 417 // This indicates a connection is established to some address:port that 418 // we don't expect. This shouldn't happen under normal circumstances. 419 iterator.remove(); 420 break; 421 } 422 else 423 { 424 counts[slot]++; 425 } 426 } 427 428 429 // Sort the servers based on the number of established connections. 430 final TreeMap<Integer,List<ObjectPair<String,Integer>>> m = 431 new TreeMap<Integer,List<ObjectPair<String,Integer>>>(); 432 for (int i=0; i < counts.length; i++) 433 { 434 final Integer count = counts[i]; 435 List<ObjectPair<String,Integer>> serverList = m.get(count); 436 if (serverList == null) 437 { 438 serverList = new ArrayList<ObjectPair<String,Integer>>(counts.length); 439 m.put(count, serverList); 440 } 441 serverList.add(new ObjectPair<String,Integer>(addresses[i], ports[i])); 442 } 443 444 445 // Iterate through the sorted elements, trying each server in sequence until 446 // we are able to successfully establish a connection. 447 LDAPException lastException = null; 448 for (final List<ObjectPair<String,Integer>> l : m.values()) 449 { 450 for (final ObjectPair<String,Integer> p : l) 451 { 452 try 453 { 454 final LDAPConnection conn = new LDAPConnection(socketFactory, 455 connectionOptions, p.getFirst(), p.getSecond()); 456 doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, 457 postConnectProcessor, healthCheck); 458 establishedConnections.add(conn); 459 return conn; 460 } 461 catch (final LDAPException le) 462 { 463 debugException(le); 464 lastException = le; 465 } 466 } 467 } 468 469 470 // If we've gotten here, then we've tried all servers without any success, 471 // so throw the last exception that was encountered. 472 throw lastException; 473 } 474 475 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override() 481 public void toString(final StringBuilder buffer) 482 { 483 buffer.append("FewestConnectionsServerSet(servers={"); 484 485 for (int i=0; i < addresses.length; i++) 486 { 487 if (i > 0) 488 { 489 buffer.append(", "); 490 } 491 492 buffer.append(addresses[i]); 493 buffer.append(':'); 494 buffer.append(ports[i]); 495 } 496 497 buffer.append("}, includesAuthentication="); 498 buffer.append(bindRequest != null); 499 buffer.append(", includesPostConnectProcessing="); 500 buffer.append(postConnectProcessor != null); 501 buffer.append(", establishedConnections="); 502 503 synchronized (this) 504 { 505 final Iterator<LDAPConnection> iterator = 506 establishedConnections.iterator(); 507 while (iterator.hasNext()) 508 { 509 final LDAPConnection conn = iterator.next(); 510 if (! conn.isConnected()) 511 { 512 iterator.remove(); 513 } 514 } 515 buffer.append(establishedConnections.size()); 516 } 517 518 buffer.append(')'); 519 } 520}