root/trunk/src/java/org/jcoderz/commons/connector/http/HttpConnectionHelper.java

Revision 1011, 13.5 kB (checked in by amandel, 4 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.connector.http;
34
35import java.util.ArrayList;
36import java.util.Iterator;
37import java.util.List;
38import java.util.logging.Level;
39import java.util.logging.Logger;
40
41import javax.resource.ResourceException;
42
43import org.jcoderz.commons.InternalErrorException;
44import org.jcoderz.commons.connector.ConnectionTimeoutErrorException;
45import org.jcoderz.commons.connector.ConnectorConfiguration;
46import org.jcoderz.commons.connector.ConnectorException;
47import org.jcoderz.commons.connector.CreatingConnectorFailedException;
48import org.jcoderz.commons.connector.http.transport.ConnectorContext;
49import org.jcoderz.commons.connector.http.transport.HttpConnectorEventListener;
50import org.jcoderz.commons.connector.http.transport.HttpRequestResponseHeader;
51import org.jcoderz.commons.util.Assert;
52
53
54/**
55 * Implementation of the HttpConnection interface.
56 * This class will be used by the client as connection handle
57 * (supports retries).
58 *
59 */
60public final class HttpConnectionHelper
61      implements HttpConnection
62{
63   /** Class name used for logging. */
64   private static final String CLASSNAME
65         = HttpConnectionHelper.class.getName();
66   /** Logger in use. */
67   private static final Logger logger
68         = Logger.getLogger(CLASSNAME);
69   /** ConnectionFactory to create HttpConnectionImpl instance that
70       is associated with a managed connecion. */
71   private final HttpConnectionFactoryImpl mConnectionFactoryImpl;
72   /** Connection Spec for the requested connection. */
73   private final HttpConnectionSpec mConnectionSpec;
74   /** Connection handle to the managed connection. */
75   private HttpConnectionExtended mConnection;
76   /** Delay for retries in milli seconds. */
77   private int mRequiredDelay;
78   /** Flag indicating that a delay for retries is necessary. */
79   private boolean mIsRetryRequired = false;
80   private final ConnectorConfiguration mConfig;
81   /** Number of retries to perform. */
82   private final int mAmountOfTries;
83   private HttpRequestResponseHeader mRequestResponseHeader = null;
84
85   private HttpConnectorEventListener mEventListener;
86   private ConnectorContext mListenerContext;
87
88   /**
89    * Constructor.
90    * Gets the connection spec and the connection factory to establish new
91    * connections for retries if necessary.
92    *
93    * @param cf the connection factory for establishing connections
94    * @param cs the connection spec identifying the connection target
95    */
96   public HttpConnectionHelper (
97         HttpConnectionFactory cf, HttpConnectionSpec cs)
98   {
99      Assert.notNull(cs, "cs");
100      mConnectionFactoryImpl = (HttpConnectionFactoryImpl) cf;
101      mConnectionSpec = cs;
102
103      mConfig = ConfigurationFactory.getConfiguration();
104      mAmountOfTries = mConfig.getAmountOfTriesForwardingRequest();
105   }
106
107   /** {@inheritDoc} */
108   public byte[] sendAndReceive (byte[] message)
109         throws ResourceException, ConnectorException
110   {
111      final String methodName = "sendAndReceive";
112      if (logger.isLoggable(Level.FINER))
113      {
114         logger.entering(CLASSNAME, methodName);
115      }
116      ConnectorException caughtException = null;
117
118      mIsRetryRequired = false;
119      byte[] response = null;
120      int tryNumber = 0;
121      List collectedExceptions = null;
122
123      fireBeforeSend();
124
125      do
126      {
127         try
128         {
129            tryNumber++;
130            response = process(message);
131         }
132         catch (ConnectorException ce)
133         {
134            if (collectedExceptions == null)
135            {
136               collectedExceptions = new ArrayList();
137            }
138            caughtException = ce;
139            if (mIsRetryRequired && tryNumber < mAmountOfTries)
140            {
141               collectedExceptions.add(caughtException);
142               logForRetry(tryNumber, caughtException);
143               caughtException = null;
144               sleepForDelay();
145            }
146         }
147      } // ..until we have received a response or the amount of tries
148        // has been exceeded or the last caught exception does not lead
149        // to an retry
150      while (response == null
151            && tryNumber < mAmountOfTries
152            && caughtException == null);
153
154      fireAfterReceive(tryNumber, response);
155
156      assertRetries(tryNumber, caughtException, collectedExceptions);
157      if (logger.isLoggable(Level.FINER))
158      {
159         logger.exiting(CLASSNAME, methodName);
160      }
161      return response;
162   }
163
164   private void assertRetries (
165         int tryNumber,
166         ConnectorException caughtException,
167         List collectedExceptions)
168         throws ConnectorException
169   {
170      if (tryNumber >= mAmountOfTries)
171      {  // throw TimeoutException if amout or tries are exceeded
172         collectedExceptions.add(caughtException);
173         final ConnectorException ex = createFinalTimeoutException(
174               collectedExceptions);
175         throw ex;
176      }
177      else if (caughtException != null)
178      {
179         // ..otherwise throw last exception
180         throw caughtException;
181      }
182   }
183
184   /**
185    * Performs the send and receive on the real connection handle
186    * (HttpConnectionImpl).
187    *
188    * @param message the message to send
189    * @return byte[] the response in return
190    * @throws ResourceException in case of an resource adapter failure
191    *          within the application server
192    * @throws ConnectorException in case of a connection specific failure
193    */
194   private byte[] process (byte[] message)
195         throws ResourceException, ConnectorException
196   {
197      final String methodName = "process";
198      if (logger.isLoggable(Level.FINER))
199      {
200         logger.entering(CLASSNAME, methodName);
201      }
202      byte[] response;
203      try
204      {
205         response = getConnection().sendAndReceive(message);
206      }
207      catch (ConnectorException ce)
208      {
209         mIsRetryRequired = mConnection.isRetryRequired();
210         mRequiredDelay = mConnection.getRequiredDelayForRetries();
211         mConnection = null;
212         if (logger.isLoggable(Level.FINER))
213         {
214            logger.throwing(CLASSNAME, methodName, ce);
215         }
216         throw ce;
217      }
218      catch (ResourceException re)
219      {
220         if (mConnection == null)
221         {
222            mIsRetryRequired = true;
223            mRequiredDelay
224                  = mConfig.getConnectionErrorRetryDelayInMilliSeconds();
225            final CreatingConnectorFailedException rqe
226                  = new CreatingConnectorFailedException(
227                     mConnectionSpec.getUrl(), re);
228            if (logger.isLoggable(Level.FINER))
229            {
230               logger.throwing(CLASSNAME, methodName, rqe);
231            }
232            throw rqe;
233         }
234         if (logger.isLoggable(Level.FINER))
235         {
236            logger.throwing(CLASSNAME, methodName, re);
237         }
238         throw re;
239      }
240      if (logger.isLoggable(Level.FINER))
241      {
242         logger.exiting(CLASSNAME, methodName);
243      }
244      return response;
245   }
246
247   /** {@inheritDoc} */
248   public void setEventListener (HttpConnectorEventListener listener,
249         ConnectorContext context)
250         throws ResourceException
251   {
252      mEventListener = listener;
253      mListenerContext = context;
254      getConnection().setEventListener(listener, context);
255   }
256
257   /** {@inheritDoc} */
258   public void setRequestResponseHeader (HttpRequestResponseHeader header)
259         throws ResourceException
260   {
261      mRequestResponseHeader = header;
262      if (mConnection != null)
263      {
264         mConnection.setRequestResponseHeader(mRequestResponseHeader);
265      }
266   }
267
268   /** {@inheritDoc} */
269   public void close ()
270   {
271      if (mConnection != null)
272      {
273         mConnection.close();
274      }
275   }
276
277   private void fireBeforeSend ()
278   {
279      if (mEventListener != null)
280      {
281         mEventListener.requestSendWithRetry(mListenerContext);
282      }
283   }
284
285   private void fireAfterReceive (int retries, byte[] response)
286   {
287      if (mEventListener != null)
288      {
289         mEventListener.responseReceivedAfterRetry(
290               retries, response, mListenerContext);
291      }
292   }
293
294   /**
295    * Gets the HttpConnectionImpl object implementing the
296    * HttpConnection interface.
297    * @return HttpConnection
298    * @throws ResourceException in case of an error whilst obtaining the
299    *          MpiConnection object
300    */
301   private HttpConnection getConnection ()
302         throws ResourceException
303   {
304      final String methodName = "getConnection";
305      if (logger.isLoggable(Level.FINER))
306      {
307         logger.entering(CLASSNAME, methodName);
308      }
309      if (mConnection == null)
310      {
311         mConnection = (HttpConnectionExtended) mConnectionFactoryImpl.
312               getConnectionHandle(mConnectionSpec);
313         if (mRequestResponseHeader != null)
314         {
315            mConnection.setRequestResponseHeader(mRequestResponseHeader);
316         }
317      }
318      if (logger.isLoggable(Level.FINER))
319      {
320         logger.exiting(CLASSNAME, methodName, mConnection);
321      }
322      return mConnection;
323   }
324
325   /**
326    * Logs additional information on LEVEL.FINE for a retry sending request.
327    * @param tryNumber the number of try
328    * @param ce the caught exception
329    */
330   private void logForRetry (int tryNumber, ConnectorException ce)
331   {
332      if (logger.isLoggable(Level.FINE))
333      {
334         final String messageText = "Will retry to send request "
335               + " after sleeping " + mRequiredDelay + " millis."
336               + "Resend caused by an '" + ce
337               + "'. This is try number " + tryNumber + ".";
338         logger.fine(messageText);
339      }
340   }
341
342   /**
343    * Sleeping for the time in milli seconds set for delay.
344    * @param tries
345    *          the number of current try
346    */
347   private void sleepForDelay ()
348   {
349      try
350      {
351         Thread.sleep(mRequiredDelay);
352      }
353      catch (InterruptedException e)
354      {
355         throw new InternalErrorException(
356               "Interrupt while sleeping for delay between connection retries",
357               e);
358      }
359   }
360
361   /**
362    * Adds the previously occurred exceptions as parameters to the
363    * last exception,.
364    */
365   private ConnectorException createFinalTimeoutException (List exceptions)
366   {
367      // T-T-UC8.E2 - Three Failed Attempts to Send Request
368      // T-T-UC8.E2.1
369      // All failures are mapped to a timeout exception!
370      ConnectionTimeoutErrorException result;
371      if (exceptions != null)
372      {
373         final Iterator i = exceptions.iterator();
374         int pos = 0;
375         final StringBuffer failures = new StringBuffer();
376         while (i.hasNext())
377         {
378            pos++;
379            final Exception e = (Exception) i.next();
380            failures.append("TRY_");
381            failures.append(pos);
382            failures.append("_EXCEPTION_WAS ");
383            failures.append(String.valueOf(e));
384            failures.append('\n');
385            failures.append("stacktrace:");
386            failures.append(getStackTrace(e, null));
387            failures.append('\n');
388         }
389         result = new ConnectionTimeoutErrorException(
390            mConnectionSpec.getUrl(), failures.toString());
391      }
392      else
393      {
394         result = new ConnectionTimeoutErrorException(
395               mConnectionSpec.getUrl(), null);
396      }
397      return result;
398   }
399
400   private String getStackTrace (Throwable ex, StringBuffer result)
401   {
402      final StackTraceElement[] stack = ex.getStackTrace();
403      final StringBuffer buffer;
404      if (result == null)
405      {
406         buffer = new StringBuffer();
407      }
408      else
409      {
410         buffer = result;
411         buffer.append('\n');
412         buffer.append("Caused by:");
413         buffer.append(ex.toString());
414      }
415      buffer.append('\n');
416      int ix = 0;
417      while (ix < stack.length)
418      {
419         final StackTraceElement stackElement = stack[ix];
420         buffer.append(stackElement.toString());
421         buffer.append('\n');
422         ix++;
423      }
424      final Throwable cause = ex.getCause();
425      if (cause != null)
426      {
427         getStackTrace(cause, buffer);
428      }
429      return buffer.toString();
430   }
431
432}
Note: See TracBrowser for help on using the browser.