001 /*
002 * Copyright 2012-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2012-2013 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 * String[] addresses =
061 * {
062 * "ds1.example.com",
063 * "ds2.example.com",
064 * };
065 * int[] ports =
066 * {
067 * 389,
068 * 389
069 * }
070 * FastestConnectServerSet fastestConnectSet =
071 * new FastestConnectServerSet(addresses, ports);
072 * </PRE>
073 */
074 @NotMutable()
075 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
076 public final class FastestConnectServerSet
077 extends ServerSet
078 {
079 // The port numbers of the target servers.
080 private final int[] ports;
081
082 // The set of connection options to use for new connections.
083 private final LDAPConnectionOptions connectionOptions;
084
085 // The socket factory to use to establish connections.
086 private final SocketFactory socketFactory;
087
088 // The addresses of the target servers.
089 private final String[] addresses;
090
091
092
093 /**
094 * Creates a new fastest connect server set with the specified set of
095 * directory server addresses and port numbers. It will use the default
096 * socket factory provided by the JVM to create the underlying sockets.
097 *
098 * @param addresses The addresses of the directory servers to which the
099 * connections should be established. It must not be
100 * {@code null} or empty.
101 * @param ports The ports of the directory servers to which the
102 * connections should be established. It must not be
103 * {@code null}, and it must have the same number of
104 * elements as the {@code addresses} array. The order of
105 * elements in the {@code addresses} array must correspond
106 * to the order of elements in the {@code ports} array.
107 */
108 public FastestConnectServerSet(final String[] addresses, final int[] ports)
109 {
110 this(addresses, ports, null, null);
111 }
112
113
114
115 /**
116 * Creates a new fastest connect 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
121 * the connections should be established. It must
122 * not be {@code null} or empty.
123 * @param ports The ports of the directory servers to which the
124 * connections should be established. It must not
125 * be {@code null}, and it must have the same
126 * number of elements as the {@code addresses}
127 * array. The order of elements in the
128 * {@code addresses} array must correspond to the
129 * order of elements in the {@code ports} array.
130 * @param connectionOptions The set of connection options to use for the
131 * underlying connections.
132 */
133 public FastestConnectServerSet(final String[] addresses, final int[] ports,
134 final LDAPConnectionOptions connectionOptions)
135 {
136 this(addresses, ports, null, connectionOptions);
137 }
138
139
140
141 /**
142 * Creates a new fastest connect server set with the specified set of
143 * directory server addresses and port numbers. It will use the provided
144 * socket factory to create the underlying sockets.
145 *
146 * @param addresses The addresses of the directory servers to which the
147 * connections should be established. It must not be
148 * {@code null} or empty.
149 * @param ports The ports of the directory servers to which the
150 * connections should be established. It must not be
151 * {@code null}, and it must have the same number of
152 * elements as the {@code addresses} array. The order
153 * of elements in the {@code addresses} array must
154 * correspond to the order of elements in the
155 * {@code ports} array.
156 * @param socketFactory The socket factory to use to create the underlying
157 * connections.
158 */
159 public FastestConnectServerSet(final String[] addresses, final int[] ports,
160 final SocketFactory socketFactory)
161 {
162 this(addresses, ports, socketFactory, null);
163 }
164
165
166
167 /**
168 * Creates a new fastest connect server set with the specified set of
169 * directory server addresses and port numbers. It will use the provided
170 * socket factory to create the underlying sockets.
171 *
172 * @param addresses The addresses of the directory servers to which
173 * the connections should be established. It must
174 * not be {@code null} or empty.
175 * @param ports The ports of the directory servers to which the
176 * connections should be established. It must not
177 * be {@code null}, and it must have the same
178 * number of elements as the {@code addresses}
179 * array. The order of elements in the
180 * {@code addresses} array must correspond to the
181 * order of elements in the {@code ports} array.
182 * @param socketFactory The socket factory to use to create the
183 * underlying connections.
184 * @param connectionOptions The set of connection options to use for the
185 * underlying connections.
186 */
187 public FastestConnectServerSet(final String[] addresses, final int[] ports,
188 final SocketFactory socketFactory,
189 final LDAPConnectionOptions connectionOptions)
190 {
191 Validator.ensureNotNull(addresses, ports);
192 Validator.ensureTrue(addresses.length > 0,
193 "RoundRobinServerSet.addresses must not be empty.");
194 Validator.ensureTrue(addresses.length == ports.length,
195 "RoundRobinServerSet addresses and ports arrays must be the same " +
196 "size.");
197
198 this.addresses = addresses;
199 this.ports = ports;
200
201 if (socketFactory == null)
202 {
203 this.socketFactory = SocketFactory.getDefault();
204 }
205 else
206 {
207 this.socketFactory = socketFactory;
208 }
209
210 if (connectionOptions == null)
211 {
212 this.connectionOptions = new LDAPConnectionOptions();
213 }
214 else
215 {
216 this.connectionOptions = connectionOptions;
217 }
218 }
219
220
221
222 /**
223 * Retrieves the addresses of the directory servers to which the connections
224 * should be established.
225 *
226 * @return The addresses of the directory servers to which the connections
227 * should be established.
228 */
229 public String[] getAddresses()
230 {
231 return addresses;
232 }
233
234
235
236 /**
237 * Retrieves the ports of the directory servers to which the connections
238 * should be established.
239 *
240 * @return The ports of the directory servers to which the connections should
241 * be established.
242 */
243 public int[] getPorts()
244 {
245 return ports;
246 }
247
248
249
250 /**
251 * Retrieves the socket factory that will be used to establish connections.
252 *
253 * @return The socket factory that will be used to establish connections.
254 */
255 public SocketFactory getSocketFactory()
256 {
257 return socketFactory;
258 }
259
260
261
262 /**
263 * Retrieves the set of connection options that will be used for underlying
264 * connections.
265 *
266 * @return The set of connection options that will be used for underlying
267 * connections.
268 */
269 public LDAPConnectionOptions getConnectionOptions()
270 {
271 return connectionOptions;
272 }
273
274
275
276 /**
277 * {@inheritDoc}
278 */
279 @Override()
280 public LDAPConnection getConnection()
281 throws LDAPException
282 {
283 return getConnection(null);
284 }
285
286
287
288 /**
289 * {@inheritDoc}
290 */
291 @Override()
292 public LDAPConnection getConnection(
293 final LDAPConnectionPoolHealthCheck healthCheck)
294 throws LDAPException
295 {
296 if (! connectionOptions.allowConcurrentSocketFactoryUse())
297 {
298 throw new LDAPException(ResultCode.CONNECT_ERROR,
299 ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get());
300 }
301
302 final ArrayBlockingQueue<Object> resultQueue =
303 new ArrayBlockingQueue<Object>(addresses.length, false);
304 final AtomicBoolean connectionSelected = new AtomicBoolean(false);
305
306 final FastestConnectThread[] connectThreads =
307 new FastestConnectThread[addresses.length];
308 for (int i=0; i < connectThreads.length; i++)
309 {
310 connectThreads[i] = new FastestConnectThread(addresses[i], ports[i],
311 socketFactory, connectionOptions, healthCheck, resultQueue,
312 connectionSelected);
313 }
314
315 for (final FastestConnectThread t : connectThreads)
316 {
317 t.start();
318 }
319
320 try
321 {
322 final long effectiveConnectTimeout;
323 final long connectTimeout =
324 connectionOptions.getConnectTimeoutMillis();
325 if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE))
326 {
327 effectiveConnectTimeout = connectTimeout;
328 }
329 else
330 {
331 effectiveConnectTimeout = Integer.MAX_VALUE;
332 }
333
334 int connectFailures = 0;
335 final long stopWaitingTime =
336 System.currentTimeMillis() + effectiveConnectTimeout;
337 while (true)
338 {
339 final Object o;
340 final long waitTime = stopWaitingTime - System.currentTimeMillis();
341 if (waitTime > 0L)
342 {
343 o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS);
344 }
345 else
346 {
347 o = resultQueue.poll();
348 }
349
350 if (o == null)
351 {
352 throw new LDAPException(ResultCode.CONNECT_ERROR,
353 ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get(
354 effectiveConnectTimeout));
355 }
356 else if (o instanceof LDAPConnection)
357 {
358 return (LDAPConnection) o;
359 }
360 else
361 {
362 connectFailures++;
363 if (connectFailures >= addresses.length)
364 {
365 throw new LDAPException(ResultCode.CONNECT_ERROR,
366 ERR_FASTEST_CONNECT_SET_ALL_FAILED.get());
367 }
368 }
369 }
370 }
371 catch (final LDAPException le)
372 {
373 Debug.debugException(le);
374 throw le;
375 }
376 catch (final Exception e)
377 {
378 Debug.debugException(e);
379 throw new LDAPException(ResultCode.CONNECT_ERROR,
380 ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get(
381 StaticUtils.getExceptionMessage(e)),
382 e);
383 }
384 }
385
386
387
388 /**
389 * {@inheritDoc}
390 */
391 @Override()
392 public void toString(final StringBuilder buffer)
393 {
394 buffer.append("FastestConnectServerSet(servers={");
395
396 for (int i=0; i < addresses.length; i++)
397 {
398 if (i > 0)
399 {
400 buffer.append(", ");
401 }
402
403 buffer.append(addresses[i]);
404 buffer.append(':');
405 buffer.append(ports[i]);
406 }
407
408 buffer.append("})");
409 }
410 }