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 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Iterator;
027 import java.util.List;
028 import java.util.TreeMap;
029 import javax.net.SocketFactory;
030
031 import com.unboundid.util.NotMutable;
032 import com.unboundid.util.ObjectPair;
033 import com.unboundid.util.ThreadSafety;
034 import com.unboundid.util.ThreadSafetyLevel;
035
036 import static com.unboundid.util.Debug.*;
037 import 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)
094 public 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 }