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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.concurrent.ArrayBlockingQueue;
026import java.util.concurrent.TimeUnit;
027import java.util.concurrent.atomic.AtomicBoolean;
028import javax.net.SocketFactory;
029
030import com.unboundid.util.Debug;
031import com.unboundid.util.NotMutable;
032import com.unboundid.util.StaticUtils;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035import com.unboundid.util.Validator;
036
037import 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)
093public 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}