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    }