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.ExchangeProperty;
021    import org.apache.camel.Processor;
022    import org.apache.camel.RuntimeCamelException;
023    import org.apache.camel.processor.DelegateProcessor;
024    import org.apache.camel.processor.RedeliveryPolicy;
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    import org.springframework.transaction.TransactionDefinition;
028    import org.springframework.transaction.TransactionStatus;
029    import org.springframework.transaction.support.DefaultTransactionStatus;
030    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
031    import org.springframework.transaction.support.TransactionSynchronizationManager;
032    import org.springframework.transaction.support.TransactionTemplate;
033    
034    /**
035     * The <a href="http://activemq.apache.org/camel/transactional-client.html">Transactional Client</a>
036     * EIP pattern.
037     *
038     * @version $Revision: 674383 $
039     */
040    public class TransactionInterceptor extends DelegateProcessor {
041        public static final ExchangeProperty<Boolean> TRANSACTED =
042            new ExchangeProperty<Boolean>("transacted", "org.apache.camel.transacted", Boolean.class);
043        private static final transient Log LOG = LogFactory.getLog(TransactionInterceptor.class);
044        private final TransactionTemplate transactionTemplate;
045        private ThreadLocal<RedeliveryData> previousRollback = new ThreadLocal<RedeliveryData>() {
046            @Override
047            protected RedeliveryData initialValue() {
048                return new RedeliveryData();
049            }
050        };
051        private RedeliveryPolicy redeliveryPolicy;
052    
053        public TransactionInterceptor(TransactionTemplate transactionTemplate) {
054            this.transactionTemplate = transactionTemplate;
055        }
056    
057        public TransactionInterceptor(Processor processor, TransactionTemplate transactionTemplate) {
058            super(processor);
059            this.transactionTemplate = transactionTemplate;
060        }
061    
062        public TransactionInterceptor(Processor processor, TransactionTemplate transactionTemplate, RedeliveryPolicy redeliveryPolicy) {
063            this(processor, transactionTemplate);
064            this.redeliveryPolicy = redeliveryPolicy;
065        }
066    
067        @Override
068        public String toString() {
069            return "TransactionInterceptor:"
070                + propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
071                + "[" + getProcessor() + "]";
072        }
073    
074        public void process(final Exchange exchange) {
075            LOG.debug("Transaction begin");
076    
077            final RedeliveryData redeliveryData = previousRollback.get();
078    
079            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
080                protected void doInTransactionWithoutResult(TransactionStatus status) {
081                    // TODO: The delay is in some cases never triggered - see CAMEL-663
082                    if (redeliveryPolicy != null && redeliveryData.previousRollback) {
083                        // lets delay
084                        redeliveryData.redeliveryDelay = redeliveryPolicy.sleep(redeliveryData.redeliveryDelay);
085                    }
086    
087                    // wrapper exception to throw if the exchange failed
088                    // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
089                    RuntimeCamelException rce = null;
090    
091                    boolean activeTx = false;
092                    try {
093                        // find out if there is an actual transaction alive, and thus we are in transacted mode
094                        activeTx = TransactionSynchronizationManager.isActualTransactionActive();
095                        if (!activeTx) {
096                            activeTx = status.isNewTransaction() && !status.isCompleted();
097                            if (!activeTx) {
098                                if (DefaultTransactionStatus.class.isAssignableFrom(status.getClass())) {
099                                    DefaultTransactionStatus defStatus = DefaultTransactionStatus.class
100                                        .cast(status);
101                                    activeTx = defStatus.hasTransaction() && !status.isCompleted();
102                                }
103                            }
104                        }
105                        if (LOG.isDebugEnabled()) {
106                            LOG.debug("Is actual transaction active: " + activeTx);
107                        }
108    
109                        // okay mark the exchange as transacted, then the DeadLetterChannel or others know
110                        // its an transacted exchange
111                        if (activeTx) {
112                            TRANSACTED.set(exchange, Boolean.TRUE);
113                        }
114    
115                        // process the exchange
116                        processNext(exchange);
117    
118                        // wrap if the exchange failed with an exception
119                        if (exchange.getException() != null) {
120                            rce = new RuntimeCamelException(exchange.getException());
121                        }
122                    } catch (Exception e) {
123                         // wrap if the exchange threw an exception
124                        rce = new RuntimeCamelException(e);
125                    }
126    
127                    // rehrow exception if the exchange failed
128                    if (rce != null) {
129                        redeliveryData.previousRollback = true;
130                        if (activeTx) {
131                            status.setRollbackOnly();
132                            LOG.debug("Transaction rollback");
133                        }
134                        throw rce;
135                    }
136                }
137            });
138    
139            redeliveryData.previousRollback = false;
140            redeliveryData.redeliveryDelay = 0L;
141    
142            LOG.debug("Transaction commit");
143        }
144    
145    
146        public RedeliveryPolicy getRedeliveryPolicy() {
147            return redeliveryPolicy;
148        }
149    
150        public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
151            this.redeliveryPolicy = redeliveryPolicy;
152        }
153    
154        protected static class RedeliveryData {
155            boolean previousRollback;
156            long redeliveryDelay;
157        }
158    
159        protected String propagationBehaviorToString(int propagationBehavior) {
160            String rc;
161            switch (propagationBehavior) {
162            case TransactionDefinition.PROPAGATION_MANDATORY:
163                rc = "PROPAGATION_MANDATORY";
164                break;
165            case TransactionDefinition.PROPAGATION_NESTED:
166                rc = "PROPAGATION_NESTED";
167                break;
168            case TransactionDefinition.PROPAGATION_NEVER:
169                rc = "PROPAGATION_NEVER";
170                break;
171            case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
172                rc = "PROPAGATION_NOT_SUPPORTED";
173                break;
174            case TransactionDefinition.PROPAGATION_REQUIRED:
175                rc = "PROPAGATION_REQUIRED";
176                break;
177            case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
178                rc = "PROPAGATION_REQUIRES_NEW";
179                break;
180            case TransactionDefinition.PROPAGATION_SUPPORTS:
181                rc = "PROPAGATION_SUPPORTS";
182                break;
183            default:
184                rc = "UNKNOWN";
185            }
186            return rc;
187        }
188    
189    }