001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-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 com.unboundid.util.Debug;
026import com.unboundid.util.Extensible;
027import com.unboundid.util.ThreadSafety;
028import com.unboundid.util.ThreadSafetyLevel;
029
030
031
032/**
033 * This class defines an API that can be used to select between multiple
034 * directory servers when establishing a connection.  Implementations are free
035 * to use any kind of logic that they desire when selecting the server to which
036 * the connection is to be established.  They may also support the use of
037 * health checks to determine whether the created connections are suitable for
038 * use.
039 * <BR><BR>
040 * Implementations MUST be threadsafe to allow for multiple concurrent attempts
041 * to establish new connections.
042 */
043@Extensible()
044@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
045public abstract class ServerSet
046{
047  /**
048   * Creates a new instance of this server set.
049   */
050  protected ServerSet()
051  {
052    // No implementation is required.
053  }
054
055
056
057  /**
058   * Indicates whether connections created by this server set will be
059   * authenticated.
060   *
061   * @return  {@code true} if connections created by this server set will be
062   *          authenticated, or {@code false} if not.
063   */
064  public boolean includesAuthentication()
065  {
066    return false;
067  }
068
069
070
071  /**
072   * Indicates whether connections created by this server set will have
073   * post-connect processing performed.
074   *
075   * @return  {@code true} if connections created by this server set will have
076   *          post-connect processing performed, or {@code false} if not.
077   */
078  public boolean includesPostConnectProcessing()
079  {
080    return false;
081  }
082
083
084
085  /**
086   * Attempts to establish a connection to one of the directory servers in this
087   * server set.  The connection that is returned must be established.  The
088   * {@link #includesAuthentication()} must return true if and only if the
089   * connection will also be authenticated, and the
090   * {@link #includesPostConnectProcessing()} method must return true if and
091   * only if pre-authentication and post-authentication post-connect processing
092   * will have been performed.  The caller may determine the server to which the
093   * connection is established using the
094   * {@link LDAPConnection#getConnectedAddress} and
095   * {@link LDAPConnection#getConnectedPort} methods.
096   *
097   * @return  An {@code LDAPConnection} object that is established to one of the
098   *          servers in this server set.
099   *
100   * @throws  LDAPException  If it is not possible to establish a connection to
101   *                         any of the servers in this server set.
102   */
103  public abstract LDAPConnection getConnection()
104         throws LDAPException;
105
106
107
108  /**
109   * Attempts to establish a connection to one of the directory servers in this
110   * server set, using the provided health check to further validate the
111   * connection.  The connection that is returned must be established.  The
112   * {@link #includesAuthentication()} must return true if and only if the
113   * connection will also be authenticated, and the
114   * {@link #includesPostConnectProcessing()} method must return true if and
115   * only if pre-authentication and post-authentication post-connect processing
116   * will have been performed.  The caller may determine the server to which the
117   * connection is established using the
118   * {@link LDAPConnection#getConnectedAddress} and
119   * {@link LDAPConnection#getConnectedPort} methods.
120   *
121   * @param  healthCheck  The health check to use to verify the health of the
122   *                      newly-created connection.  It may be {@code null} if
123   *                      no additional health check should be performed.  If it
124   *                      is non-{@code null} and this server set performs
125   *                      authentication, then the health check's
126   *                      {@code ensureConnectionValidAfterAuthentication}
127   *                      method will be invoked immediately after the bind
128   *                      operation is processed (regardless of whether the bind
129   *                      was successful or not).  And regardless of whether
130   *                      this server set performs authentication, the
131   *                      health check's {@code ensureNewConnectionValid}
132   *                      method must be invoked on the connection to ensure
133   *                      that it is valid immediately before it is returned.
134   *
135   * @return  An {@code LDAPConnection} object that is established to one of the
136   *          servers in this server set.
137   *
138   * @throws  LDAPException  If it is not possible to establish a connection to
139   *                         any of the servers in this server set.
140   */
141  public LDAPConnection getConnection(
142                             final LDAPConnectionPoolHealthCheck healthCheck)
143         throws LDAPException
144  {
145    final LDAPConnection c = getConnection();
146
147    if (healthCheck != null)
148    {
149      try
150      {
151        healthCheck.ensureNewConnectionValid(c);
152      }
153      catch (final LDAPException le)
154      {
155        Debug.debugException(le);
156        c.close();
157        throw le;
158      }
159    }
160
161    return c;
162  }
163
164
165
166  /**
167   * Performs the appropriate bind, post-connect, and health check processing
168   * for  the provided connection, in the provided order.  The processing
169   * performed will include:
170   * <OL>
171   *   <LI>
172   *     If the provided {@code postConnectProcessor} is not {@code null}, then
173   *     invoke its {@code processPreAuthenticatedConnection} method on the
174   *     provided connection.  If this method throws an {@code LDAPException},
175   *     then it will propagated up to the caller of this method.
176   *   </LI>
177   *   <LI>
178   *     If the provided {@code bindRequest} is not {@code null}, then
179   *     authenticate the connection using that request.  If the provided
180   *     {@code healthCheck} is also not {@code null}, then invoke its
181   *     {@code ensureConnectionValidAfterAuthentication} method on the
182   *     connection, even if the bind was not successful.  If the health check
183   *     throws an {@code LDAPException}, then it will be propagated up to the
184   *     caller of this method.  If there is no health check or if it did not
185   *     throw an exception but the bind attempt did throw an exception, then
186   *     propagate that exception instead.
187   *   </LI>
188   *   <LI>
189   *     If the provided {@code postConnectProcessor} is not {@code null}, then
190   *     invoke its {@code processPostAuthenticatedConnection} method on the
191   *     provided connection.  If this method throws an {@code LDAPException},
192   *     then it will propagated up to the caller of this method.
193   *   </LI>
194   *   <LI>
195   *     If the provided {@code healthCheck} is not {@code null}, then invoke
196   *     its {@code ensureNewConnectionValid} method on the provided connection.
197   *     If this method throws an {@code LDAPException}, then it will be
198   *     propagated up to the caller of this method.
199   *   </LI>
200   * </OL>
201   *
202   * @param  connection            The connection to be processed.  It must not
203   *                               be {@code null}, and it must be established.
204   *                               Note that if an {@code LDAPException} is
205   *                               thrown by this method or anything that it
206   *                               calls, then the connection will have been
207   *                               closed before that exception has been
208   *                               propagated up to the caller of this method.
209   * @param  bindRequest           The bind request to use to authenticate the
210   *                               connection.  It may be {@code null} if no
211   *                               authentication should be performed.
212   * @param  postConnectProcessor  The post-connect processor to invoke on the
213   *                               provided connection.  It may be {@code null}
214   *                               if no post-connect processing should be
215   *                               performed.
216   * @param  healthCheck           The health check to use to verify the health
217   *                               of connection.  It may be {@code null} if no
218   *                               health check processing should be performed.
219   *
220   * @throws  LDAPException  If a problem is encountered during any of the
221   *                         processing performed by this method.  If an
222   *                         exception is thrown, then the provided connection
223   *                         will have been closed.
224   */
225  protected static void doBindPostConnectAndHealthCheckProcessing(
226                             final LDAPConnection connection,
227                             final BindRequest bindRequest,
228                             final PostConnectProcessor postConnectProcessor,
229                             final LDAPConnectionPoolHealthCheck healthCheck)
230            throws LDAPException
231  {
232    try
233    {
234      if (postConnectProcessor != null)
235      {
236        postConnectProcessor.processPreAuthenticatedConnection(connection);
237      }
238
239      if (bindRequest != null)
240      {
241        BindResult bindResult;
242        LDAPException bindException = null;
243        try
244        {
245          bindResult = connection.bind(bindRequest.duplicate());
246        }
247        catch (final LDAPException le)
248        {
249          Debug.debugException(le);
250          bindException = le;
251          bindResult = new BindResult(le);
252        }
253
254        if (healthCheck != null)
255        {
256          healthCheck.ensureConnectionValidAfterAuthentication(connection,
257               bindResult);
258        }
259
260        if (bindException != null)
261        {
262          throw bindException;
263        }
264      }
265
266      if (postConnectProcessor != null)
267      {
268        postConnectProcessor.processPostAuthenticatedConnection(connection);
269      }
270
271      if (healthCheck != null)
272      {
273        healthCheck.ensureNewConnectionValid(connection);
274      }
275    }
276    catch (final LDAPException le)
277    {
278      Debug.debugException(le);
279      connection.closeWithoutUnbind();
280      throw le;
281    }
282  }
283
284
285
286  /**
287   * Updates the provided connection to indicate that it was created by this
288   * server set.
289   *
290   * @param  connection  The connection to be updated to indicate it was created
291   *                     by this server set.
292   */
293  protected final void associateConnectionWithThisServerSet(
294                            final LDAPConnection connection)
295  {
296    if (connection != null)
297    {
298      connection.setServerSet(this);
299    }
300  }
301
302
303
304  /**
305   * Performs any processing that may be required when the provided connection
306   * is closed.  This will only be invoked for connections created by this
307   * server set, and only if the {@link #associateConnectionWithThisServerSet}
308   * method was called on the connection when it was created by this server set.
309   *
310   * @param  connection      The connection that has been closed.
311   * @param  host            The address of the server to which the connection
312   *                         had been established.
313   * @param  port            The port of the server to which the connection had
314   *                         been established.
315   * @param  disconnectType  The disconnect type, which provides general
316   *                         information about the nature of the disconnect.
317   * @param  message         A message that may be associated with the
318   *                         disconnect.  It may be {@code null} if no message
319   *                         is available.
320   * @param  cause           A {@code Throwable} that was caught and triggered
321   *                         the disconnect.  It may be {@code null} if the
322   *                         disconnect was not triggered by a client-side
323   *                         exception or error.
324   */
325  protected void handleConnectionClosed(final LDAPConnection connection,
326                                        final String host, final int port,
327                                        final DisconnectType disconnectType,
328                                        final String message,
329                                        final Throwable cause)
330  {
331    // No action is taken by default.
332  }
333
334
335
336  /**
337   * Retrieves a string representation of this server set.
338   *
339   * @return  A string representation of this server set.
340   */
341  @Override()
342  public String toString()
343  {
344    final StringBuilder buffer = new StringBuilder();
345    toString(buffer);
346    return buffer.toString();
347  }
348
349
350
351  /**
352   * Appends a string representation of this server set to the provided buffer.
353   *
354   * @param  buffer  The buffer to which the string representation should be
355   *                 appended.
356   */
357  public void toString(final StringBuilder buffer)
358  {
359    buffer.append("ServerSet(className=");
360    buffer.append(getClass().getName());
361    buffer.append(')');
362  }
363}