001 /*
002 * Copyright 2012-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2012-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.concurrent.ArrayBlockingQueue;
026 import java.util.concurrent.TimeUnit;
027 import java.util.concurrent.atomic.AtomicBoolean;
028 import javax.net.SocketFactory;
029
030 import com.unboundid.util.Debug;
031 import com.unboundid.util.NotMutable;
032 import com.unboundid.util.StaticUtils;
033 import com.unboundid.util.ThreadSafety;
034 import com.unboundid.util.ThreadSafetyLevel;
035 import com.unboundid.util.Validator;
036
037 import static com.unboundid.ldap.sdk.LDAPMessages.*;
038
039
040
041 /**
042 * This class provides a server set implementation that will attempt to
043 * establish connections to all associated servers in parallel, keeping the one
044 * that was first to be successfully established and closing all others.
045 * <BR><BR>
046 * Note that this server set implementation may only be used in conjunction with
047 * connection options that allow the associated socket factory to create
048 * multiple connections in parallel. If the
049 * {@link LDAPConnectionOptions#allowConcurrentSocketFactoryUse} method returns
050 * false for the associated connection options, then the {@code getConnection}
051 * methods will throw an exception.
052 * <BR><BR>
053 * <H2>Example</H2>
054 * The following example demonstrates the process for creating a fastest connect
055 * server set that may be used to establish connections to either of two
056 * servers. When using the server set to attempt to create a connection, it
057 * will try both in parallel and will return the first connection that it is
058 * able to establish:
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 * FastestConnectServerSet fastestConnectSet =
075 * new FastestConnectServerSet(addresses, ports);
076 *
077 * // Verify that we can establish a single connection using the server set.
078 * LDAPConnection connection = fastestConnectSet.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(fastestConnectSet, bindRequest, 10);
087 * RootDSE rootDSEFromPool = pool.getRootDSE();
088 * pool.close();
089 * </PRE>
090 */
091 @NotMutable()
092 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093 public final class FastestConnectServerSet
094 extends ServerSet
095 {
096 // The port numbers of the target servers.
097 private final int[] ports;
098
099 // The set of connection options to use for new connections.
100 private final LDAPConnectionOptions connectionOptions;
101
102 // The socket factory to use to establish connections.
103 private final SocketFactory socketFactory;
104
105 // The addresses of the target servers.
106 private final String[] addresses;
107
108
109
110 /**
111 * Creates a new fastest connect server set with the specified set of
112 * directory server addresses and port numbers. It will use the default
113 * socket factory provided by the JVM to create the underlying sockets.
114 *
115 * @param addresses The addresses of the directory servers to which the
116 * connections should be established. It must not be
117 * {@code null} or empty.
118 * @param ports The ports of the directory servers to which the
119 * connections should be established. It must not be
120 * {@code null}, and it must have the same number of
121 * elements as the {@code addresses} array. The order of
122 * elements in the {@code addresses} array must correspond
123 * to the order of elements in the {@code ports} array.
124 */
125 public FastestConnectServerSet(final String[] addresses, final int[] ports)
126 {
127 this(addresses, ports, null, null);
128 }
129
130
131
132 /**
133 * Creates a new fastest connect server set with the specified set of
134 * directory server addresses and port numbers. It will use the default
135 * socket factory provided by the JVM to create the underlying sockets.
136 *
137 * @param addresses The addresses of the directory servers to which
138 * the connections should be established. It must
139 * not be {@code null} or empty.
140 * @param ports The ports of the directory servers to which the
141 * connections should be established. It must not
142 * be {@code null}, and it must have the same
143 * number of elements as the {@code addresses}
144 * array. The order of elements in the
145 * {@code addresses} array must correspond to the
146 * order of elements in the {@code ports} array.
147 * @param connectionOptions The set of connection options to use for the
148 * underlying connections.
149 */
150 public FastestConnectServerSet(final String[] addresses, final int[] ports,
151 final LDAPConnectionOptions connectionOptions)
152 {
153 this(addresses, ports, null, connectionOptions);
154 }
155
156
157
158 /**
159 * Creates a new fastest connect server set with the specified set of
160 * directory server addresses and port numbers. It will use the provided
161 * socket factory to create the underlying sockets.
162 *
163 * @param addresses The addresses of the directory servers to which the
164 * connections should be established. It must not be
165 * {@code null} or empty.
166 * @param ports The ports of the directory servers to which the
167 * connections should be established. It must not be
168 * {@code null}, and it must have the same number of
169 * elements as the {@code addresses} array. The order
170 * of elements in the {@code addresses} array must
171 * correspond to the order of elements in the
172 * {@code ports} array.
173 * @param socketFactory The socket factory to use to create the underlying
174 * connections.
175 */
176 public FastestConnectServerSet(final String[] addresses, final int[] ports,
177 final SocketFactory socketFactory)
178 {
179 this(addresses, ports, socketFactory, null);
180 }
181
182
183
184 /**
185 * Creates a new fastest connect server set with the specified set of
186 * directory server addresses and port numbers. It will use the provided
187 * socket factory to create the underlying sockets.
188 *
189 * @param addresses The addresses of the directory servers to which
190 * the connections should be established. It must
191 * not be {@code null} or empty.
192 * @param ports The ports of the directory servers to which the
193 * connections should be established. It must not
194 * be {@code null}, and it must have the same
195 * number of elements as the {@code addresses}
196 * array. The order of elements in the
197 * {@code addresses} array must correspond to the
198 * order of elements in the {@code ports} array.
199 * @param socketFactory The socket factory to use to create the
200 * underlying connections.
201 * @param connectionOptions The set of connection options to use for the
202 * underlying connections.
203 */
204 public FastestConnectServerSet(final String[] addresses, final int[] ports,
205 final SocketFactory socketFactory,
206 final LDAPConnectionOptions connectionOptions)
207 {
208 Validator.ensureNotNull(addresses, ports);
209 Validator.ensureTrue(addresses.length > 0,
210 "RoundRobinServerSet.addresses must not be empty.");
211 Validator.ensureTrue(addresses.length == ports.length,
212 "RoundRobinServerSet addresses and ports arrays must be the same " +
213 "size.");
214
215 this.addresses = addresses;
216 this.ports = ports;
217
218 if (socketFactory == null)
219 {
220 this.socketFactory = SocketFactory.getDefault();
221 }
222 else
223 {
224 this.socketFactory = socketFactory;
225 }
226
227 if (connectionOptions == null)
228 {
229 this.connectionOptions = new LDAPConnectionOptions();
230 }
231 else
232 {
233 this.connectionOptions = connectionOptions;
234 }
235 }
236
237
238
239 /**
240 * Retrieves the addresses of the directory servers to which the connections
241 * should be established.
242 *
243 * @return The addresses of the directory servers to which the connections
244 * should be established.
245 */
246 public String[] getAddresses()
247 {
248 return addresses;
249 }
250
251
252
253 /**
254 * Retrieves the ports of the directory servers to which the connections
255 * should be established.
256 *
257 * @return The ports of the directory servers to which the connections should
258 * be established.
259 */
260 public int[] getPorts()
261 {
262 return ports;
263 }
264
265
266
267 /**
268 * Retrieves the socket factory that will be used to establish connections.
269 *
270 * @return The socket factory that will be used to establish connections.
271 */
272 public SocketFactory getSocketFactory()
273 {
274 return socketFactory;
275 }
276
277
278
279 /**
280 * Retrieves the set of connection options that will be used for underlying
281 * connections.
282 *
283 * @return The set of connection options that will be used for underlying
284 * connections.
285 */
286 public LDAPConnectionOptions getConnectionOptions()
287 {
288 return connectionOptions;
289 }
290
291
292
293 /**
294 * {@inheritDoc}
295 */
296 @Override()
297 public LDAPConnection getConnection()
298 throws LDAPException
299 {
300 return getConnection(null);
301 }
302
303
304
305 /**
306 * {@inheritDoc}
307 */
308 @Override()
309 public LDAPConnection getConnection(
310 final LDAPConnectionPoolHealthCheck healthCheck)
311 throws LDAPException
312 {
313 if (! connectionOptions.allowConcurrentSocketFactoryUse())
314 {
315 throw new LDAPException(ResultCode.CONNECT_ERROR,
316 ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get());
317 }
318
319 final ArrayBlockingQueue<Object> resultQueue =
320 new ArrayBlockingQueue<Object>(addresses.length, false);
321 final AtomicBoolean connectionSelected = new AtomicBoolean(false);
322
323 final FastestConnectThread[] connectThreads =
324 new FastestConnectThread[addresses.length];
325 for (int i=0; i < connectThreads.length; i++)
326 {
327 connectThreads[i] = new FastestConnectThread(addresses[i], ports[i],
328 socketFactory, connectionOptions, healthCheck, resultQueue,
329 connectionSelected);
330 }
331
332 for (final FastestConnectThread t : connectThreads)
333 {
334 t.start();
335 }
336
337 try
338 {
339 final long effectiveConnectTimeout;
340 final long connectTimeout =
341 connectionOptions.getConnectTimeoutMillis();
342 if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE))
343 {
344 effectiveConnectTimeout = connectTimeout;
345 }
346 else
347 {
348 effectiveConnectTimeout = Integer.MAX_VALUE;
349 }
350
351 int connectFailures = 0;
352 final long stopWaitingTime =
353 System.currentTimeMillis() + effectiveConnectTimeout;
354 while (true)
355 {
356 final Object o;
357 final long waitTime = stopWaitingTime - System.currentTimeMillis();
358 if (waitTime > 0L)
359 {
360 o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS);
361 }
362 else
363 {
364 o = resultQueue.poll();
365 }
366
367 if (o == null)
368 {
369 throw new LDAPException(ResultCode.CONNECT_ERROR,
370 ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get(
371 effectiveConnectTimeout));
372 }
373 else if (o instanceof LDAPConnection)
374 {
375 return (LDAPConnection) o;
376 }
377 else
378 {
379 connectFailures++;
380 if (connectFailures >= addresses.length)
381 {
382 throw new LDAPException(ResultCode.CONNECT_ERROR,
383 ERR_FASTEST_CONNECT_SET_ALL_FAILED.get());
384 }
385 }
386 }
387 }
388 catch (final LDAPException le)
389 {
390 Debug.debugException(le);
391 throw le;
392 }
393 catch (final Exception e)
394 {
395 Debug.debugException(e);
396 throw new LDAPException(ResultCode.CONNECT_ERROR,
397 ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get(
398 StaticUtils.getExceptionMessage(e)),
399 e);
400 }
401 }
402
403
404
405 /**
406 * {@inheritDoc}
407 */
408 @Override()
409 public void toString(final StringBuilder buffer)
410 {
411 buffer.append("FastestConnectServerSet(servers={");
412
413 for (int i=0; i < addresses.length; i++)
414 {
415 if (i > 0)
416 {
417 buffer.append(", ");
418 }
419
420 buffer.append(addresses[i]);
421 buffer.append(':');
422 buffer.append(ports[i]);
423 }
424
425 buffer.append("})");
426 }
427 }