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 */
017package org.apache.camel.processor;
018
019import java.util.Collection;
020import java.util.Iterator;
021import java.util.List;
022
023import org.apache.camel.AsyncCallback;
024import org.apache.camel.AsyncProcessor;
025import org.apache.camel.CamelContext;
026import org.apache.camel.Exchange;
027import org.apache.camel.Processor;
028import org.apache.camel.Traceable;
029import org.apache.camel.util.AsyncProcessorConverterHelper;
030import org.apache.camel.util.AsyncProcessorHelper;
031import org.apache.camel.util.ExchangeHelper;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import static org.apache.camel.processor.PipelineHelper.continueProcessing;
036
037/**
038 * Creates a Pipeline pattern where the output of the previous step is sent as
039 * input to the next step, reusing the same message exchanges
040 *
041 * @version 
042 */
043public class Pipeline extends MulticastProcessor implements AsyncProcessor, Traceable {
044    private static final Logger LOG = LoggerFactory.getLogger(Pipeline.class);
045
046    public Pipeline(CamelContext camelContext, Collection<Processor> processors) {
047        super(camelContext, processors);
048    }
049
050    public static Processor newInstance(CamelContext camelContext, List<Processor> processors) {
051        if (processors.isEmpty()) {
052            return null;
053        } else if (processors.size() == 1) {
054            return processors.get(0);
055        }
056        return new Pipeline(camelContext, processors);
057    }
058
059    public void process(Exchange exchange) throws Exception {
060        AsyncProcessorHelper.process(this, exchange);
061    }
062
063    public boolean process(Exchange exchange, AsyncCallback callback) {
064        Iterator<Processor> processors = getProcessors().iterator();
065        Exchange nextExchange = exchange;
066        boolean first = true;
067
068        while (continueRouting(processors, nextExchange)) {
069            if (first) {
070                first = false;
071            } else {
072                // prepare for next run
073                nextExchange = createNextExchange(nextExchange);
074            }
075
076            // get the next processor
077            Processor processor = processors.next();
078
079            AsyncProcessor async = AsyncProcessorConverterHelper.convert(processor);
080            boolean sync = process(exchange, nextExchange, callback, processors, async);
081
082            // continue as long its being processed synchronously
083            if (!sync) {
084                LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
085                // the remainder of the pipeline will be completed async
086                // so we break out now, then the callback will be invoked which then continue routing from where we left here
087                return false;
088            }
089
090            LOG.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
091
092            // check for error if so we should break out
093            if (!continueProcessing(nextExchange, "so breaking out of pipeline", LOG)) {
094                break;
095            }
096        }
097
098        // logging nextExchange as it contains the exchange that might have altered the payload and since
099        // we are logging the completion if will be confusing if we log the original instead
100        // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
101        LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), nextExchange);
102
103        // copy results back to the original exchange
104        ExchangeHelper.copyResults(exchange, nextExchange);
105
106        callback.done(true);
107        return true;
108    }
109
110    private boolean process(final Exchange original, final Exchange exchange, final AsyncCallback callback,
111                            final Iterator<Processor> processors, final AsyncProcessor asyncProcessor) {
112        // this does the actual processing so log at trace level
113        LOG.trace("Processing exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
114
115        // implement asynchronous routing logic in callback so we can have the callback being
116        // triggered and then continue routing where we left
117        //boolean sync = AsyncProcessorHelper.process(asyncProcessor, exchange,
118        boolean sync = asyncProcessor.process(exchange, new AsyncCallback() {
119            public void done(boolean doneSync) {
120                // we only have to handle async completion of the pipeline
121                if (doneSync) {
122                    return;
123                }
124
125                // continue processing the pipeline asynchronously
126                Exchange nextExchange = exchange;
127                while (continueRouting(processors, nextExchange)) {
128                    AsyncProcessor processor = AsyncProcessorConverterHelper.convert(processors.next());
129
130                    // check for error if so we should break out
131                    if (!continueProcessing(nextExchange, "so breaking out of pipeline", LOG)) {
132                        break;
133                    }
134
135                    nextExchange = createNextExchange(nextExchange);
136                    doneSync = process(original, nextExchange, callback, processors, processor);
137                    if (!doneSync) {
138                        LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
139                        return;
140                    }
141                }
142
143                ExchangeHelper.copyResults(original, nextExchange);
144                LOG.trace("Processing complete for exchangeId: {} >>> {}", original.getExchangeId(), original);
145                callback.done(false);
146            }
147        });
148
149        return sync;
150    }
151
152    /**
153     * Strategy method to create the next exchange from the previous exchange.
154     * <p/>
155     * Remember to copy the original exchange id otherwise correlation of ids in the log is a problem
156     *
157     * @param previousExchange the previous exchange
158     * @return a new exchange
159     */
160    protected Exchange createNextExchange(Exchange previousExchange) {
161        return PipelineHelper.createNextExchange(previousExchange);
162    }
163
164    protected boolean continueRouting(Iterator<Processor> it, Exchange exchange) {
165        boolean answer = true;
166
167        Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
168        if (stop != null) {
169            boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
170            if (doStop) {
171                LOG.debug("ExchangeId: {} is marked to stop routing: {}", exchange.getExchangeId(), exchange);
172                answer = false;
173            }
174        } else {
175            // continue if there are more processors to route
176            answer = it.hasNext();
177        }
178
179        LOG.trace("ExchangeId: {} should continue routing: {}", exchange.getExchangeId(), answer);
180        return answer;
181    }
182
183    @Override
184    public String toString() {
185        return "Pipeline[" + getProcessors() + "]";
186    }
187
188    @Override
189    public String getTraceLabel() {
190        return "pipeline";
191    }
192}