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