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    }