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