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