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.io.Closeable; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Scanner; 027import java.util.concurrent.ExecutorService; 028 029import org.apache.camel.AsyncCallback; 030import org.apache.camel.AsyncProcessor; 031import org.apache.camel.CamelContext; 032import org.apache.camel.Exchange; 033import org.apache.camel.Expression; 034import org.apache.camel.Message; 035import org.apache.camel.Processor; 036import org.apache.camel.RuntimeCamelException; 037import org.apache.camel.Traceable; 038import org.apache.camel.processor.aggregate.AggregationStrategy; 039import org.apache.camel.processor.aggregate.UseOriginalAggregationStrategy; 040import org.apache.camel.spi.RouteContext; 041import org.apache.camel.util.ExchangeHelper; 042import org.apache.camel.util.IOHelper; 043import org.apache.camel.util.ObjectHelper; 044 045import static org.apache.camel.util.ObjectHelper.notNull; 046 047/** 048 * Implements a dynamic <a 049 * href="http://camel.apache.org/splitter.html">Splitter</a> pattern 050 * where an expression is evaluated to iterate through each of the parts of a 051 * message and then each part is then send to some endpoint. 052 * 053 * @version 054 */ 055public class Splitter extends MulticastProcessor implements AsyncProcessor, Traceable { 056 057 private final Expression expression; 058 059 public Splitter(CamelContext camelContext, Expression expression, Processor destination, AggregationStrategy aggregationStrategy) { 060 this(camelContext, expression, destination, aggregationStrategy, false, null, false, false, false, 0, null, false); 061 } 062 063 @Deprecated 064 public Splitter(CamelContext camelContext, Expression expression, Processor destination, AggregationStrategy aggregationStrategy, 065 boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, 066 boolean streaming, boolean stopOnException, long timeout, Processor onPrepare, boolean useSubUnitOfWork) { 067 this(camelContext, expression, destination, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService, 068 streaming, stopOnException, timeout, onPrepare, useSubUnitOfWork, false); 069 } 070 071 public Splitter(CamelContext camelContext, Expression expression, Processor destination, AggregationStrategy aggregationStrategy, 072 boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, 073 boolean streaming, boolean stopOnException, long timeout, Processor onPrepare, boolean useSubUnitOfWork, 074 boolean parallelAggregate) { 075 super(camelContext, Collections.singleton(destination), aggregationStrategy, parallelProcessing, executorService, 076 shutdownExecutorService, streaming, stopOnException, timeout, onPrepare, useSubUnitOfWork, parallelAggregate); 077 this.expression = expression; 078 notNull(expression, "expression"); 079 notNull(destination, "destination"); 080 } 081 082 @Override 083 public String toString() { 084 return "Splitter[on: " + expression + " to: " + getProcessors().iterator().next() + " aggregate: " + getAggregationStrategy() + "]"; 085 } 086 087 @Override 088 public String getTraceLabel() { 089 return "split[" + expression + "]"; 090 } 091 092 @Override 093 public boolean process(Exchange exchange, final AsyncCallback callback) { 094 final AggregationStrategy strategy = getAggregationStrategy(); 095 096 // if no custom aggregation strategy is being used then fallback to keep the original 097 // and propagate exceptions which is done by a per exchange specific aggregation strategy 098 // to ensure it supports async routing 099 if (strategy == null) { 100 UseOriginalAggregationStrategy original = new UseOriginalAggregationStrategy(exchange, true); 101 setAggregationStrategyOnExchange(exchange, original); 102 } 103 104 return super.process(exchange, callback); 105 } 106 107 @Override 108 protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) throws Exception { 109 Object value = expression.evaluate(exchange, Object.class); 110 if (exchange.getException() != null) { 111 // force any exceptions occurred during evaluation to be thrown 112 throw exchange.getException(); 113 } 114 115 Iterable<ProcessorExchangePair> answer; 116 if (isStreaming()) { 117 answer = createProcessorExchangePairsIterable(exchange, value); 118 } else { 119 answer = createProcessorExchangePairsList(exchange, value); 120 } 121 if (exchange.getException() != null) { 122 // force any exceptions occurred during creation of exchange paris to be thrown 123 // before returning the answer; 124 throw exchange.getException(); 125 } 126 127 return answer; 128 } 129 130 private Iterable<ProcessorExchangePair> createProcessorExchangePairsIterable(final Exchange exchange, final Object value) { 131 return new SplitterIterable(exchange, value); 132 } 133 134 private final class SplitterIterable implements Iterable<ProcessorExchangePair>, Closeable { 135 136 // create a copy which we use as master to copy during splitting 137 // this avoids any side effect reflected upon the incoming exchange 138 final Object value; 139 final Iterator<?> iterator; 140 private final Exchange copy; 141 private final RouteContext routeContext; 142 private final Exchange original; 143 144 private SplitterIterable(Exchange exchange, Object value) { 145 this.original = exchange; 146 this.value = value; 147 this.iterator = ObjectHelper.createIterator(value); 148 this.copy = copyExchangeNoAttachments(exchange, true); 149 this.routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null; 150 } 151 152 @Override 153 public Iterator<ProcessorExchangePair> iterator() { 154 return new Iterator<ProcessorExchangePair>() { 155 private int index; 156 private boolean closed; 157 158 public boolean hasNext() { 159 if (closed) { 160 return false; 161 } 162 163 boolean answer = iterator.hasNext(); 164 if (!answer) { 165 // we are now closed 166 closed = true; 167 // nothing more so we need to close the expression value in case it needs to be 168 try { 169 close(); 170 } catch (IOException e) { 171 throw new RuntimeCamelException("Scanner aborted because of an IOException!", e); 172 } 173 } 174 return answer; 175 } 176 177 public ProcessorExchangePair next() { 178 Object part = iterator.next(); 179 if (part != null) { 180 // create a correlated copy as the new exchange to be routed in the splitter from the copy 181 // and do not share the unit of work 182 Exchange newExchange = ExchangeHelper.createCorrelatedCopy(copy, false); 183 // If the splitter has an aggregation strategy 184 // then the StreamCache created by the child routes must not be 185 // closed by the unit of work of the child route, but by the unit of 186 // work of the parent route or grand parent route or grand grand parent route... (in case of nesting). 187 // Therefore, set the unit of work of the parent route as stream cache unit of work, if not already set. 188 if (newExchange.getProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK) == null) { 189 newExchange.setProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK, original.getUnitOfWork()); 190 } 191 // if we share unit of work, we need to prepare the child exchange 192 if (isShareUnitOfWork()) { 193 prepareSharedUnitOfWork(newExchange, copy); 194 } 195 if (part instanceof Message) { 196 newExchange.setIn((Message) part); 197 } else { 198 Message in = newExchange.getIn(); 199 in.setBody(part); 200 } 201 return createProcessorExchangePair(index++, getProcessors().iterator().next(), newExchange, routeContext); 202 } else { 203 return null; 204 } 205 } 206 207 public void remove() { 208 throw new UnsupportedOperationException("Remove is not supported by this iterator"); 209 } 210 }; 211 } 212 213 @Override 214 public void close() throws IOException { 215 if (value instanceof Scanner) { 216 // special for Scanner which implement the Closeable since JDK7 217 Scanner scanner = (Scanner) value; 218 scanner.close(); 219 IOException ioException = scanner.ioException(); 220 if (ioException != null) { 221 throw ioException; 222 } 223 } else if (value instanceof Closeable) { 224 // we should throw out the exception here 225 IOHelper.closeWithException((Closeable) value); 226 } 227 } 228 229 } 230 231 private Iterable<ProcessorExchangePair> createProcessorExchangePairsList(Exchange exchange, Object value) { 232 List<ProcessorExchangePair> result = new ArrayList<ProcessorExchangePair>(); 233 234 // reuse iterable and add it to the result list 235 Iterable<ProcessorExchangePair> pairs = createProcessorExchangePairsIterable(exchange, value); 236 try { 237 for (ProcessorExchangePair pair : pairs) { 238 if (pair != null) { 239 result.add(pair); 240 } 241 } 242 } finally { 243 if (pairs instanceof Closeable) { 244 IOHelper.close((Closeable) pairs, "Splitter:ProcessorExchangePairs"); 245 } 246 } 247 248 return result; 249 } 250 251 @Override 252 protected void updateNewExchange(Exchange exchange, int index, Iterable<ProcessorExchangePair> allPairs, 253 Iterator<ProcessorExchangePair> it) { 254 // do not share unit of work 255 exchange.setUnitOfWork(null); 256 257 exchange.setProperty(Exchange.SPLIT_INDEX, index); 258 if (allPairs instanceof Collection) { 259 // non streaming mode, so we know the total size already 260 exchange.setProperty(Exchange.SPLIT_SIZE, ((Collection<?>) allPairs).size()); 261 } 262 if (it.hasNext()) { 263 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.FALSE); 264 } else { 265 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.TRUE); 266 // streaming mode, so set total size when we are complete based on the index 267 exchange.setProperty(Exchange.SPLIT_SIZE, index + 1); 268 } 269 } 270 271 @Override 272 protected Integer getExchangeIndex(Exchange exchange) { 273 return exchange.getProperty(Exchange.SPLIT_INDEX, Integer.class); 274 } 275 276 public Expression getExpression() { 277 return expression; 278 } 279 280 private static Exchange copyExchangeNoAttachments(Exchange exchange, boolean preserveExchangeId) { 281 Exchange answer = ExchangeHelper.createCopy(exchange, preserveExchangeId); 282 // we do not want attachments for the splitted sub-messages 283 answer.getIn().setAttachments(null); 284 // we do not want to copy the message history for splitted sub-messages 285 answer.getProperties().remove(Exchange.MESSAGE_HISTORY); 286 return answer; 287 } 288}