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}