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.concurrent.atomic.AtomicInteger;
020
021import org.apache.camel.AsyncCallback;
022import org.apache.camel.Exchange;
023import org.apache.camel.Expression;
024import org.apache.camel.NoTypeConversionAvailableException;
025import org.apache.camel.Processor;
026import org.apache.camel.Traceable;
027import org.apache.camel.util.ExchangeHelper;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import static org.apache.camel.processor.PipelineHelper.continueProcessing;
032
033/**
034 * The processor which sends messages in a loop.
035 */
036public class LoopProcessor extends DelegateAsyncProcessor implements Traceable {
037    private static final Logger LOG = LoggerFactory.getLogger(LoopProcessor.class);
038
039    private final Expression expression;
040    private final boolean copy;
041
042    public LoopProcessor(Processor processor, Expression expression, boolean copy) {
043        super(processor);
044        this.expression = expression;
045        this.copy = copy;
046    }
047
048    @Override
049    public boolean process(Exchange exchange, AsyncCallback callback) {
050        // use atomic integer to be able to pass reference and keep track on the values
051        AtomicInteger index = new AtomicInteger();
052        AtomicInteger count = new AtomicInteger();
053
054        // Intermediate conversion to String is needed when direct conversion to Integer is not available
055        // but evaluation result is a textual representation of a numeric value.
056        String text = expression.evaluate(exchange, String.class);
057        try {
058            int num = ExchangeHelper.convertToMandatoryType(exchange, Integer.class, text);
059            count.set(num);
060        } catch (NoTypeConversionAvailableException e) {
061            exchange.setException(e);
062            callback.done(true);
063            return true;
064        }
065        
066        // we hold on to the original Exchange in case it's needed for copies
067        final Exchange original = exchange;
068        
069        // per-iteration exchange
070        Exchange target = exchange;
071
072        // set the size before we start
073        exchange.setProperty(Exchange.LOOP_SIZE, count);
074
075        // loop synchronously
076        while (index.get() < count.get()) {
077
078            // and prepare for next iteration
079            // if (!copy) target = exchange; else copy of original
080            target = prepareExchange(exchange, index.get(), original);
081            boolean sync = process(target, callback, index, count, original);
082
083            if (!sync) {
084                LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", target.getExchangeId());
085                // the remainder of the routing slip 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", target.getExchangeId());
091
092            // check for error if so we should break out
093            if (!continueProcessing(target, "so breaking out of loop", LOG)) {
094                break;
095            }
096
097            // increment counter before next loop
098            index.getAndIncrement();
099        }
100
101        // we are done so prepare the result
102        ExchangeHelper.copyResults(exchange, target);
103        LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
104        callback.done(true);
105        return true;
106    }
107
108    protected boolean process(final Exchange exchange, final AsyncCallback callback,
109                              final AtomicInteger index, final AtomicInteger count,
110                              final Exchange original) {
111
112        // set current index as property
113        LOG.debug("LoopProcessor: iteration #{}", index.get());
114        exchange.setProperty(Exchange.LOOP_INDEX, index.get());
115        
116        boolean sync = processor.process(exchange, new AsyncCallback() {
117            public void done(boolean doneSync) {
118                // we only have to handle async completion of the routing slip
119                if (doneSync) {
120                    return;
121                }
122
123                Exchange target = exchange;
124
125                // increment index as we have just processed once
126                index.getAndIncrement();
127
128                // continue looping asynchronously
129                while (index.get() < count.get()) {
130
131                    // and prepare for next iteration
132                    target = prepareExchange(exchange, index.get(), original);
133
134                    // process again
135                    boolean sync = process(target, callback, index, count, original);
136                    if (!sync) {
137                        LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", target.getExchangeId());
138                        // the remainder of the routing slip will be completed async
139                        // so we break out now, then the callback will be invoked which then continue routing from where we left here
140                        return;
141                    }
142
143                    // check for error if so we should break out
144                    if (!continueProcessing(target, "so breaking out of loop", LOG)) {
145                        break;
146                    }
147
148                    // increment counter before next loop
149                    index.getAndIncrement();
150                }
151
152                // we are done so prepare the result
153                ExchangeHelper.copyResults(exchange, target);
154                LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
155                callback.done(false);
156            }
157        });
158
159        return sync;
160    }
161
162    /**
163     * Prepares the exchange for the next iteration
164     *
165     * @param exchange the exchange
166     * @param index the index of the next iteration
167     * @return the exchange to use
168     */
169    protected Exchange prepareExchange(Exchange exchange, int index, Exchange original) {
170        if (copy) {
171            // use a copy but let it reuse the same exchange id so it appear as one exchange
172            // use the original exchange rather than the looping exchange (esp. with the async routing engine)
173            return ExchangeHelper.createCopy(original, true);
174        } else {
175            ExchangeHelper.prepareOutToIn(exchange);
176            return exchange;
177        }
178    }
179
180    public Expression getExpression() {
181        return expression;
182    }
183
184    public String getTraceLabel() {
185        return "loop[" + expression + "]";
186    }
187
188    @Override
189    public String toString() {
190        return "Loop[for: " + expression + " times do: " + getProcessor() + "]";
191    }
192}