001/*
002 * Copyright 2012-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2012-2019 Ping Identity Corporation
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 bind request to use to authenticate connections created by this
097  // server set.
098  private final BindRequest bindRequest;
099
100  // The port numbers of the target servers.
101  private final int[] ports;
102
103  // The set of connection options to use for new connections.
104  private final LDAPConnectionOptions connectionOptions;
105
106  // The post-connect processor to invoke against connections created by this
107  // server set.
108  private final PostConnectProcessor postConnectProcessor;
109
110  // The socket factory to use to establish connections.
111  private final SocketFactory socketFactory;
112
113  // The addresses of the target servers.
114  private final String[] addresses;
115
116
117
118  /**
119   * Creates a new fastest connect server set with the specified set of
120   * directory server addresses and port numbers.  It will use the default
121   * socket factory provided by the JVM to create the underlying sockets.
122   *
123   * @param  addresses  The addresses of the directory servers to which the
124   *                    connections should be established.  It must not be
125   *                    {@code null} or empty.
126   * @param  ports      The ports of the directory servers to which the
127   *                    connections should be established.  It must not be
128   *                    {@code null}, and it must have the same number of
129   *                    elements as the {@code addresses} array.  The order of
130   *                    elements in the {@code addresses} array must correspond
131   *                    to the order of elements in the {@code ports} array.
132   */
133  public FastestConnectServerSet(final String[] addresses, final int[] ports)
134  {
135    this(addresses, ports, null, null);
136  }
137
138
139
140  /**
141   * Creates a new fastest connect server set with the specified set of
142   * directory server addresses and port numbers.  It will use the default
143   * socket factory provided by the JVM to create the underlying sockets.
144   *
145   * @param  addresses          The addresses of the directory servers to which
146   *                            the connections should be established.  It must
147   *                            not be {@code null} or empty.
148   * @param  ports              The ports of the directory servers to which the
149   *                            connections should be established.  It must not
150   *                            be {@code null}, and it must have the same
151   *                            number of elements as the {@code addresses}
152   *                            array.  The order of elements in the
153   *                            {@code addresses} array must correspond to the
154   *                            order of elements in the {@code ports} array.
155   * @param  connectionOptions  The set of connection options to use for the
156   *                            underlying connections.
157   */
158  public FastestConnectServerSet(final String[] addresses, final int[] ports,
159                                 final LDAPConnectionOptions connectionOptions)
160  {
161    this(addresses, ports, null, connectionOptions);
162  }
163
164
165
166  /**
167   * Creates a new fastest connect server set with the specified set of
168   * directory server addresses and port numbers.  It will use the provided
169   * socket factory to create the underlying sockets.
170   *
171   * @param  addresses      The addresses of the directory servers to which the
172   *                        connections should be established.  It must not be
173   *                        {@code null} or empty.
174   * @param  ports          The ports of the directory servers to which the
175   *                        connections should be established.  It must not be
176   *                        {@code null}, and it must have the same number of
177   *                        elements as the {@code addresses} array.  The order
178   *                        of elements in the {@code addresses} array must
179   *                        correspond to the order of elements in the
180   *                        {@code ports} array.
181   * @param  socketFactory  The socket factory to use to create the underlying
182   *                        connections.
183   */
184  public FastestConnectServerSet(final String[] addresses, final int[] ports,
185                                 final SocketFactory socketFactory)
186  {
187    this(addresses, ports, socketFactory, null);
188  }
189
190
191
192  /**
193   * Creates a new fastest connect server set with the specified set of
194   * directory server addresses and port numbers.  It will use the provided
195   * socket factory to create the underlying sockets.
196   *
197   * @param  addresses          The addresses of the directory servers to which
198   *                            the connections should be established.  It must
199   *                            not be {@code null} or empty.
200   * @param  ports              The ports of the directory servers to which the
201   *                            connections should be established.  It must not
202   *                            be {@code null}, and it must have the same
203   *                            number of elements as the {@code addresses}
204   *                            array.  The order of elements in the
205   *                            {@code addresses} array must correspond to the
206   *                            order of elements in the {@code ports} array.
207   * @param  socketFactory      The socket factory to use to create the
208   *                            underlying connections.
209   * @param  connectionOptions  The set of connection options to use for the
210   *                            underlying connections.
211   */
212  public FastestConnectServerSet(final String[] addresses, final int[] ports,
213                                 final SocketFactory socketFactory,
214                                 final LDAPConnectionOptions connectionOptions)
215  {
216    this(addresses, ports, socketFactory, connectionOptions, null, null);
217  }
218
219
220
221  /**
222   * Creates a new fastest connect server set with the specified set of
223   * directory server addresses and port numbers.  It will use the provided
224   * socket factory to create the underlying sockets.
225   *
226   * @param  addresses             The addresses of the directory servers to
227   *                               which the connections should be established.
228   *                               It must not be {@code null} or empty.
229   * @param  ports                 The ports of the directory servers to which
230   *                               the connections should be established.  It
231   *                               must not be {@code null}, and it must have
232   *                               the same number of elements as the
233   *                               {@code addresses} array.  The order of
234   *                               elements in the {@code addresses} array must
235   *                               correspond to the order of elements in the
236   *                               {@code ports} array.
237   * @param  socketFactory         The socket factory to use to create the
238   *                               underlying connections.
239   * @param  connectionOptions     The set of connection options to use for the
240   *                               underlying connections.
241   * @param  bindRequest           The bind request that should be used to
242   *                               authenticate newly-established connections.
243   *                               It may be {@code null} if this server set
244   *                               should not perform any authentication.
245   * @param  postConnectProcessor  The post-connect processor that should be
246   *                               invoked on newly-established connections.  It
247   *                               may be {@code null} if this server set should
248   *                               not perform any post-connect processing.
249   */
250  public FastestConnectServerSet(final String[] addresses, final int[] ports,
251              final SocketFactory socketFactory,
252              final LDAPConnectionOptions connectionOptions,
253              final BindRequest bindRequest,
254              final PostConnectProcessor postConnectProcessor)
255  {
256    Validator.ensureNotNull(addresses, ports);
257    Validator.ensureTrue(addresses.length > 0,
258         "RoundRobinServerSet.addresses must not be empty.");
259    Validator.ensureTrue(addresses.length == ports.length,
260         "RoundRobinServerSet addresses and ports arrays must be the same " +
261              "size.");
262
263    this.addresses = addresses;
264    this.ports = ports;
265    this.bindRequest = bindRequest;
266    this.postConnectProcessor = postConnectProcessor;
267
268    if (socketFactory == null)
269    {
270      this.socketFactory = SocketFactory.getDefault();
271    }
272    else
273    {
274      this.socketFactory = socketFactory;
275    }
276
277    if (connectionOptions == null)
278    {
279      this.connectionOptions = new LDAPConnectionOptions();
280    }
281    else
282    {
283      this.connectionOptions = connectionOptions;
284    }
285  }
286
287
288
289  /**
290   * Retrieves the addresses of the directory servers to which the connections
291   * should be established.
292   *
293   * @return  The addresses of the directory servers to which the connections
294   *          should be established.
295   */
296  public String[] getAddresses()
297  {
298    return addresses;
299  }
300
301
302
303  /**
304   * Retrieves the ports of the directory servers to which the connections
305   * should be established.
306   *
307   * @return  The ports of the directory servers to which the connections should
308   *          be established.
309   */
310  public int[] getPorts()
311  {
312    return ports;
313  }
314
315
316
317  /**
318   * Retrieves the socket factory that will be used to establish connections.
319   *
320   * @return  The socket factory that will be used to establish connections.
321   */
322  public SocketFactory getSocketFactory()
323  {
324    return socketFactory;
325  }
326
327
328
329  /**
330   * Retrieves the set of connection options that will be used for underlying
331   * connections.
332   *
333   * @return  The set of connection options that will be used for underlying
334   *          connections.
335   */
336  public LDAPConnectionOptions getConnectionOptions()
337  {
338    return connectionOptions;
339  }
340
341
342
343  /**
344   * {@inheritDoc}
345   */
346  @Override()
347  public boolean includesAuthentication()
348  {
349    return (bindRequest != null);
350  }
351
352
353
354  /**
355   * {@inheritDoc}
356   */
357  @Override()
358  public boolean includesPostConnectProcessing()
359  {
360    return (postConnectProcessor != null);
361  }
362
363
364
365  /**
366   * {@inheritDoc}
367   */
368  @Override()
369  public LDAPConnection getConnection()
370         throws LDAPException
371  {
372    return getConnection(null);
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override()
381  public LDAPConnection getConnection(
382                             final LDAPConnectionPoolHealthCheck healthCheck)
383         throws LDAPException
384  {
385    if (! connectionOptions.allowConcurrentSocketFactoryUse())
386    {
387      throw new LDAPException(ResultCode.CONNECT_ERROR,
388           ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get());
389    }
390
391    final ArrayBlockingQueue<Object> resultQueue =
392         new ArrayBlockingQueue<>(addresses.length, false);
393    final AtomicBoolean connectionSelected = new AtomicBoolean(false);
394
395    final FastestConnectThread[] connectThreads =
396         new FastestConnectThread[addresses.length];
397    for (int i=0; i < connectThreads.length; i++)
398    {
399      connectThreads[i] = new FastestConnectThread(addresses[i], ports[i],
400           socketFactory, connectionOptions, bindRequest, postConnectProcessor,
401           healthCheck, resultQueue, connectionSelected);
402    }
403
404    for (final FastestConnectThread t : connectThreads)
405    {
406      t.start();
407    }
408
409    try
410    {
411      final long effectiveConnectTimeout;
412      final long connectTimeout =
413           connectionOptions.getConnectTimeoutMillis();
414      if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE))
415      {
416        effectiveConnectTimeout = connectTimeout;
417      }
418      else
419      {
420        effectiveConnectTimeout = Integer.MAX_VALUE;
421      }
422
423      int connectFailures = 0;
424      final long stopWaitingTime =
425           System.currentTimeMillis() + effectiveConnectTimeout;
426      while (true)
427      {
428        final Object o;
429        final long waitTime = stopWaitingTime - System.currentTimeMillis();
430        if (waitTime > 0L)
431        {
432          o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS);
433        }
434        else
435        {
436          o = resultQueue.poll();
437        }
438
439        if (o == null)
440        {
441          throw new LDAPException(ResultCode.CONNECT_ERROR,
442               ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get(
443                    effectiveConnectTimeout));
444        }
445        else if (o instanceof LDAPConnection)
446        {
447          return (LDAPConnection) o;
448        }
449        else
450        {
451          connectFailures++;
452          if (connectFailures >= addresses.length)
453          {
454            throw new LDAPException(ResultCode.CONNECT_ERROR,
455                 ERR_FASTEST_CONNECT_SET_ALL_FAILED.get());
456          }
457        }
458      }
459    }
460    catch (final LDAPException le)
461    {
462      Debug.debugException(le);
463      throw le;
464    }
465    catch (final Exception e)
466    {
467      Debug.debugException(e);
468
469      if (e instanceof InterruptedException)
470      {
471        Thread.currentThread().interrupt();
472      }
473
474      throw new LDAPException(ResultCode.CONNECT_ERROR,
475           ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get(
476                StaticUtils.getExceptionMessage(e)),
477           e);
478    }
479  }
480
481
482
483  /**
484   * {@inheritDoc}
485   */
486  @Override()
487  public void toString(final StringBuilder buffer)
488  {
489    buffer.append("FastestConnectServerSet(servers={");
490
491    for (int i=0; i < addresses.length; i++)
492    {
493      if (i > 0)
494      {
495        buffer.append(", ");
496      }
497
498      buffer.append(addresses[i]);
499      buffer.append(':');
500      buffer.append(ports[i]);
501    }
502
503    buffer.append("}, includesAuthentication=");
504    buffer.append(bindRequest != null);
505    buffer.append(", includesPostConnectProcessing=");
506    buffer.append(postConnectProcessor != null);
507    buffer.append(')');
508  }
509}