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