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}