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.DelayPolicy;
024    import org.apache.camel.processor.DelegateProcessor;
025    import org.apache.camel.processor.RedeliveryPolicy;
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.springframework.transaction.TransactionDefinition;
029    import org.springframework.transaction.TransactionStatus;
030    import org.springframework.transaction.support.DefaultTransactionStatus;
031    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
032    import org.springframework.transaction.support.TransactionSynchronizationManager;
033    import org.springframework.transaction.support.TransactionTemplate;
034    
035    import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
036    
037    /**
038     * The <a href="http://activemq.apache.org/camel/transactional-client.html">Transactional Client</a>
039     * EIP pattern.
040     *
041     * @version $Revision: 695721 $
042     */
043    public class TransactionInterceptor extends DelegateProcessor {
044        public static final ExchangeProperty<Boolean> TRANSACTED =
045            new ExchangeProperty<Boolean>("transacted", "org.apache.camel.transacted", Boolean.class);
046        private static final transient Log LOG = LogFactory.getLog(TransactionInterceptor.class);
047        private final TransactionTemplate transactionTemplate;
048        private RedeliveryPolicy redeliveryPolicy;
049        private DelayPolicy delayPolicy;
050    
051        public TransactionInterceptor(TransactionTemplate transactionTemplate) {
052            this.transactionTemplate = transactionTemplate;
053        }
054    
055        public TransactionInterceptor(Processor processor, TransactionTemplate transactionTemplate) {
056            super(processor);
057            this.transactionTemplate = transactionTemplate;
058        }
059    
060        /**
061         * @deprecated use DelayPolicy. Will be removed in Camel 2.0
062         */
063        public TransactionInterceptor(Processor processor, TransactionTemplate transactionTemplate, RedeliveryPolicy redeliveryPolicy) {
064            this(processor, transactionTemplate);
065            this.redeliveryPolicy = redeliveryPolicy;
066            this.delayPolicy = redeliveryPolicy;
067        }
068    
069        public TransactionInterceptor(Processor processor, TransactionTemplate transactionTemplate, DelayPolicy delayPolicy) {
070            this(processor, transactionTemplate);
071            this.delayPolicy = delayPolicy;
072        }
073    
074        @Override
075        public String toString() {
076            return "TransactionInterceptor:"
077                + propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
078                + "[" + getProcessor() + "]";
079        }
080    
081        public void process(final Exchange exchange) {
082            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
083                protected void doInTransactionWithoutResult(TransactionStatus status) {
084    
085                    // wrapper exception to throw if the exchange failed
086                    // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
087                    RuntimeCamelException rce = null;
088    
089                    boolean activeTx = false;
090                    try {
091                        // find out if there is an actual transaction alive, and thus we are in transacted mode
092                        activeTx = TransactionSynchronizationManager.isActualTransactionActive();
093                        if (!activeTx) {
094                            activeTx = status.isNewTransaction() && !status.isCompleted();
095                            if (!activeTx) {
096                                if (DefaultTransactionStatus.class.isAssignableFrom(status.getClass())) {
097                                    DefaultTransactionStatus defStatus =
098                                            DefaultTransactionStatus.class.cast(status);
099                                    activeTx = defStatus.hasTransaction() && !status.isCompleted();
100                                }
101                            }
102                        }
103                        if (LOG.isTraceEnabled()) {
104                            LOG.trace("Is actual transaction active: " + activeTx);
105                        }
106    
107                        // okay mark the exchange as transacted, then the DeadLetterChannel or others know
108                        // its a transacted exchange
109                        if (activeTx) {
110                            TRANSACTED.set(exchange, Boolean.TRUE);
111                        }
112    
113                        // process the exchange
114                        processNext(exchange);
115    
116                        // wrap if the exchange failed with an exception
117                        if (exchange.getException() != null) {
118                            rce = wrapRuntimeCamelException(exchange.getException());
119                        }
120                    } catch (Exception e) {
121                        rce = wrapRuntimeCamelException(e);
122                    }
123    
124                    // rethrow exception if the exchange failed
125                    if (rce != null) {
126                        // an exception occured so please sleep before we rethrow the exception
127                        delayBeforeRedelivery();
128                        if (activeTx) {
129                            status.setRollbackOnly();
130                            LOG.debug("Setting transaction to rollbackOnly due to exception being thrown: " + rce.getMessage());
131                        }
132                        throw rce;
133                    }
134                }
135            });
136        }
137    
138        /**
139         * Sleeps before the transaction is set as rollback and the caused exception is rethrown to let the
140         * Spring TransactionManager handle the rollback.
141         */
142        protected void delayBeforeRedelivery() {
143            long delay = 0;
144            if (redeliveryPolicy != null) {
145                delay = redeliveryPolicy.getDelay();
146            } else if (delayPolicy != null) {
147                delay = delayPolicy.getDelay();
148            }
149    
150            if (delay > 0) {
151                try {
152                    if (LOG.isDebugEnabled()) {
153                        LOG.debug("Sleeping for: " + delay + " millis until attempting redelivery");
154                    }
155                    Thread.sleep(delay);
156                } catch (InterruptedException e) {
157                    if (LOG.isDebugEnabled()) {
158                        LOG.debug("Thread interrupted: " + e, e);
159                    }
160                }
161            }
162        }
163    
164        /**
165         * @deprecated use DelayPolicy. Will be removed in Camel 2.0
166         */
167        public RedeliveryPolicy getRedeliveryPolicy() {
168            return redeliveryPolicy;
169        }
170    
171        /**
172         * @deprecated use DelayPolicy. Will be removed in Camel 2.0
173         */
174        public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
175            this.redeliveryPolicy = redeliveryPolicy;
176        }
177    
178        public DelayPolicy getDelayPolicy() {
179            return delayPolicy;
180        }
181    
182        public void setDelayPolicy(DelayPolicy delayPolicy) {
183            this.delayPolicy = delayPolicy;
184        }
185    
186        protected String propagationBehaviorToString(int propagationBehavior) {
187            String rc;
188            switch (propagationBehavior) {
189            case TransactionDefinition.PROPAGATION_MANDATORY:
190                rc = "PROPAGATION_MANDATORY";
191                break;
192            case TransactionDefinition.PROPAGATION_NESTED:
193                rc = "PROPAGATION_NESTED";
194                break;
195            case TransactionDefinition.PROPAGATION_NEVER:
196                rc = "PROPAGATION_NEVER";
197                break;
198            case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
199                rc = "PROPAGATION_NOT_SUPPORTED";
200                break;
201            case TransactionDefinition.PROPAGATION_REQUIRED:
202                rc = "PROPAGATION_REQUIRED";
203                break;
204            case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
205                rc = "PROPAGATION_REQUIRES_NEW";
206                break;
207            case TransactionDefinition.PROPAGATION_SUPPORTS:
208                rc = "PROPAGATION_SUPPORTS";
209                break;
210            default:
211                rc = "UNKNOWN";
212            }
213            return rc;
214        }
215    
216    }