001/*
002 * Copyright 2012-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2012-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.concurrent.ArrayBlockingQueue;
041import java.util.concurrent.TimeUnit;
042import java.util.concurrent.atomic.AtomicBoolean;
043import javax.net.SocketFactory;
044
045import com.unboundid.util.Debug;
046import com.unboundid.util.NotMutable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052import static com.unboundid.ldap.sdk.LDAPMessages.*;
053
054
055
056/**
057 * This class provides a server set implementation that will attempt to
058 * establish connections to all associated servers in parallel, keeping the one
059 * that was first to be successfully established and closing all others.
060 * <BR><BR>
061 * Note that this server set implementation may only be used in conjunction with
062 * connection options that allow the associated socket factory to create
063 * multiple connections in parallel.  If the
064 * {@link LDAPConnectionOptions#allowConcurrentSocketFactoryUse} method returns
065 * false for the associated connection options, then the {@code getConnection}
066 * methods will throw an exception.
067 * <BR><BR>
068 * <H2>Example</H2>
069 * The following example demonstrates the process for creating a fastest connect
070 * server set that may be used to establish connections to either of two
071 * servers.  When using the server set to attempt to create a connection, it
072 * will try both in parallel and will return the first connection that it is
073 * able to establish:
074 * <PRE>
075 * // Create arrays with the addresses and ports of the directory server
076 * // instances.
077 * String[] addresses =
078 * {
079 *   server1Address,
080 *   server2Address
081 * };
082 * int[] ports =
083 * {
084 *   server1Port,
085 *   server2Port
086 * };
087 *
088 * // Create the server set using the address and port arrays.
089 * FastestConnectServerSet fastestConnectSet =
090 *      new FastestConnectServerSet(addresses, ports);
091 *
092 * // Verify that we can establish a single connection using the server set.
093 * LDAPConnection connection = fastestConnectSet.getConnection();
094 * RootDSE rootDSEFromConnection = connection.getRootDSE();
095 * connection.close();
096 *
097 * // Verify that we can establish a connection pool using the server set.
098 * SimpleBindRequest bindRequest =
099 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
100 * LDAPConnectionPool pool =
101 *      new LDAPConnectionPool(fastestConnectSet, bindRequest, 10);
102 * RootDSE rootDSEFromPool = pool.getRootDSE();
103 * pool.close();
104 * </PRE>
105 */
106@NotMutable()
107@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
108public final class FastestConnectServerSet
109       extends ServerSet
110{
111  // The bind request to use to authenticate connections created by this
112  // server set.
113  private final BindRequest bindRequest;
114
115  // The port numbers of the target servers.
116  private final int[] ports;
117
118  // The set of connection options to use for new connections.
119  private final LDAPConnectionOptions connectionOptions;
120
121  // The post-connect processor to invoke against connections created by this
122  // server set.
123  private final PostConnectProcessor postConnectProcessor;
124
125  // The socket factory to use to establish connections.
126  private final SocketFactory socketFactory;
127
128  // The addresses of the target servers.
129  private final String[] addresses;
130
131
132
133  /**
134   * Creates a new fastest connect server set with the specified set of
135   * directory server addresses and port numbers.  It will use the default
136   * socket factory provided by the JVM to create the underlying sockets.
137   *
138   * @param  addresses  The addresses of the directory servers to which the
139   *                    connections should be established.  It must not be
140   *                    {@code null} or empty.
141   * @param  ports      The ports of the directory servers to which the
142   *                    connections should be established.  It must not be
143   *                    {@code null}, and it must have the same number of
144   *                    elements as the {@code addresses} array.  The order of
145   *                    elements in the {@code addresses} array must correspond
146   *                    to the order of elements in the {@code ports} array.
147   */
148  public FastestConnectServerSet(final String[] addresses, final int[] ports)
149  {
150    this(addresses, ports, null, null);
151  }
152
153
154
155  /**
156   * Creates a new fastest connect server set with the specified set of
157   * directory server addresses and port numbers.  It will use the default
158   * socket factory provided by the JVM to create the underlying sockets.
159   *
160   * @param  addresses          The addresses of the directory servers to which
161   *                            the connections should be established.  It must
162   *                            not be {@code null} or empty.
163   * @param  ports              The ports of the directory servers to which the
164   *                            connections should be established.  It must not
165   *                            be {@code null}, and it must have the same
166   *                            number of elements as the {@code addresses}
167   *                            array.  The order of elements in the
168   *                            {@code addresses} array must correspond to the
169   *                            order of elements in the {@code ports} array.
170   * @param  connectionOptions  The set of connection options to use for the
171   *                            underlying connections.
172   */
173  public FastestConnectServerSet(final String[] addresses, final int[] ports,
174                                 final LDAPConnectionOptions connectionOptions)
175  {
176    this(addresses, ports, null, connectionOptions);
177  }
178
179
180
181  /**
182   * Creates a new fastest connect server set with the specified set of
183   * directory server addresses and port numbers.  It will use the provided
184   * socket factory to create the underlying sockets.
185   *
186   * @param  addresses      The addresses of the directory servers to which the
187   *                        connections should be established.  It must not be
188   *                        {@code null} or empty.
189   * @param  ports          The ports of the directory servers to which the
190   *                        connections should be established.  It must not be
191   *                        {@code null}, and it must have the same number of
192   *                        elements as the {@code addresses} array.  The order
193   *                        of elements in the {@code addresses} array must
194   *                        correspond to the order of elements in the
195   *                        {@code ports} array.
196   * @param  socketFactory  The socket factory to use to create the underlying
197   *                        connections.
198   */
199  public FastestConnectServerSet(final String[] addresses, final int[] ports,
200                                 final SocketFactory socketFactory)
201  {
202    this(addresses, ports, socketFactory, null);
203  }
204
205
206
207  /**
208   * Creates a new fastest connect server set with the specified set of
209   * directory server addresses and port numbers.  It will use the provided
210   * socket factory to create the underlying sockets.
211   *
212   * @param  addresses          The addresses of the directory servers to which
213   *                            the connections should be established.  It must
214   *                            not be {@code null} or empty.
215   * @param  ports              The ports of the directory servers to which the
216   *                            connections should be established.  It must not
217   *                            be {@code null}, and it must have the same
218   *                            number of elements as the {@code addresses}
219   *                            array.  The order of elements in the
220   *                            {@code addresses} array must correspond to the
221   *                            order of elements in the {@code ports} array.
222   * @param  socketFactory      The socket factory to use to create the
223   *                            underlying connections.
224   * @param  connectionOptions  The set of connection options to use for the
225   *                            underlying connections.
226   */
227  public FastestConnectServerSet(final String[] addresses, final int[] ports,
228                                 final SocketFactory socketFactory,
229                                 final LDAPConnectionOptions connectionOptions)
230  {
231    this(addresses, ports, socketFactory, connectionOptions, null, null);
232  }
233
234
235
236  /**
237   * Creates a new fastest connect server set with the specified set of
238   * directory server addresses and port numbers.  It will use the provided
239   * socket factory to create the underlying sockets.
240   *
241   * @param  addresses             The addresses of the directory servers to
242   *                               which the connections should be established.
243   *                               It must not be {@code null} or empty.
244   * @param  ports                 The ports of the directory servers to which
245   *                               the connections should be established.  It
246   *                               must not be {@code null}, and it must have
247   *                               the same number of elements as the
248   *                               {@code addresses} array.  The order of
249   *                               elements in the {@code addresses} array must
250   *                               correspond to the order of elements in the
251   *                               {@code ports} array.
252   * @param  socketFactory         The socket factory to use to create the
253   *                               underlying connections.
254   * @param  connectionOptions     The set of connection options to use for the
255   *                               underlying connections.
256   * @param  bindRequest           The bind request that should be used to
257   *                               authenticate newly-established connections.
258   *                               It may be {@code null} if this server set
259   *                               should not perform any authentication.
260   * @param  postConnectProcessor  The post-connect processor that should be
261   *                               invoked on newly-established connections.  It
262   *                               may be {@code null} if this server set should
263   *                               not perform any post-connect processing.
264   */
265  public FastestConnectServerSet(final String[] addresses, final int[] ports,
266              final SocketFactory socketFactory,
267              final LDAPConnectionOptions connectionOptions,
268              final BindRequest bindRequest,
269              final PostConnectProcessor postConnectProcessor)
270  {
271    Validator.ensureNotNull(addresses, ports);
272    Validator.ensureTrue(addresses.length > 0,
273         "RoundRobinServerSet.addresses must not be empty.");
274    Validator.ensureTrue(addresses.length == ports.length,
275         "RoundRobinServerSet addresses and ports arrays must be the same " +
276              "size.");
277
278    this.addresses = addresses;
279    this.ports = ports;
280    this.bindRequest = bindRequest;
281    this.postConnectProcessor = postConnectProcessor;
282
283    if (socketFactory == null)
284    {
285      this.socketFactory = SocketFactory.getDefault();
286    }
287    else
288    {
289      this.socketFactory = socketFactory;
290    }
291
292    if (connectionOptions == null)
293    {
294      this.connectionOptions = new LDAPConnectionOptions();
295    }
296    else
297    {
298      this.connectionOptions = connectionOptions;
299    }
300  }
301
302
303
304  /**
305   * Retrieves the addresses of the directory servers to which the connections
306   * should be established.
307   *
308   * @return  The addresses of the directory servers to which the connections
309   *          should be established.
310   */
311  public String[] getAddresses()
312  {
313    return addresses;
314  }
315
316
317
318  /**
319   * Retrieves the ports of the directory servers to which the connections
320   * should be established.
321   *
322   * @return  The ports of the directory servers to which the connections should
323   *          be established.
324   */
325  public int[] getPorts()
326  {
327    return ports;
328  }
329
330
331
332  /**
333   * Retrieves the socket factory that will be used to establish connections.
334   *
335   * @return  The socket factory that will be used to establish connections.
336   */
337  public SocketFactory getSocketFactory()
338  {
339    return socketFactory;
340  }
341
342
343
344  /**
345   * Retrieves the set of connection options that will be used for underlying
346   * connections.
347   *
348   * @return  The set of connection options that will be used for underlying
349   *          connections.
350   */
351  public LDAPConnectionOptions getConnectionOptions()
352  {
353    return connectionOptions;
354  }
355
356
357
358  /**
359   * {@inheritDoc}
360   */
361  @Override()
362  public boolean includesAuthentication()
363  {
364    return (bindRequest != null);
365  }
366
367
368
369  /**
370   * {@inheritDoc}
371   */
372  @Override()
373  public boolean includesPostConnectProcessing()
374  {
375    return (postConnectProcessor != null);
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public LDAPConnection getConnection()
385         throws LDAPException
386  {
387    return getConnection(null);
388  }
389
390
391
392  /**
393   * {@inheritDoc}
394   */
395  @Override()
396  public LDAPConnection getConnection(
397                             final LDAPConnectionPoolHealthCheck healthCheck)
398         throws LDAPException
399  {
400    if (! connectionOptions.allowConcurrentSocketFactoryUse())
401    {
402      throw new LDAPException(ResultCode.CONNECT_ERROR,
403           ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get());
404    }
405
406    final ArrayBlockingQueue<Object> resultQueue =
407         new ArrayBlockingQueue<>(addresses.length, false);
408    final AtomicBoolean connectionSelected = new AtomicBoolean(false);
409
410    final FastestConnectThread[] connectThreads =
411         new FastestConnectThread[addresses.length];
412    for (int i=0; i < connectThreads.length; i++)
413    {
414      connectThreads[i] = new FastestConnectThread(addresses[i], ports[i],
415           socketFactory, connectionOptions, bindRequest, postConnectProcessor,
416           healthCheck, resultQueue, connectionSelected);
417    }
418
419    for (final FastestConnectThread t : connectThreads)
420    {
421      t.start();
422    }
423
424    try
425    {
426      final long effectiveConnectTimeout;
427      final long connectTimeout =
428           connectionOptions.getConnectTimeoutMillis();
429      if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE))
430      {
431        effectiveConnectTimeout = connectTimeout;
432      }
433      else
434      {
435        effectiveConnectTimeout = Integer.MAX_VALUE;
436      }
437
438      int connectFailures = 0;
439      final long stopWaitingTime =
440           System.currentTimeMillis() + effectiveConnectTimeout;
441      while (true)
442      {
443        final Object o;
444        final long waitTime = stopWaitingTime - System.currentTimeMillis();
445        if (waitTime > 0L)
446        {
447          o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS);
448        }
449        else
450        {
451          o = resultQueue.poll();
452        }
453
454        if (o == null)
455        {
456          throw new LDAPException(ResultCode.CONNECT_ERROR,
457               ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get(
458                    effectiveConnectTimeout));
459        }
460        else if (o instanceof LDAPConnection)
461        {
462          final LDAPConnection conn = (LDAPConnection) o;
463          associateConnectionWithThisServerSet(conn);
464          return conn;
465        }
466        else
467        {
468          connectFailures++;
469          if (connectFailures >= addresses.length)
470          {
471            throw new LDAPException(ResultCode.CONNECT_ERROR,
472                 ERR_FASTEST_CONNECT_SET_ALL_FAILED.get());
473          }
474        }
475      }
476    }
477    catch (final LDAPException le)
478    {
479      Debug.debugException(le);
480      throw le;
481    }
482    catch (final Exception e)
483    {
484      Debug.debugException(e);
485
486      if (e instanceof InterruptedException)
487      {
488        Thread.currentThread().interrupt();
489      }
490
491      throw new LDAPException(ResultCode.CONNECT_ERROR,
492           ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get(
493                StaticUtils.getExceptionMessage(e)),
494           e);
495    }
496  }
497
498
499
500  /**
501   * {@inheritDoc}
502   */
503  @Override()
504  public void toString(final StringBuilder buffer)
505  {
506    buffer.append("FastestConnectServerSet(servers={");
507
508    for (int i=0; i < addresses.length; i++)
509    {
510      if (i > 0)
511      {
512        buffer.append(", ");
513      }
514
515      buffer.append(addresses[i]);
516      buffer.append(':');
517      buffer.append(ports[i]);
518    }
519
520    buffer.append("}, includesAuthentication=");
521    buffer.append(bindRequest != null);
522    buffer.append(", includesPostConnectProcessing=");
523    buffer.append(postConnectProcessor != null);
524    buffer.append(')');
525  }
526}