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.RuntimeCamelException;
023 import org.apache.camel.processor.Logger;
024 import org.apache.camel.processor.RedeliveryErrorHandler;
025 import org.apache.camel.processor.RedeliveryPolicy;
026 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy;
027 import org.apache.camel.util.ObjectHelper;
028 import org.springframework.transaction.TransactionDefinition;
029 import org.springframework.transaction.TransactionStatus;
030 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
031 import org.springframework.transaction.support.TransactionTemplate;
032
033 /**
034 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a>
035 * EIP pattern.
036 *
037 * @version $Revision: 886781 $
038 */
039 public class TransactionErrorHandler extends RedeliveryErrorHandler {
040
041 private final TransactionTemplate transactionTemplate;
042
043 /**
044 * Creates the transaction error handler.
045 *
046 * @param output outer processor that should use this default error handler
047 * @param logger logger to use for logging failures and redelivery attempts
048 * @param redeliveryProcessor an optional processor to run before redelivery attempt
049 * @param redeliveryPolicy policy for redelivery
050 * @param handledPolicy policy for handling failed exception that are moved to the dead letter queue
051 * @param exceptionPolicyStrategy strategy for onException handling
052 * @param transactionTemplate the transaction template
053 */
054 public TransactionErrorHandler(Processor output, Logger logger, Processor redeliveryProcessor,
055 RedeliveryPolicy redeliveryPolicy, Predicate handledPolicy,
056 ExceptionPolicyStrategy exceptionPolicyStrategy, TransactionTemplate transactionTemplate) {
057 super(output, logger, redeliveryProcessor, redeliveryPolicy, handledPolicy, null, null, false);
058 setExceptionPolicy(exceptionPolicyStrategy);
059 this.transactionTemplate = transactionTemplate;
060 }
061
062 public boolean supportTransacted() {
063 return true;
064 }
065
066 @Override
067 public String toString() {
068 if (output == null) {
069 // if no output then don't do any description
070 return "";
071 }
072 return "TransactionErrorHandler:"
073 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
074 + "[" + getOutput() + "]";
075 }
076
077 public void process(final Exchange exchange) throws Exception {
078 if (exchange.getUnitOfWork().isTransactedBy(transactionTemplate)) {
079 // already transacted by this transaction template
080 // so lets just let the regular default error handler process it
081 processByRegularErrorHandler(exchange);
082 } else {
083 // not yet wrapped in transaction so lets do that
084 processInTransaction(exchange);
085 }
086 }
087
088 protected void processByRegularErrorHandler(Exchange exchange) {
089 try {
090 super.process(exchange);
091 } catch (Exception e) {
092 exchange.setException(e);
093 }
094 }
095
096 protected void processInTransaction(final Exchange exchange) throws Exception {
097 String id = ObjectHelper.getIdentityHashCode(transactionTemplate);
098 try {
099 // mark the beginning of this transaction boundary
100 exchange.getUnitOfWork().beginTransactedBy(transactionTemplate);
101
102 if (log.isDebugEnabled()) {
103 log.debug("Transaction begin (" + id + ") for ExchangeId: " + exchange.getExchangeId());
104 }
105
106 doInTransactionTemplate(exchange);
107
108 if (log.isDebugEnabled()) {
109 log.debug("Transaction commit (" + id + ") for ExchangeId: " + exchange.getExchangeId());
110 }
111 } catch (TransactionRollbackException e) {
112 // ignore as its just a dummy exception to force spring TX to rollback
113 if (log.isDebugEnabled()) {
114 log.debug("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId());
115 }
116 } catch (Exception e) {
117 log.warn("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId() + " due exception: " + e.getMessage());
118 exchange.setException(e);
119 } finally {
120 // mark the end of this transaction boundary
121 exchange.getUnitOfWork().endTransactedBy(transactionTemplate);
122 }
123 }
124
125 protected void doInTransactionTemplate(final Exchange exchange) {
126
127 // spring transaction template is working best with rollback if you throw it a runtime exception
128 // otherwise it may not rollback messages send to JMS queues etc.
129
130 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
131 protected void doInTransactionWithoutResult(TransactionStatus status) {
132 // wrapper exception to throw if the exchange failed
133 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
134 RuntimeCamelException rce = null;
135
136 exchange.setProperty(Exchange.TRANSACTED, Boolean.TRUE);
137
138 // and now let process the exchange
139 try {
140 TransactionErrorHandler.super.process(exchange);
141 } catch (Exception e) {
142 exchange.setException(e);
143 }
144
145 // after handling and still an exception or marked as rollback only then rollback
146 if (exchange.getException() != null || exchange.isRollbackOnly()) {
147
148 // if it was a local rollback only then remove its marker so outer transaction
149 // wont rollback as well (Note: isRollbackOnly() also returns true for ROLLBACK_ONLY_LAST)
150 exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST);
151
152 // wrap exception in transacted exception
153 if (exchange.getException() != null) {
154 rce = ObjectHelper.wrapRuntimeCamelException(exchange.getException());
155 }
156
157 if (!status.isRollbackOnly()) {
158 status.setRollbackOnly();
159 }
160
161 // rethrow if an exception occurred
162 if (rce != null) {
163 throw rce;
164 } else {
165 // create dummy exception to force spring transaction manager to rollback
166 throw new TransactionRollbackException();
167 }
168 }
169 }
170 });
171 }
172
173 protected String propagationBehaviorToString(int propagationBehavior) {
174 String rc;
175 switch (propagationBehavior) {
176 case TransactionDefinition.PROPAGATION_MANDATORY:
177 rc = "PROPAGATION_MANDATORY";
178 break;
179 case TransactionDefinition.PROPAGATION_NESTED:
180 rc = "PROPAGATION_NESTED";
181 break;
182 case TransactionDefinition.PROPAGATION_NEVER:
183 rc = "PROPAGATION_NEVER";
184 break;
185 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
186 rc = "PROPAGATION_NOT_SUPPORTED";
187 break;
188 case TransactionDefinition.PROPAGATION_REQUIRED:
189 rc = "PROPAGATION_REQUIRED";
190 break;
191 case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
192 rc = "PROPAGATION_REQUIRES_NEW";
193 break;
194 case TransactionDefinition.PROPAGATION_SUPPORTS:
195 rc = "PROPAGATION_SUPPORTS";
196 break;
197 default:
198 rc = "UNKNOWN";
199 }
200 return rc;
201 }
202
203 }