/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.processor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.Traceable;
import org.apache.camel.spi.IdAware;
import org.apache.camel.util.ExchangeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.processor.PipelineHelper.continueProcessing;
/**
* The processor which sends messages in a loop.
*/
public class LoopProcessor extends DelegateAsyncProcessor implements Traceable, IdAware {
private static final Logger LOG = LoggerFactory.getLogger(LoopProcessor.class);
private String id;
private final Expression expression;
private final Predicate predicate;
private final boolean copy;
public LoopProcessor(Processor processor, Expression expression, Predicate predicate, boolean copy) {
super(processor);
this.expression = expression;
this.predicate = predicate;
this.copy = copy;
}
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
// use atomic integer to be able to pass reference and keep track on the values
AtomicInteger index = new AtomicInteger();
AtomicInteger count = new AtomicInteger();
AtomicBoolean doWhile = new AtomicBoolean();
try {
if (expression != null) {
// Intermediate conversion to String is needed when direct conversion to Integer is not available
// but evaluation result is a textual representation of a numeric value.
String text = expression.evaluate(exchange, String.class);
int num = ExchangeHelper.convertToMandatoryType(exchange, Integer.class, text);
count.set(num);
} else {
boolean result = predicate.matches(exchange);
doWhile.set(result);
}
} catch (Exception e) {
exchange.setException(e);
callback.done(true);
return true;
}
// we hold on to the original Exchange in case it's needed for copies
final Exchange original = exchange;
// per-iteration exchange
Exchange target = exchange;
// set the size before we start
if (predicate == null) {
exchange.setProperty(Exchange.LOOP_SIZE, count);
}
// loop synchronously
while ((predicate != null && doWhile.get()) || (index.get() < count.get())) {
// and prepare for next iteration
// if (!copy) target = exchange; else copy of original
target = prepareExchange(exchange, index.get(), original);
// the following process method will in the done method re-evaluate the predicate
// so we do not need to do it here as well
boolean sync = process(target, callback, index, count, doWhile, original);
if (!sync) {
LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", target.getExchangeId());
// the remainder of the loop will be completed async
// so we break out now, then the callback will be invoked which then continue routing from where we left here
return false;
}
LOG.trace("Processing exchangeId: {} is continued being processed synchronously", target.getExchangeId());
// check for error if so we should break out
if (!continueProcessing(target, "so breaking out of loop", LOG)) {
break;
}
}
// we are done so prepare the result
ExchangeHelper.copyResults(exchange, target);
LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
callback.done(true);
return true;
}
protected boolean process(final Exchange exchange, final AsyncCallback callback,
final AtomicInteger index, final AtomicInteger count, final AtomicBoolean doWhile,
final Exchange original) {
// set current index as property
LOG.debug("LoopProcessor: iteration #{}", index.get());
exchange.setProperty(Exchange.LOOP_INDEX, index.get());
boolean sync = processor.process(exchange, new AsyncCallback() {
public void done(boolean doneSync) {
// increment counter after done
index.getAndIncrement();
// evaluate predicate for next loop
if (predicate != null && index.get() > 0) {
try {
boolean result = predicate.matches(exchange);
doWhile.set(result);
} catch (Exception e) {
// break out looping due that exception
exchange.setException(e);
doWhile.set(false);
}
}
// we only have to handle async completion of the loop
// (as the sync is done in the outer processor)
if (doneSync) {
return;
}
Exchange target = exchange;
// continue looping asynchronously
while ((predicate != null && doWhile.get()) || (index.get() < count.get())) {
// check for error if so we should break out
if (!continueProcessing(target, "so breaking out of loop", LOG)) {
break;
}
// and prepare for next iteration
target = prepareExchange(exchange, index.get(), original);
// process again
boolean sync = process(target, callback, index, count, doWhile, original);
if (!sync) {
LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", target.getExchangeId());
// the remainder of the routing slip will be completed async
// so we break out now, then the callback will be invoked which then continue routing from where we left here
return;
}
}
// we are done so prepare the result
ExchangeHelper.copyResults(exchange, target);
LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
callback.done(false);
}
});
return sync;
}
/**
* Prepares the exchange for the next iteration
*
* @param exchange the exchange
* @param index the index of the next iteration
* @return the exchange to use
*/
protected Exchange prepareExchange(Exchange exchange, int index, Exchange original) {
if (copy) {
// use a copy but let it reuse the same exchange id so it appear as one exchange
// use the original exchange rather than the looping exchange (esp. with the async routing engine)
return ExchangeHelper.createCopy(original, true);
} else {
ExchangeHelper.prepareOutToIn(exchange);
return exchange;
}
}
public Expression getExpression() {
return expression;
}
public Predicate getPredicate() {
return predicate;
}
public boolean isCopy() {
return copy;
}
public String getTraceLabel() {
if (predicate != null) {
return "loopWhile[" + predicate + "]";
} else {
return "loop[" + expression + "]";
}
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
if (predicate != null) {
return "Loop[while: " + predicate + " do: " + getProcessor() + "]";
} else {
return "Loop[for: " + expression + " times do: " + getProcessor() + "]";
}
}
}