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.processor;
018    
019    import java.util.Collection;
020    import java.util.Iterator;
021    import java.util.List;
022    
023    import org.apache.camel.Exchange;
024    import org.apache.camel.Processor;
025    import org.apache.camel.impl.DefaultExchange;
026    import org.apache.camel.util.ExchangeHelper;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    
030    /**
031     * Creates a Pipeline pattern where the output of the previous step is sent as
032     * input to the next step, reusing the same message exchanges
033     *
034     * @version $Revision: 836213 $
035     */
036    public class Pipeline extends MulticastProcessor implements Processor, Traceable {
037        private static final transient Log LOG = LogFactory.getLog(Pipeline.class);
038    
039        public Pipeline(Collection<Processor> processors) {
040            super(processors);
041        }
042    
043        public static Processor newInstance(List<Processor> processors) {
044            if (processors.isEmpty()) {
045                return null;
046            } else if (processors.size() == 1) {
047                return processors.get(0);
048            }
049            return new Pipeline(processors);
050        }
051    
052        public void process(Exchange exchange) throws Exception {
053            Iterator<Processor> processors = getProcessors().iterator();
054            Exchange nextExchange = exchange;
055            boolean first = true;
056    
057            while (continueRouting(processors, nextExchange)) {
058                if (first) {
059                    first = false;
060                } else {
061                    // prepare for next run
062                    nextExchange = createNextExchange(nextExchange);
063                }
064    
065                // get the next processor
066                Processor processor = processors.next();
067    
068                // process the next exchange
069                try {
070                    if (LOG.isTraceEnabled()) {
071                        // this does the actual processing so log at trace level
072                        LOG.trace("Processing exchangeId: " + nextExchange.getExchangeId() + " >>> " + nextExchange);
073                    }
074                    processor.process(nextExchange);
075                } catch (Exception e) {
076                    nextExchange.setException(e);
077                }
078    
079                // check for error if so we should break out
080                boolean exceptionHandled = hasExceptionBeenHandledByErrorHandler(nextExchange);
081                if (nextExchange.isFailed() || nextExchange.isRollbackOnly() ||  exceptionHandled) {
082                    // The Exchange.ERRORHANDLED_HANDLED property is only set if satisfactory handling was done
083                    // by the error handler. It's still an exception, the exchange still failed.
084                    if (LOG.isDebugEnabled()) {
085                        StringBuilder sb = new StringBuilder();
086                        sb.append("Message exchange has failed so breaking out of pipeline: ").append(nextExchange);
087                        if (nextExchange.isRollbackOnly()) {
088                            sb.append(" Marked as rollback only.");
089                        }
090                        if (nextExchange.getException() != null) {
091                            sb.append(" Exception: ").append(nextExchange.getException());
092                        }
093                        if (nextExchange.hasOut() && nextExchange.getOut().isFault()) {
094                            sb.append(" Fault: ").append(nextExchange.getOut());
095                        }
096                        if (exceptionHandled) {
097                            sb.append(" Handled by the error handler.");
098                        }
099                        LOG.debug(sb.toString());
100                    }
101                    break;
102                }
103            }
104    
105            if (LOG.isTraceEnabled()) {
106                // logging nextExchange as it contains the exchange that might have altered the payload and since
107                // we are logging the completion if will be confusing if we log the original instead
108                // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
109                LOG.trace("Processing complete for exchangeId: " + exchange.getExchangeId() + " >>> " + nextExchange);
110            }
111    
112            // copy results back to the original exchange
113            ExchangeHelper.copyResults(exchange, nextExchange);
114        }
115    
116        private static boolean hasExceptionBeenHandledByErrorHandler(Exchange nextExchange) {
117            return Boolean.TRUE.equals(nextExchange.getProperty(Exchange.ERRORHANDLER_HANDLED));
118        }
119    
120        /**
121         * Strategy method to create the next exchange from the previous exchange.
122         * <p/>
123         * Remember to copy the original exchange id otherwise correlation of ids in the log is a problem
124         *
125         * @param previousExchange the previous exchange
126         * @return a new exchange
127         */
128        protected Exchange createNextExchange(Exchange previousExchange) {
129            Exchange answer = new DefaultExchange(previousExchange);
130            // we must use the same id as this is a snapshot strategy where Camel copies a snapshot
131            // before processing the next step in the pipeline, so we have a snapshot of the exchange
132            // just before. This snapshot is used if Camel should do redeliveries (re try) using
133            // DeadLetterChannel. That is why it's important the id is the same, as it is the *same*
134            // exchange being routed.
135            answer.setExchangeId(previousExchange.getExchangeId());
136    
137            answer.getProperties().putAll(previousExchange.getProperties());
138    
139            // now lets set the input of the next exchange to the output of the
140            // previous message if it is not null
141            answer.setIn(previousExchange.hasOut() 
142                ? previousExchange.getOut().copy() : previousExchange.getIn().copy());
143            return answer;
144        }
145    
146        protected boolean continueRouting(Iterator<Processor> it, Exchange exchange) {
147            Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
148            if (stop != null) {
149                boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
150                if (doStop) {
151                    if (LOG.isDebugEnabled()) {
152                        LOG.debug("Exchange is marked to stop routing: " + exchange);
153                    }
154                    return false;
155                }
156            }
157    
158            // continue if there are more processors to route
159            return it.hasNext();
160        }
161    
162        @Override
163        public String toString() {
164            return "Pipeline" + getProcessors();
165        }
166    
167        @Override
168        public String getTraceLabel() {
169            return "pipeline";
170        }
171    }