/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.commons.connector.http; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.resource.ResourceException; import org.jcoderz.commons.InternalErrorException; import org.jcoderz.commons.connector.ConnectionTimeoutErrorException; import org.jcoderz.commons.connector.ConnectorConfiguration; import org.jcoderz.commons.connector.ConnectorException; import org.jcoderz.commons.connector.CreatingConnectorFailedException; import org.jcoderz.commons.connector.http.transport.ConnectorContext; import org.jcoderz.commons.connector.http.transport.HttpConnectorEventListener; import org.jcoderz.commons.connector.http.transport.HttpRequestResponseHeader; import org.jcoderz.commons.util.Assert; /** * Implementation of the HttpConnection interface. * This class will be used by the client as connection handle * (supports retries). * */ public final class HttpConnectionHelper implements HttpConnection { /** Class name used for logging. */ private static final String CLASSNAME = HttpConnectionHelper.class.getName(); /** Logger in use. */ private static final Logger logger = Logger.getLogger(CLASSNAME); /** ConnectionFactory to create HttpConnectionImpl instance that is associated with a managed connecion. */ private final HttpConnectionFactoryImpl mConnectionFactoryImpl; /** Connection Spec for the requested connection. */ private final HttpConnectionSpec mConnectionSpec; /** Connection handle to the managed connection. */ private HttpConnectionExtended mConnection; /** Delay for retries in milli seconds. */ private int mRequiredDelay; /** Flag indicating that a delay for retries is necessary. */ private boolean mIsRetryRequired = false; private final ConnectorConfiguration mConfig; /** Number of retries to perform. */ private final int mAmountOfTries; private HttpRequestResponseHeader mRequestResponseHeader = null; private HttpConnectorEventListener mEventListener; private ConnectorContext mListenerContext; /** * Constructor. * Gets the connection spec and the connection factory to establish new * connections for retries if necessary. * * @param cf the connection factory for establishing connections * @param cs the connection spec identifying the connection target */ public HttpConnectionHelper ( HttpConnectionFactory cf, HttpConnectionSpec cs) { Assert.notNull(cs, "cs"); mConnectionFactoryImpl = (HttpConnectionFactoryImpl) cf; mConnectionSpec = cs; mConfig = ConfigurationFactory.getConfiguration(); mAmountOfTries = mConfig.getAmountOfTriesForwardingRequest(); } /** {@inheritDoc} */ public byte[] sendAndReceive (byte[] message) throws ResourceException, ConnectorException { final String methodName = "sendAndReceive"; if (logger.isLoggable(Level.FINER)) { logger.entering(CLASSNAME, methodName); } ConnectorException caughtException = null; mIsRetryRequired = false; byte[] response = null; int tryNumber = 0; List collectedExceptions = null; fireBeforeSend(); do { try { tryNumber++; response = process(message); } catch (ConnectorException ce) { if (collectedExceptions == null) { collectedExceptions = new ArrayList(); } caughtException = ce; if (mIsRetryRequired && tryNumber < mAmountOfTries) { collectedExceptions.add(caughtException); logForRetry(tryNumber, caughtException); caughtException = null; sleepForDelay(); } } } // ..until we have received a response or the amount of tries // has been exceeded or the last caught exception does not lead // to an retry while (response == null && tryNumber < mAmountOfTries && caughtException == null); fireAfterReceive(tryNumber, response); assertRetries(tryNumber, caughtException, collectedExceptions); if (logger.isLoggable(Level.FINER)) { logger.exiting(CLASSNAME, methodName); } return response; } private void assertRetries ( int tryNumber, ConnectorException caughtException, List collectedExceptions) throws ConnectorException { if (tryNumber >= mAmountOfTries) { // throw TimeoutException if amout or tries are exceeded collectedExceptions.add(caughtException); final ConnectorException ex = createFinalTimeoutException( collectedExceptions); throw ex; } else if (caughtException != null) { // ..otherwise throw last exception throw caughtException; } } /** * Performs the send and receive on the real connection handle * (HttpConnectionImpl). * * @param message the message to send * @return byte[] the response in return * @throws ResourceException in case of an resource adapter failure * within the application server * @throws ConnectorException in case of a connection specific failure */ private byte[] process (byte[] message) throws ResourceException, ConnectorException { final String methodName = "process"; if (logger.isLoggable(Level.FINER)) { logger.entering(CLASSNAME, methodName); } byte[] response; try { response = getConnection().sendAndReceive(message); } catch (ConnectorException ce) { mIsRetryRequired = mConnection.isRetryRequired(); mRequiredDelay = mConnection.getRequiredDelayForRetries(); mConnection = null; if (logger.isLoggable(Level.FINER)) { logger.throwing(CLASSNAME, methodName, ce); } throw ce; } catch (ResourceException re) { if (mConnection == null) { mIsRetryRequired = true; mRequiredDelay = mConfig.getConnectionErrorRetryDelayInMilliSeconds(); final CreatingConnectorFailedException rqe = new CreatingConnectorFailedException( mConnectionSpec.getUrl(), re); if (logger.isLoggable(Level.FINER)) { logger.throwing(CLASSNAME, methodName, rqe); } throw rqe; } if (logger.isLoggable(Level.FINER)) { logger.throwing(CLASSNAME, methodName, re); } throw re; } if (logger.isLoggable(Level.FINER)) { logger.exiting(CLASSNAME, methodName); } return response; } /** {@inheritDoc} */ public void setEventListener (HttpConnectorEventListener listener, ConnectorContext context) throws ResourceException { mEventListener = listener; mListenerContext = context; getConnection().setEventListener(listener, context); } /** {@inheritDoc} */ public void setRequestResponseHeader (HttpRequestResponseHeader header) throws ResourceException { mRequestResponseHeader = header; if (mConnection != null) { mConnection.setRequestResponseHeader(mRequestResponseHeader); } } /** {@inheritDoc} */ public void close () { if (mConnection != null) { mConnection.close(); } } private void fireBeforeSend () { if (mEventListener != null) { mEventListener.requestSendWithRetry(mListenerContext); } } private void fireAfterReceive (int retries, byte[] response) { if (mEventListener != null) { mEventListener.responseReceivedAfterRetry( retries, response, mListenerContext); } } /** * Gets the HttpConnectionImpl object implementing the * HttpConnection interface. * @return HttpConnection * @throws ResourceException in case of an error whilst obtaining the * MpiConnection object */ private HttpConnection getConnection () throws ResourceException { final String methodName = "getConnection"; if (logger.isLoggable(Level.FINER)) { logger.entering(CLASSNAME, methodName); } if (mConnection == null) { mConnection = (HttpConnectionExtended) mConnectionFactoryImpl. getConnectionHandle(mConnectionSpec); if (mRequestResponseHeader != null) { mConnection.setRequestResponseHeader(mRequestResponseHeader); } } if (logger.isLoggable(Level.FINER)) { logger.exiting(CLASSNAME, methodName, mConnection); } return mConnection; } /** * Logs additional information on LEVEL.FINE for a retry sending request. * @param tryNumber the number of try * @param ce the caught exception */ private void logForRetry (int tryNumber, ConnectorException ce) { if (logger.isLoggable(Level.FINE)) { final String messageText = "Will retry to send request " + " after sleeping " + mRequiredDelay + " millis." + "Resend caused by an '" + ce + "'. This is try number " + tryNumber + "."; logger.fine(messageText); } } /** * Sleeping for the time in milli seconds set for delay. * @param tries * the number of current try */ private void sleepForDelay () { try { Thread.sleep(mRequiredDelay); } catch (InterruptedException e) { throw new InternalErrorException( "Interrupt while sleeping for delay between connection retries", e); } } /** * Adds the previously occurred exceptions as parameters to the * last exception,. */ private ConnectorException createFinalTimeoutException (List exceptions) { // T-T-UC8.E2 - Three Failed Attempts to Send Request // T-T-UC8.E2.1 // All failures are mapped to a timeout exception! ConnectionTimeoutErrorException result; if (exceptions != null) { final Iterator i = exceptions.iterator(); int pos = 0; final StringBuffer failures = new StringBuffer(); while (i.hasNext()) { pos++; final Exception e = (Exception) i.next(); failures.append("TRY_"); failures.append(pos); failures.append("_EXCEPTION_WAS "); failures.append(String.valueOf(e)); failures.append('\n'); failures.append("stacktrace:"); failures.append(getStackTrace(e, null)); failures.append('\n'); } result = new ConnectionTimeoutErrorException( mConnectionSpec.getUrl(), failures.toString()); } else { result = new ConnectionTimeoutErrorException( mConnectionSpec.getUrl(), null); } return result; } private String getStackTrace (Throwable ex, StringBuffer result) { final StackTraceElement[] stack = ex.getStackTrace(); final StringBuffer buffer; if (result == null) { buffer = new StringBuffer(); } else { buffer = result; buffer.append('\n'); buffer.append("Caused by:"); buffer.append(ex.toString()); } buffer.append('\n'); int ix = 0; while (ix < stack.length) { final StackTraceElement stackElement = stack[ix]; buffer.append(stackElement.toString()); buffer.append('\n'); ix++; } final Throwable cause = ex.getCause(); if (cause != null) { getStackTrace(cause, buffer); } return buffer.toString(); } }