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