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