001/* 002 * Copyright 2013-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-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.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 port numbers of the target servers. 098 private final int[] ports; 099 100 // The set of connection options to use for new connections. 101 private final LDAPConnectionOptions connectionOptions; 102 103 // A list of the potentially-established connections created by this server 104 // set. 105 private final List<LDAPConnection> establishedConnections; 106 107 // The socket factory to use to establish connections. 108 private final SocketFactory socketFactory; 109 110 // The addresses of the target servers. 111 private final String[] addresses; 112 113 114 115 /** 116 * Creates a new fewest connections server set with the specified set of 117 * directory server addresses and port numbers. It will use the default 118 * socket factory provided by the JVM to create the underlying sockets. 119 * 120 * @param addresses The addresses of the directory servers to which the 121 * connections should be established. It must not be 122 * {@code null} or empty. 123 * @param ports The ports of the directory servers to which the 124 * connections should be established. It must not be 125 * {@code null}, and it must have the same number of 126 * elements as the {@code addresses} array. The order of 127 * elements in the {@code addresses} array must correspond 128 * to the order of elements in the {@code ports} array. 129 */ 130 public FewestConnectionsServerSet(final String[] addresses, final int[] ports) 131 { 132 this(addresses, ports, null, null); 133 } 134 135 136 137 /** 138 * Creates a new fewest connections server set with the specified set of 139 * directory server addresses and port numbers. It will use the default 140 * socket factory provided by the JVM to create the underlying sockets. 141 * 142 * @param addresses The addresses of the directory servers to which 143 * the connections should be established. It must 144 * not be {@code null} or empty. 145 * @param ports The ports of the directory servers to which the 146 * connections should be established. It must not 147 * be {@code null}, and it must have the same 148 * number of elements as the {@code addresses} 149 * array. The order of elements in the 150 * {@code addresses} array must correspond to the 151 * order of elements in the {@code ports} array. 152 * @param connectionOptions The set of connection options to use for the 153 * underlying connections. 154 */ 155 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 156 final LDAPConnectionOptions connectionOptions) 157 { 158 this(addresses, ports, null, connectionOptions); 159 } 160 161 162 163 /** 164 * Creates a new fewest connections server set with the specified set of 165 * directory server addresses and port numbers. It will use the provided 166 * socket factory to create the underlying sockets. 167 * 168 * @param addresses The addresses of the directory servers to which the 169 * connections should be established. It must not be 170 * {@code null} or empty. 171 * @param ports The ports of the directory servers to which the 172 * connections should be established. It must not be 173 * {@code null}, and it must have the same number of 174 * elements as the {@code addresses} array. The order 175 * of elements in the {@code addresses} array must 176 * correspond to the order of elements in the 177 * {@code ports} array. 178 * @param socketFactory The socket factory to use to create the underlying 179 * connections. 180 */ 181 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 182 final SocketFactory socketFactory) 183 { 184 this(addresses, ports, socketFactory, null); 185 } 186 187 188 189 /** 190 * Creates a new fewest connections server set with the specified set of 191 * directory server addresses and port numbers. It will use the provided 192 * socket factory to create the underlying sockets. 193 * 194 * @param addresses The addresses of the directory servers to which 195 * the connections should be established. It must 196 * not be {@code null} or empty. 197 * @param ports The ports of the directory servers to which the 198 * connections should be established. It must not 199 * be {@code null}, and it must have the same 200 * number of elements as the {@code addresses} 201 * array. The order of elements in the 202 * {@code addresses} array must correspond to the 203 * order of elements in the {@code ports} array. 204 * @param socketFactory The socket factory to use to create the 205 * underlying connections. 206 * @param connectionOptions The set of connection options to use for the 207 * underlying connections. 208 */ 209 public FewestConnectionsServerSet(final String[] addresses, final int[] ports, 210 final SocketFactory socketFactory, 211 final LDAPConnectionOptions connectionOptions) 212 { 213 ensureNotNull(addresses, ports); 214 ensureTrue(addresses.length > 0, 215 "FewestConnectionsServerSet.addresses must not be empty."); 216 ensureTrue(addresses.length == ports.length, 217 "FewestConnectionsServerSet addresses and ports arrays must " + 218 "be the same size."); 219 220 this.addresses = addresses; 221 this.ports = ports; 222 223 establishedConnections = new ArrayList<LDAPConnection>(100); 224 225 if (socketFactory == null) 226 { 227 this.socketFactory = SocketFactory.getDefault(); 228 } 229 else 230 { 231 this.socketFactory = socketFactory; 232 } 233 234 if (connectionOptions == null) 235 { 236 this.connectionOptions = new LDAPConnectionOptions(); 237 } 238 else 239 { 240 this.connectionOptions = connectionOptions; 241 } 242 } 243 244 245 246 /** 247 * Retrieves the addresses of the directory servers to which the connections 248 * should be established. 249 * 250 * @return The addresses of the directory servers to which the connections 251 * should be established. 252 */ 253 public String[] getAddresses() 254 { 255 return addresses; 256 } 257 258 259 260 /** 261 * Retrieves the ports of the directory servers to which the connections 262 * should be established. 263 * 264 * @return The ports of the directory servers to which the connections should 265 * be established. 266 */ 267 public int[] getPorts() 268 { 269 return ports; 270 } 271 272 273 274 /** 275 * Retrieves the socket factory that will be used to establish connections. 276 * 277 * @return The socket factory that will be used to establish connections. 278 */ 279 public SocketFactory getSocketFactory() 280 { 281 return socketFactory; 282 } 283 284 285 286 /** 287 * Retrieves the set of connection options that will be used for underlying 288 * connections. 289 * 290 * @return The set of connection options that will be used for underlying 291 * connections. 292 */ 293 public LDAPConnectionOptions getConnectionOptions() 294 { 295 return connectionOptions; 296 } 297 298 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override() 304 public LDAPConnection getConnection() 305 throws LDAPException 306 { 307 return getConnection(null); 308 } 309 310 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override() 316 public synchronized LDAPConnection getConnection( 317 final LDAPConnectionPoolHealthCheck healthCheck) 318 throws LDAPException 319 { 320 // Count the number of connections established to each server. 321 final int[] counts = new int[addresses.length]; 322 final Iterator<LDAPConnection> iterator = establishedConnections.iterator(); 323 while (iterator.hasNext()) 324 { 325 final LDAPConnection conn = iterator.next(); 326 if (! conn.isConnected()) 327 { 328 iterator.remove(); 329 continue; 330 } 331 332 int slot = -1; 333 for (int i=0; i < addresses.length; i++) 334 { 335 if (addresses[i].equals(conn.getConnectedAddress()) && 336 (ports[i] == conn.getConnectedPort())) 337 { 338 slot = i; 339 break; 340 } 341 } 342 343 if (slot < 0) 344 { 345 // This indicates a connection is established to some address:port that 346 // we don't expect. This shouldn't happen under normal circumstances. 347 iterator.remove(); 348 break; 349 } 350 else 351 { 352 counts[slot]++; 353 } 354 } 355 356 357 // Sort the servers based on the number of established connections. 358 final TreeMap<Integer,List<ObjectPair<String,Integer>>> m = 359 new TreeMap<Integer,List<ObjectPair<String,Integer>>>(); 360 for (int i=0; i < counts.length; i++) 361 { 362 final Integer count = counts[i]; 363 List<ObjectPair<String,Integer>> serverList = m.get(count); 364 if (serverList == null) 365 { 366 serverList = new ArrayList<ObjectPair<String,Integer>>(counts.length); 367 m.put(count, serverList); 368 } 369 serverList.add(new ObjectPair<String,Integer>(addresses[i], ports[i])); 370 } 371 372 373 // Iterate through the sorted elements, trying each server in sequence until 374 // we are able to successfully establish a connection. 375 LDAPException lastException = null; 376 for (final List<ObjectPair<String,Integer>> l : m.values()) 377 { 378 for (final ObjectPair<String,Integer> p : l) 379 { 380 try 381 { 382 final LDAPConnection conn = new LDAPConnection(socketFactory, 383 connectionOptions, p.getFirst(), p.getSecond()); 384 if (healthCheck != null) 385 { 386 try 387 { 388 healthCheck.ensureNewConnectionValid(conn); 389 } 390 catch (final LDAPException le) 391 { 392 debugException(le); 393 conn.close(); 394 throw le; 395 } 396 } 397 398 establishedConnections.add(conn); 399 return conn; 400 } 401 catch (final LDAPException le) 402 { 403 debugException(le); 404 lastException = le; 405 } 406 } 407 } 408 409 410 // If we've gotten here, then we've tried all servers without any success, 411 // so throw the last exception that was encountered. 412 throw lastException; 413 } 414 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override() 421 public void toString(final StringBuilder buffer) 422 { 423 buffer.append("FewestConnectionsServerSet(servers={"); 424 425 for (int i=0; i < addresses.length; i++) 426 { 427 if (i > 0) 428 { 429 buffer.append(", "); 430 } 431 432 buffer.append(addresses[i]); 433 buffer.append(':'); 434 buffer.append(ports[i]); 435 } 436 437 buffer.append("})"); 438 } 439}