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 // create a correlated copy as the new exchange to be routed in the splitter from the copy 180 // and do not share the unit of work 181 Exchange newExchange = ExchangeHelper.createCorrelatedCopy(copy, false); 182 // If the splitter has an aggregation strategy 183 // then the StreamCache created by the child routes must not be 184 // closed by the unit of work of the child route, but by the unit of 185 // work of the parent route or grand parent route or grand grand parent route... (in case of nesting). 186 // Therefore, set the unit of work of the parent route as stream cache unit of work, if not already set. 187 if (newExchange.getProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK) == null) { 188 newExchange.setProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK, original.getUnitOfWork()); 189 } 190 // if we share unit of work, we need to prepare the child exchange 191 if (isShareUnitOfWork()) { 192 prepareSharedUnitOfWork(newExchange, copy); 193 } 194 if (part instanceof Message) { 195 newExchange.setIn((Message) part); 196 } else { 197 Message in = newExchange.getIn(); 198 in.setBody(part); 199 } 200 return createProcessorExchangePair(index++, getProcessors().iterator().next(), newExchange, routeContext); 201 } 202 203 public void remove() { 204 throw new UnsupportedOperationException("Remove is not supported by this iterator"); 205 } 206 }; 207 } 208 209 @Override 210 public void close() throws IOException { 211 if (value instanceof Scanner) { 212 // special for Scanner which implement the Closeable since JDK7 213 Scanner scanner = (Scanner) value; 214 scanner.close(); 215 IOException ioException = scanner.ioException(); 216 if (ioException != null) { 217 throw ioException; 218 } 219 } else if (value instanceof Closeable) { 220 // we should throw out the exception here 221 IOHelper.closeWithException((Closeable) value); 222 } 223 } 224 225 } 226 227 private Iterable<ProcessorExchangePair> createProcessorExchangePairsList(Exchange exchange, Object value) { 228 List<ProcessorExchangePair> result = new ArrayList<ProcessorExchangePair>(); 229 230 // reuse iterable and add it to the result list 231 Iterable<ProcessorExchangePair> pairs = createProcessorExchangePairsIterable(exchange, value); 232 try { 233 for (ProcessorExchangePair pair : pairs) { 234 result.add(pair); 235 } 236 } finally { 237 if (pairs instanceof Closeable) { 238 IOHelper.close((Closeable) pairs, "Splitter:ProcessorExchangePairs"); 239 } 240 } 241 242 return result; 243 } 244 245 @Override 246 protected void updateNewExchange(Exchange exchange, int index, Iterable<ProcessorExchangePair> allPairs, 247 Iterator<ProcessorExchangePair> it) { 248 // do not share unit of work 249 exchange.setUnitOfWork(null); 250 251 exchange.setProperty(Exchange.SPLIT_INDEX, index); 252 if (allPairs instanceof Collection) { 253 // non streaming mode, so we know the total size already 254 exchange.setProperty(Exchange.SPLIT_SIZE, ((Collection<?>) allPairs).size()); 255 } 256 if (it.hasNext()) { 257 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.FALSE); 258 } else { 259 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.TRUE); 260 // streaming mode, so set total size when we are complete based on the index 261 exchange.setProperty(Exchange.SPLIT_SIZE, index + 1); 262 } 263 } 264 265 @Override 266 protected Integer getExchangeIndex(Exchange exchange) { 267 return exchange.getProperty(Exchange.SPLIT_INDEX, Integer.class); 268 } 269 270 public Expression getExpression() { 271 return expression; 272 } 273 274 private static Exchange copyExchangeNoAttachments(Exchange exchange, boolean preserveExchangeId) { 275 Exchange answer = ExchangeHelper.createCopy(exchange, preserveExchangeId); 276 // we do not want attachments for the splitted sub-messages 277 answer.getIn().setAttachments(null); 278 // we do not want to copy the message history for splitted sub-messages 279 answer.getProperties().remove(Exchange.MESSAGE_HISTORY); 280 return answer; 281 } 282}