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.AsyncCallback;
020 import org.apache.camel.CamelContext;
021 import org.apache.camel.Exchange;
022 import org.apache.camel.Predicate;
023 import org.apache.camel.Processor;
024 import org.apache.camel.processor.CamelLogger;
025 import org.apache.camel.processor.RedeliveryErrorHandler;
026 import org.apache.camel.processor.RedeliveryPolicy;
027 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy;
028 import org.apache.camel.util.ObjectHelper;
029 import org.springframework.transaction.TransactionDefinition;
030 import org.springframework.transaction.TransactionStatus;
031 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
032 import org.springframework.transaction.support.TransactionTemplate;
033
034 /**
035 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a>
036 * EIP pattern.
037 *
038 * @version
039 */
040 public class TransactionErrorHandler extends RedeliveryErrorHandler {
041
042 private final TransactionTemplate transactionTemplate;
043 private final String transactionKey;
044
045 /**
046 * Creates the transaction error handler.
047 *
048 * @param camelContext the camel context
049 * @param output outer processor that should use this default error handler
050 * @param logger logger to use for logging failures and redelivery attempts
051 * @param redeliveryProcessor an optional processor to run before redelivery attempt
052 * @param redeliveryPolicy policy for redelivery
053 * @param handledPolicy policy for handling failed exception that are moved to the dead letter queue
054 * @param exceptionPolicyStrategy strategy for onException handling
055 * @param transactionTemplate the transaction template
056 * @param retryWhile retry while
057 */
058 public TransactionErrorHandler(CamelContext camelContext, Processor output, CamelLogger logger, Processor redeliveryProcessor,
059 RedeliveryPolicy redeliveryPolicy, Predicate handledPolicy, ExceptionPolicyStrategy exceptionPolicyStrategy,
060 TransactionTemplate transactionTemplate, Predicate retryWhile) {
061 super(camelContext, output, logger, redeliveryProcessor, redeliveryPolicy, handledPolicy, null, null, false, retryWhile);
062 setExceptionPolicy(exceptionPolicyStrategy);
063 this.transactionTemplate = transactionTemplate;
064 this.transactionKey = ObjectHelper.getIdentityHashCode(transactionTemplate);
065 }
066
067 public boolean supportTransacted() {
068 return true;
069 }
070
071 @Override
072 public String toString() {
073 if (output == null) {
074 // if no output then don't do any description
075 return "";
076 }
077 return "TransactionErrorHandler:"
078 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
079 + "[" + getOutput() + "]";
080 }
081
082 @Override
083 public void process(Exchange exchange) throws Exception {
084 // we have to run this synchronously as Spring Transaction does *not* support
085 // using multiple threads to span a transaction
086 if (exchange.getUnitOfWork().isTransactedBy(transactionKey)) {
087 // already transacted by this transaction template
088 // so lets just let the error handler process it
089 processByErrorHandler(exchange);
090 } else {
091 // not yet wrapped in transaction so lets do that
092 // and then have it invoke the error handler from within that transaction
093 processInTransaction(exchange);
094 }
095 }
096
097 @Override
098 public boolean process(Exchange exchange, AsyncCallback callback) {
099 // invoke ths synchronous method as Spring Transaction does *not* support
100 // using multiple threads to span a transaction
101 try {
102 process(exchange);
103 } catch (Throwable e) {
104 exchange.setException(e);
105 }
106
107 // notify callback we are done synchronously
108 callback.done(true);
109 return true;
110 }
111
112 protected void processInTransaction(final Exchange exchange) throws Exception {
113 try {
114 // mark the beginning of this transaction boundary
115 exchange.getUnitOfWork().beginTransactedBy(transactionKey);
116
117 if (log.isDebugEnabled()) {
118 log.debug("Transaction begin (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId());
119 }
120
121 doInTransactionTemplate(exchange);
122
123 if (log.isDebugEnabled()) {
124 log.debug("Transaction commit (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId());
125 }
126 } catch (TransactionRollbackException e) {
127 // ignore as its just a dummy exception to force spring TX to rollback
128 if (log.isDebugEnabled()) {
129 log.debug("Transaction rollback (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId() + " due exchange was marked for rollbackOnly");
130 }
131 } catch (Exception e) {
132 log.warn("Transaction rollback (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId() + " due exception: " + e.getMessage());
133 exchange.setException(e);
134 } finally {
135 // mark the end of this transaction boundary
136 exchange.getUnitOfWork().endTransactedBy(transactionKey);
137 }
138
139 // if it was a local rollback only then remove its marker so outer transaction wont see the marker
140 Boolean onlyLast = (Boolean) exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST);
141 if (onlyLast != null && onlyLast) {
142 if (log.isDebugEnabled()) {
143 // log exception if there was a cause exception so we have the stacktrace
144 Exception cause = exchange.getException();
145 if (cause != null) {
146 log.debug("Transaction rollback (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId()
147 + " due exchange was marked for rollbackOnlyLast and due exception: ", cause);
148 } else {
149 log.debug("Transaction rollback (" + transactionKey + ") for ExchangeId: " + exchange.getExchangeId()
150 + " due exchange was marked for rollbackOnlyLast");
151 }
152 }
153 // remove caused exception due we was marked as rollback only last
154 // so by removing the exception, any outer transaction will not be affected
155 exchange.setException(null);
156 }
157 }
158
159 protected void doInTransactionTemplate(final Exchange exchange) {
160
161 // spring transaction template is working best with rollback if you throw it a runtime exception
162 // otherwise it may not rollback messages send to JMS queues etc.
163
164 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
165 protected void doInTransactionWithoutResult(TransactionStatus status) {
166 // wrapper exception to throw if the exchange failed
167 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
168 RuntimeException rce;
169
170 // and now let process the exchange by the error handler
171 processByErrorHandler(exchange);
172
173 // after handling and still an exception or marked as rollback only then rollback
174 if (exchange.getException() != null || exchange.isRollbackOnly()) {
175
176 // wrap exception in transacted exception
177 if (exchange.getException() != null) {
178 rce = ObjectHelper.wrapRuntimeCamelException(exchange.getException());
179 } else {
180 // create dummy exception to force spring transaction manager to rollback
181 rce = new TransactionRollbackException();
182 }
183
184 if (!status.isRollbackOnly()) {
185 status.setRollbackOnly();
186 }
187
188 // throw runtime exception to force rollback (which works best to rollback with Spring transaction manager)
189 throw rce;
190 }
191 }
192 });
193 }
194
195 /**
196 * Processes the {@link Exchange} using the error handler.
197 * <p/>
198 * This implementation will invoke ensure this occurs synchronously, that means if the async routing engine
199 * did kick in, then this implementation will wait for the task to complete before it continues.
200 *
201 * @param exchange the exchange
202 */
203 protected void processByErrorHandler(final Exchange exchange) {
204 // must invoke the async method with empty callback to have it invoke the
205 // super.processErrorHandler
206 // we are transacted so we have to route synchronously so don't worry about returned
207 // value from the process method
208 // and the camel routing engine will detect this is an transacted Exchange and route
209 // it fully synchronously so we don't have to wait here if we hit an async endpoint
210 // all that is taken care of in the camel-core
211 super.process(exchange, new AsyncCallback() {
212 public void done(boolean doneSync) {
213 // noop
214 }
215 });
216 }
217
218 private static String propagationBehaviorToString(int propagationBehavior) {
219 String rc;
220 switch (propagationBehavior) {
221 case TransactionDefinition.PROPAGATION_MANDATORY:
222 rc = "PROPAGATION_MANDATORY";
223 break;
224 case TransactionDefinition.PROPAGATION_NESTED:
225 rc = "PROPAGATION_NESTED";
226 break;
227 case TransactionDefinition.PROPAGATION_NEVER:
228 rc = "PROPAGATION_NEVER";
229 break;
230 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
231 rc = "PROPAGATION_NOT_SUPPORTED";
232 break;
233 case TransactionDefinition.PROPAGATION_REQUIRED:
234 rc = "PROPAGATION_REQUIRED";
235 break;
236 case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
237 rc = "PROPAGATION_REQUIRES_NEW";
238 break;
239 case TransactionDefinition.PROPAGATION_SUPPORTS:
240 rc = "PROPAGATION_SUPPORTS";
241 break;
242 default:
243 rc = "UNKNOWN";
244 }
245 return rc;
246 }
247
248 }