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