001    /*
002     * Copyright 2012-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2012-2015 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    }