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