001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.io.Serializable;
026    
027    import com.unboundid.util.NotMutable;
028    import com.unboundid.util.ThreadSafety;
029    import com.unboundid.util.ThreadSafetyLevel;
030    
031    import static com.unboundid.ldap.sdk.LDAPMessages.*;
032    import static com.unboundid.util.Debug.*;
033    import static com.unboundid.util.StaticUtils.*;
034    
035    
036    
037    /**
038     * This class provides an LDAP connection pool health check implementation that
039     * may be used to check the health of the associated server by verifying that a
040     * specified entry can be retrieved in an acceptable period of time.  If the
041     * entry cannot be retrieved (either because it does not exist, or because an
042     * error occurs while attempting to retrieve it), or if it takes too long to
043     * retrieve the entry, then the associated connection will be classified as
044     * unavailable.
045     * <BR><BR>
046     * It is possible to control under which conditions an attempt should be made to
047     * retrieve the target entry, and also to specify a maximum acceptable response
048     * time.  For best results, the target entry should be available to be retrieved
049     * by a client with any authentication state.
050     */
051    @NotMutable()
052    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
053    public final class GetEntryLDAPConnectionPoolHealthCheck
054           extends LDAPConnectionPoolHealthCheck
055           implements Serializable
056    {
057      /**
058       * The default maximum response time value in milliseconds, which is set to
059       * 30,000 milliseconds or 30 seconds.
060       */
061      private static final long DEFAULT_MAX_RESPONSE_TIME = 30000L;
062    
063    
064    
065      /**
066       * The serial version UID for this serializable class.
067       */
068      private static final long serialVersionUID = -3400259782503254645L;
069    
070    
071    
072      // Indicates whether to invoke the test during background health checks.
073      private final boolean invokeForBackgroundChecks;
074    
075      // Indicates whether to invoke the test when checking out a connection.
076      private final boolean invokeOnCheckout;
077    
078      // Indicates whether to invoke the test when creating a new connection.
079      private final boolean invokeOnCreate;
080    
081      // Indicates whether to invoke the test whenever an exception is encountered
082      // when using the connection.
083      private final boolean invokeOnException;
084    
085      // Indicates whether to invoke the test when releasing a connection.
086      private final boolean invokeOnRelease;
087    
088      // The maximum response time value in milliseconds.
089      private final long maxResponseTime;
090    
091      // The search request to send to the server.
092      private final SearchRequest searchRequest;
093    
094      // The DN of the entry to retrieve.
095      private final String entryDN;
096    
097    
098    
099      /**
100       * Creates a new instance of this get entry LDAP connection pool health check.
101       *
102       * @param  entryDN                    The DN of the entry to retrieve from
103       *                                    the target server.  If this is
104       *                                    {@code null}, then the server's root DSE
105       *                                    will be used.
106       * @param  maxResponseTime            The maximum length of time in
107       *                                    milliseconds that should be allowed when
108       *                                    attempting to retrieve the entry.  If
109       *                                    the provided value is less than or equal
110       *                                    to zero, then the default value of 30000
111       *                                    milliseconds (30 seconds) will be used.
112       * @param  invokeOnCreate             Indicates whether to test for the
113       *                                    existence of the target entry whenever a
114       *                                    new connection is created for use in the
115       *                                    pool.
116       * @param  invokeOnCheckout           Indicates whether to test for the
117       *                                    existence of the target entry
118       *                                    immediately before a connection is
119       *                                    checked out of the pool.
120       * @param  invokeOnRelease            Indicates whether to test for the
121       *                                    existence of the target entry
122       *                                    immediately after a connection has been
123       *                                    released back to the pool.
124       * @param  invokeForBackgroundChecks  Indicates whether to test for the
125       *                                    existence of the target entry during
126       *                                    periodic background health checks.
127       * @param  invokeOnException          Indicates whether to test for the
128       *                                    existence of the target entry if an
129       *                                    exception is encountered when using the
130       *                                    connection.
131       */
132      public GetEntryLDAPConnectionPoolHealthCheck(final String entryDN,
133                  final long maxResponseTime, final boolean invokeOnCreate,
134                  final boolean invokeOnCheckout, final boolean invokeOnRelease,
135                  final boolean invokeForBackgroundChecks,
136                  final boolean invokeOnException)
137      {
138        this.invokeOnCreate            = invokeOnCreate;
139        this.invokeOnCheckout          = invokeOnCheckout;
140        this.invokeOnRelease           = invokeOnRelease;
141        this.invokeForBackgroundChecks = invokeForBackgroundChecks;
142        this.invokeOnException         = invokeOnException;
143    
144        if (entryDN == null)
145        {
146          this.entryDN = "";
147        }
148        else
149        {
150          this.entryDN = entryDN;
151        }
152    
153        if (maxResponseTime > 0L)
154        {
155          this.maxResponseTime = maxResponseTime;
156        }
157        else
158        {
159          this.maxResponseTime = DEFAULT_MAX_RESPONSE_TIME;
160        }
161    
162        searchRequest = new SearchRequest(this.entryDN, SearchScope.BASE,
163             Filter.createPresenceFilter("objectClass"), "1.1");
164        searchRequest.setResponseTimeoutMillis(this.maxResponseTime);
165      }
166    
167    
168    
169      /**
170       * {@inheritDoc}
171       */
172      @Override()
173      public void ensureNewConnectionValid(final LDAPConnection connection)
174             throws LDAPException
175      {
176        if (invokeOnCreate)
177        {
178          getEntry(connection);
179        }
180      }
181    
182    
183    
184      /**
185       * {@inheritDoc}
186       */
187      @Override()
188      public void ensureConnectionValidForCheckout(final LDAPConnection connection)
189             throws LDAPException
190      {
191        if (invokeOnCheckout)
192        {
193          getEntry(connection);
194        }
195      }
196    
197    
198    
199      /**
200       * {@inheritDoc}
201       */
202      @Override()
203      public void ensureConnectionValidForRelease(final LDAPConnection connection)
204             throws LDAPException
205      {
206        if (invokeOnRelease)
207        {
208          getEntry(connection);
209        }
210      }
211    
212    
213    
214      /**
215       * {@inheritDoc}
216       */
217      @Override()
218      public void ensureConnectionValidForContinuedUse(
219                       final LDAPConnection connection)
220             throws LDAPException
221      {
222        if (invokeForBackgroundChecks)
223        {
224          getEntry(connection);
225        }
226      }
227    
228    
229    
230      /**
231       * {@inheritDoc}
232       */
233      @Override()
234      public void ensureConnectionValidAfterException(
235                       final LDAPConnection connection,
236                       final LDAPException exception)
237             throws LDAPException
238      {
239        super.ensureConnectionValidAfterException(connection, exception);
240    
241        if (invokeOnException)
242        {
243          getEntry(connection);
244        }
245      }
246    
247    
248    
249      /**
250       * Retrieves the DN of the entry that will be retrieved when performing the
251       * health checks.
252       *
253       * @return  The DN of the entry that will be retrieved when performing the
254       *          health checks.
255       */
256      public String getEntryDN()
257      {
258        return entryDN;
259      }
260    
261    
262    
263      /**
264       * Retrieves the maximum length of time in milliseconds that this health
265       * check should wait for the entry to be returned.
266       *
267       * @return  The maximum length of time in milliseconds that this health check
268       *          should wait for the entry to be returned.
269       */
270      public long getMaxResponseTimeMillis()
271      {
272        return maxResponseTime;
273      }
274    
275    
276    
277      /**
278       * Indicates whether this health check will test for the existence of the
279       * target entry whenever a new connection is created.
280       *
281       * @return  {@code true} if this health check will test for the existence of
282       *          the target entry whenever a new connection is created, or
283       *          {@code false} if not.
284       */
285      public boolean invokeOnCreate()
286      {
287        return invokeOnCreate;
288      }
289    
290    
291    
292      /**
293       * Indicates whether this health check will test for the existence of the
294       * target entry whenever a connection is to be checked out for use.
295       *
296       * @return  {@code true} if this health check will test for the existence of
297       *          the target entry whenever a connection is to be checked out, or
298       *          {@code false} if not.
299       */
300      public boolean invokeOnCheckout()
301      {
302        return invokeOnCheckout;
303      }
304    
305    
306    
307      /**
308       * Indicates whether this health check will test for the existence of the
309       * target entry whenever a connection is to be released back to the pool.
310       *
311       * @return  {@code true} if this health check will test for the existence of
312       *          the target entry whenever a connection is to be released, or
313       *          {@code false} if not.
314       */
315      public boolean invokeOnRelease()
316      {
317        return invokeOnRelease;
318      }
319    
320    
321    
322      /**
323       * Indicates whether this health check will test for the existence of the
324       * target entry during periodic background health checks.
325       *
326       * @return  {@code true} if this health check will test for the existence of
327       *          the target entry during periodic background health checks, or
328       *          {@code false} if not.
329       */
330      public boolean invokeForBackgroundChecks()
331      {
332        return invokeForBackgroundChecks;
333      }
334    
335    
336    
337      /**
338       * Indicates whether this health check will test for the existence of the
339       * target entry if an exception is caught while processing an operation on a
340       * connection.
341       *
342       * @return  {@code true} if this health check will test for the existence of
343       *          the target entry whenever an exception is caught, or {@code false}
344       *          if not.
345       */
346      public boolean invokeOnException()
347      {
348        return invokeOnException;
349      }
350    
351    
352    
353      /**
354       * Attempts to retrieve the target entry.  If the attempt fails, or if the
355       * connection takes too long then an exception will be thrown.
356       *
357       * @param  conn  The connection to be checked.
358       *
359       * @throws  LDAPException  If a problem occurs while trying to retrieve the
360       *                         entry, or if it cannot be retrieved in an
361       *                         acceptable length of time.
362       */
363      private void getEntry(final LDAPConnection conn)
364              throws LDAPException
365      {
366        try
367        {
368          final SearchResult result = conn.search(searchRequest.duplicate());
369          if (result.getEntryCount() != 1)
370          {
371            throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
372                 ERR_GET_ENTRY_HEALTH_CHECK_NO_ENTRY_RETURNED.get());
373          }
374        }
375        catch (Exception e)
376        {
377          debugException(e);
378    
379          final String msg = ERR_GET_ENTRY_HEALTH_CHECK_FAILURE.get(entryDN,
380               getExceptionMessage(e));
381    
382          conn.setDisconnectInfo(DisconnectType.POOLED_CONNECTION_DEFUNCT, msg, e);
383          throw new LDAPException(ResultCode.SERVER_DOWN, msg, e);
384        }
385      }
386    
387    
388    
389      /**
390       * {@inheritDoc}
391       */
392      @Override()
393      public void toString(final StringBuilder buffer)
394      {
395        buffer.append("GetEntryLDAPConnectionPoolHealthCheck(entryDN='");
396        buffer.append(entryDN);
397        buffer.append("', maxResponseTimeMillis=");
398        buffer.append(maxResponseTime);
399        buffer.append(", invokeOnCreate=");
400        buffer.append(invokeOnCreate);
401        buffer.append(", invokeOnCheckout=");
402        buffer.append(invokeOnCheckout);
403        buffer.append(", invokeOnRelease=");
404        buffer.append(invokeOnRelease);
405        buffer.append(", invokeForBackgroundChecks=");
406        buffer.append(invokeForBackgroundChecks);
407        buffer.append(", invokeOnException=");
408        buffer.append(invokeOnException);
409        buffer.append(')');
410      }
411    }