001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.spring.spi;
018
019 import org.apache.camel.Exchange;
020 import org.apache.camel.Predicate;
021 import org.apache.camel.Processor;
022 import org.apache.camel.model.OnExceptionDefinition;
023 import org.apache.camel.processor.ErrorHandlerSupport;
024 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy;
025 import org.apache.camel.util.MessageHelper;
026 import org.apache.camel.util.ServiceHelper;
027 import org.apache.commons.logging.Log;
028 import org.apache.commons.logging.LogFactory;
029 import org.springframework.transaction.TransactionDefinition;
030 import org.springframework.transaction.TransactionStatus;
031 import org.springframework.transaction.support.DefaultTransactionStatus;
032 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
033 import org.springframework.transaction.support.TransactionSynchronizationManager;
034 import org.springframework.transaction.support.TransactionTemplate;
035
036 /**
037 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a>
038 * EIP pattern.
039 *
040 * @version $Revision: 765920 $
041 */
042 public class TransactionErrorHandler extends ErrorHandlerSupport {
043
044 private static final transient Log LOG = LogFactory.getLog(TransactionErrorHandler.class);
045 private final TransactionTemplate transactionTemplate;
046 private Processor output;
047
048 public TransactionErrorHandler(TransactionTemplate transactionTemplate) {
049 this.transactionTemplate = transactionTemplate;
050 }
051
052 public TransactionErrorHandler(TransactionTemplate transactionTemplate, Processor output,
053 ExceptionPolicyStrategy exceptionPolicy) {
054 this.transactionTemplate = transactionTemplate;
055 setOutput(output);
056 setExceptionPolicy(exceptionPolicy);
057 }
058
059 public boolean supportTransacted() {
060 return true;
061 }
062
063 @Override
064 public String toString() {
065 if (output == null) {
066 // if no output then dont do any description
067 return "";
068 }
069 return "TransactionErrorHandler:"
070 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
071 + "[" + getOutput() + "]";
072 }
073
074 public void process(final Exchange exchange) {
075 if (output == null) {
076 // no output then just return as nothing to wrap in a transaction
077 return;
078 }
079
080 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
081 protected void doInTransactionWithoutResult(TransactionStatus status) {
082
083 // wrapper exception to throw if the exchange failed
084 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
085 TransactedRuntimeCamelException rce;
086
087 // find out if there is an actual transaction alive, and thus we are in transacted mode
088 boolean activeTx = TransactionSynchronizationManager.isActualTransactionActive();
089 if (!activeTx) {
090 activeTx = status.isNewTransaction() && !status.isCompleted();
091 if (!activeTx) {
092 if (DefaultTransactionStatus.class.isAssignableFrom(status.getClass())) {
093 DefaultTransactionStatus defStatus = DefaultTransactionStatus.class.cast(status);
094 activeTx = defStatus.hasTransaction() && !status.isCompleted();
095 }
096 }
097 }
098 if (LOG.isTraceEnabled()) {
099 LOG.trace("Is actual transaction active: " + activeTx);
100 }
101
102 // okay mark the exchange as transacted, then the DeadLetterChannel or others know
103 // its a transacted exchange
104 if (activeTx) {
105 exchange.setProperty(Exchange.TRANSACTED, Boolean.TRUE);
106 }
107
108 try {
109 // process the exchange
110 output.process(exchange);
111 } catch (Exception e) {
112 exchange.setException(e);
113 }
114
115 // an exception occured maybe an onException can handle it
116 if (exchange.getException() != null) {
117 // handle onException
118 // but test beforehand if we have already handled it, if so we should not do it again
119 boolean handled = false;
120 if (exchange.getException() instanceof TransactedRuntimeCamelException) {
121 TransactedRuntimeCamelException trce = exchange.getException(TransactedRuntimeCamelException.class);
122 handled = trce.isHandled();
123 }
124 if (!handled) {
125 // not handled before so handle it once
126 handleException(exchange);
127 }
128 }
129
130 // after handling and still an exception or marked as rollback only then rollback
131 if (exchange.getException() != null || exchange.isRollbackOnly()) {
132 rce = wrapTransactedRuntimeException(exchange.getException());
133
134 if (activeTx) {
135 status.setRollbackOnly();
136 if (LOG.isDebugEnabled()) {
137 if (rce != null) {
138 LOG.debug("Setting transaction to rollbackOnly due to exception being thrown: " + rce.getMessage());
139 } else {
140 LOG.debug("Setting transaction to rollbackOnly as Exchange was marked as rollback only");
141 }
142 }
143 }
144
145 // rethrow if an exception occured
146 if (rce != null) {
147 throw rce;
148 }
149 }
150 }
151 });
152 }
153
154 protected TransactedRuntimeCamelException wrapTransactedRuntimeException(Exception exception) {
155 if (exception instanceof TransactedRuntimeCamelException) {
156 return (TransactedRuntimeCamelException) exception;
157 } else {
158 // Mark as handled so we dont want to handle the same exception twice or more in other
159 // wrapped transaction error handlers in this route.
160 // We need to mark this information in the exception as we need to propagage
161 // the exception back by rehtrowing it. We cannot mark it on the exchange as Camel
162 // uses copies of exchanges in its pipeline and the data isnt copied back in case
163 // when an exception occured
164 return new TransactedRuntimeCamelException(exception, true);
165 }
166 }
167
168 /**
169 * Handles when an exception occured during processing. Is used to let the exception policy
170 * deal with it, eg letting an onException handle it.
171 *
172 * @param exchange the current exchange
173 */
174 protected void handleException(Exchange exchange) {
175 Exception e = exchange.getException();
176 // store the original caused exception in a property, so we can restore it later
177 exchange.setProperty(Exchange.EXCEPTION_CAUGHT, e);
178
179 // find the error handler to use (if any)
180 OnExceptionDefinition exceptionPolicy = getExceptionPolicy(exchange, e);
181 if (exceptionPolicy != null) {
182 Predicate handledPredicate = exceptionPolicy.getHandledPolicy();
183
184 Processor processor = exceptionPolicy.getErrorHandler();
185 prepareExchangeBeforeOnException(exchange);
186 if (processor != null) {
187 deliverToFaultProcessor(exchange, processor);
188 }
189 prepareExchangeAfterOnException(exchange, handledPredicate);
190 }
191 }
192
193 private void deliverToFaultProcessor(Exchange exchange, Processor faultProcessor) {
194 try {
195 faultProcessor.process(exchange);
196 } catch (Exception e) {
197 // fault processor also failed so set the exception
198 exchange.setException(e);
199 }
200 }
201
202 private void prepareExchangeBeforeOnException(Exchange exchange) {
203 // okay lower the exception as we are handling it by onException
204 if (exchange.getException() != null) {
205 exchange.setException(null);
206 }
207
208 // clear rollback flags
209 exchange.setProperty(Exchange.ROLLBACK_ONLY, null);
210
211 // reset cached streams so they can be read again
212 MessageHelper.resetStreamCache(exchange.getIn());
213 }
214
215 private void prepareExchangeAfterOnException(Exchange exchange, Predicate handledPredicate) {
216 if (handledPredicate == null || !handledPredicate.matches(exchange)) {
217 if (LOG.isDebugEnabled()) {
218 LOG.debug("This exchange is not handled so its marked as rollback only: " + exchange);
219 }
220 // exception not handled, put exception back in the exchange
221 exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
222 // mark as rollback so we dont do multiple onException for this one
223 exchange.setProperty(Exchange.ROLLBACK_ONLY, Boolean.TRUE);
224 } else {
225 if (LOG.isDebugEnabled()) {
226 LOG.debug("This exchange is handled so its marked as not failed: " + exchange);
227 }
228 exchange.setProperty(Exchange.EXCEPTION_HANDLED, Boolean.TRUE);
229 }
230 }
231
232 protected String propagationBehaviorToString(int propagationBehavior) {
233 String rc;
234 switch (propagationBehavior) {
235 case TransactionDefinition.PROPAGATION_MANDATORY:
236 rc = "PROPAGATION_MANDATORY";
237 break;
238 case TransactionDefinition.PROPAGATION_NESTED:
239 rc = "PROPAGATION_NESTED";
240 break;
241 case TransactionDefinition.PROPAGATION_NEVER:
242 rc = "PROPAGATION_NEVER";
243 break;
244 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
245 rc = "PROPAGATION_NOT_SUPPORTED";
246 break;
247 case TransactionDefinition.PROPAGATION_REQUIRED:
248 rc = "PROPAGATION_REQUIRED";
249 break;
250 case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
251 rc = "PROPAGATION_REQUIRES_NEW";
252 break;
253 case TransactionDefinition.PROPAGATION_SUPPORTS:
254 rc = "PROPAGATION_SUPPORTS";
255 break;
256 default:
257 rc = "UNKNOWN";
258 }
259 return rc;
260 }
261
262 protected void doStart() throws Exception {
263 ServiceHelper.startServices(output);
264 }
265
266 protected void doStop() throws Exception {
267 ServiceHelper.stopServices(output);
268 }
269
270 /**
271 * Returns the output processor
272 */
273 public Processor getOutput() {
274 return output;
275 }
276
277 public void setOutput(Processor output) {
278 this.output = output;
279 }
280
281 }