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