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