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