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.AsyncCallback;
024    import org.apache.camel.AsyncProcessor;
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Message;
027    import org.apache.camel.Processor;
028    import org.apache.camel.impl.converter.AsyncProcessorTypeConverter;
029    import org.apache.camel.util.AsyncProcessorHelper;
030    import org.apache.camel.util.ExchangeHelper;
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    
034    /**
035     * Creates a Pipeline pattern where the output of the previous step is sent as
036     * input to the next step, reusing the same message exchanges
037     *
038     * @version $Revision: 56608 $
039     */
040    public class Pipeline extends MulticastProcessor implements AsyncProcessor {
041        private static final transient Log LOG = LogFactory.getLog(Pipeline.class);
042    
043        public Pipeline(Collection<Processor> processors) {
044            super(processors);
045        }
046    
047        public static Processor newInstance(List<Processor> processors) {
048            if (processors.isEmpty()) {
049                return null;
050            } else if (processors.size() == 1) {
051                return processors.get(0);
052            }
053            return new Pipeline(processors);
054        }
055    
056        public void process(Exchange exchange) throws Exception {
057            AsyncProcessorHelper.process(this, exchange);
058        }
059    
060        public boolean process(Exchange original, AsyncCallback callback) {
061            Iterator<Processor> processors = getProcessors().iterator();
062            Exchange nextExchange = original;
063            boolean first = true;
064            while (true) {
065                boolean exceptionHandled = hasExceptionBeenHandled(nextExchange);
066                if (nextExchange.isFailed() || exceptionHandled) {
067                    // The Exchange.EXCEPTION_HANDLED_PROPERTY property is only set if satisfactory handling was done 
068                    //  by the error handler.  It's still an exception, the exchange still failed.
069                    if (LOG.isDebugEnabled()) {
070                        LOG.debug("Message exchange has failed so breaking out of pipeline: " + nextExchange
071                                  + " exception: " + nextExchange.getException() + " fault: "
072                                  + nextExchange.getFault(false)
073                                  + (exceptionHandled ? " handled by the error handler" : ""));
074                    }
075                    break;
076                }
077                if (!processors.hasNext()) {
078                    break;
079                }
080    
081                AsyncProcessor processor = AsyncProcessorTypeConverter.convert(processors.next());
082    
083                if (first) {
084                    first = false;
085                } else {
086                    nextExchange = createNextExchange(processor, nextExchange);
087                }
088    
089                boolean sync = process(original, nextExchange, callback, processors, processor);
090                // Continue processing the pipeline synchronously ...
091                if (!sync) {
092                    // The pipeline will be completed async...
093                    return false;
094                }
095            }
096    
097            // If we get here then the pipeline was processed entirely
098            // synchronously.
099            if (LOG.isTraceEnabled()) {
100                // logging nextExchange as it contains the exchange that might have altered the payload and since
101                // we are logging the completion if will be confusing if we log the original instead
102                // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
103                LOG.trace("Processing compelete for exchangeId: " + original.getExchangeId() + " >>> " + nextExchange);
104            }
105            ExchangeHelper.copyResults(original, nextExchange);
106            callback.done(true);
107            return true;
108        }
109    
110        private boolean process(final Exchange original, final Exchange exchange, final AsyncCallback callback, final Iterator<Processor> processors, AsyncProcessor processor) {
111            if (LOG.isTraceEnabled()) {
112                // this does the actual processing so log at trace level
113                LOG.trace("Processing exchangeId: " + exchange.getExchangeId() + " >>> " + exchange);
114            }
115            return processor.process(exchange, new AsyncCallback() {
116                public void done(boolean sync) {
117                    // We only have to handle async completion of the pipeline..
118                    if (sync) {
119                        return;
120                    }
121    
122                    // Continue processing the pipeline...
123                    Exchange nextExchange = exchange;
124                    while (processors.hasNext()) {
125                        AsyncProcessor processor = AsyncProcessorTypeConverter.convert(processors.next());
126    
127                        boolean exceptionHandled = hasExceptionBeenHandled(nextExchange);
128                        if (nextExchange.isFailed() || exceptionHandled) {
129                            // The Exchange.EXCEPTION_HANDLED_PROPERTY property is only set if satisfactory handling was done
130                            //  by the error handler.  It's still an exception, the exchange still failed.
131                            if (LOG.isDebugEnabled()) {
132                                LOG.debug("Message exchange has failed so breaking out of pipeline: " + nextExchange
133                                          + " exception: " + nextExchange.getException() + " fault: "
134                                          + nextExchange.getFault(false)
135                                          + (exceptionHandled ? " handled by the error handler" : ""));
136                            }
137                            break;
138                        }
139    
140                        nextExchange = createNextExchange(processor, nextExchange);
141                        sync = process(original, nextExchange, callback, processors, processor);
142                        if (!sync) {
143                            return;
144                        }
145                    }
146    
147                    ExchangeHelper.copyResults(original, nextExchange);
148                    callback.done(false);
149                }
150            });
151        }
152    
153    
154        private static boolean hasExceptionBeenHandled(Exchange nextExchange) {
155            return Boolean.TRUE.equals(nextExchange.getProperty(Exchange.EXCEPTION_HANDLED_PROPERTY));
156        }
157    
158        /**
159         * Strategy method to create the next exchange from the previous exchange.
160         * <p/>
161         * Remember to copy the original exchange id otherwise correlation of ids in the log is a problem
162         *
163         * @param producer         the producer used to send to the endpoint
164         * @param previousExchange the previous exchange
165         * @return a new exchange
166         */
167        protected Exchange createNextExchange(Processor producer, Exchange previousExchange) {
168            Exchange answer = previousExchange.newInstance();
169            // we must use the same id as this is a snapshot strategy where Camel copies a snapshot
170            // before processing the next step in the pipeline, so we have a snapshot of the exchange
171            // just before. This snapshot is used if Camel should do redeliveries (re try) using
172            // DeadLetterChannel. That is why it's important the id is the same, as it is the *same*
173            // exchange being routed.
174            answer.setExchangeId(previousExchange.getExchangeId());
175    
176            answer.getProperties().putAll(previousExchange.getProperties());
177    
178            // now lets set the input of the next exchange to the output of the
179            // previous message if it is not null
180            Message previousOut = previousExchange.getOut(false);
181            Message in = answer.getIn();
182            if (previousOut != null) {
183                in.copyFrom(previousOut);
184            } else {
185                in.copyFrom(previousExchange.getIn());
186            }
187            return answer;
188        }
189    
190        @Override
191        public String toString() {
192            return "Pipeline" + getProcessors();
193        }
194    }