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.concurrent.ExecutorService; 027 028import org.apache.camel.AsyncCallback; 029import org.apache.camel.AsyncProcessor; 030import org.apache.camel.CamelContext; 031import org.apache.camel.Exchange; 032import org.apache.camel.Expression; 033import org.apache.camel.Message; 034import org.apache.camel.Processor; 035import org.apache.camel.RuntimeCamelException; 036import org.apache.camel.Traceable; 037import org.apache.camel.processor.aggregate.AggregationStrategy; 038import org.apache.camel.processor.aggregate.DelegateAggregationStrategy; 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 AggregationStrategy strategy = getAggregationStrategy(); 102 103 if (strategy instanceof DelegateAggregationStrategy) { 104 strategy = ((DelegateAggregationStrategy) strategy).getDelegate(); 105 } 106 107 // set original exchange if not already pre-configured 108 if (strategy instanceof UseOriginalAggregationStrategy) { 109 // need to create a new private instance, as we can also have concurrency issue so we cannot store state 110 UseOriginalAggregationStrategy original = (UseOriginalAggregationStrategy) strategy; 111 AggregationStrategy clone = original.newInstance(exchange); 112 if (isShareUnitOfWork()) { 113 clone = new ShareUnitOfWorkAggregationStrategy(clone); 114 } 115 setAggregationStrategyOnExchange(exchange, clone); 116 } 117 118 // if no custom aggregation strategy is being used then fallback to keep the original 119 // and propagate exceptions which is done by a per exchange specific aggregation strategy 120 // to ensure it supports async routing 121 if (strategy == null) { 122 AggregationStrategy original = new UseOriginalAggregationStrategy(exchange, true); 123 if (isShareUnitOfWork()) { 124 original = new ShareUnitOfWorkAggregationStrategy(original); 125 } 126 setAggregationStrategyOnExchange(exchange, original); 127 } 128 129 return super.process(exchange, callback); 130 } 131 132 @Override 133 protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) throws Exception { 134 Object value = expression.evaluate(exchange, Object.class); 135 if (exchange.getException() != null) { 136 // force any exceptions occurred during evaluation to be thrown 137 throw exchange.getException(); 138 } 139 140 Iterable<ProcessorExchangePair> answer; 141 if (isStreaming()) { 142 answer = createProcessorExchangePairsIterable(exchange, value); 143 } else { 144 answer = createProcessorExchangePairsList(exchange, value); 145 } 146 if (exchange.getException() != null) { 147 // force any exceptions occurred during creation of exchange paris to be thrown 148 // before returning the answer; 149 throw exchange.getException(); 150 } 151 152 return answer; 153 } 154 155 private Iterable<ProcessorExchangePair> createProcessorExchangePairsIterable(final Exchange exchange, final Object value) { 156 return new SplitterIterable(exchange, value); 157 } 158 159 private final class SplitterIterable implements Iterable<ProcessorExchangePair>, Closeable { 160 161 // create a copy which we use as master to copy during splitting 162 // this avoids any side effect reflected upon the incoming exchange 163 final Object value; 164 final Iterator<?> iterator; 165 private final Exchange copy; 166 private final RouteContext routeContext; 167 private final Exchange original; 168 169 private SplitterIterable(Exchange exchange, Object value) { 170 this.original = exchange; 171 this.value = value; 172 this.iterator = ObjectHelper.createIterator(value); 173 this.copy = copyExchangeNoAttachments(exchange, true); 174 this.routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null; 175 } 176 177 @Override 178 public Iterator<ProcessorExchangePair> iterator() { 179 return new Iterator<ProcessorExchangePair>() { 180 private int index; 181 private boolean closed; 182 183 public boolean hasNext() { 184 if (closed) { 185 return false; 186 } 187 188 boolean answer = iterator.hasNext(); 189 if (!answer) { 190 // we are now closed 191 closed = true; 192 // nothing more so we need to close the expression value in case it needs to be 193 try { 194 close(); 195 } catch (IOException e) { 196 throw new RuntimeCamelException("Scanner aborted because of an IOException!", e); 197 } 198 } 199 return answer; 200 } 201 202 public ProcessorExchangePair next() { 203 Object part = iterator.next(); 204 if (part != null) { 205 // create a correlated copy as the new exchange to be routed in the splitter from the copy 206 // and do not share the unit of work 207 Exchange newExchange = ExchangeHelper.createCorrelatedCopy(copy, false); 208 // If the splitter has an aggregation strategy 209 // then the StreamCache created by the child routes must not be 210 // closed by the unit of work of the child route, but by the unit of 211 // work of the parent route or grand parent route or grand grand parent route... (in case of nesting). 212 // Therefore, set the unit of work of the parent route as stream cache unit of work, if not already set. 213 if (newExchange.getProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK) == null) { 214 newExchange.setProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK, original.getUnitOfWork()); 215 } 216 // if we share unit of work, we need to prepare the child exchange 217 if (isShareUnitOfWork()) { 218 prepareSharedUnitOfWork(newExchange, copy); 219 } 220 if (part instanceof Message) { 221 newExchange.setIn((Message) part); 222 } else { 223 Message in = newExchange.getIn(); 224 in.setBody(part); 225 } 226 return createProcessorExchangePair(index++, getProcessors().iterator().next(), newExchange, routeContext); 227 } else { 228 return null; 229 } 230 } 231 232 public void remove() { 233 throw new UnsupportedOperationException("Remove is not supported by this iterator"); 234 } 235 }; 236 } 237 238 @Override 239 public void close() throws IOException { 240 IOHelper.closeIterator(value); 241 } 242 243 } 244 245 private Iterable<ProcessorExchangePair> createProcessorExchangePairsList(Exchange exchange, Object value) { 246 List<ProcessorExchangePair> result = new ArrayList<>(); 247 248 // reuse iterable and add it to the result list 249 Iterable<ProcessorExchangePair> pairs = createProcessorExchangePairsIterable(exchange, value); 250 try { 251 for (ProcessorExchangePair pair : pairs) { 252 if (pair != null) { 253 result.add(pair); 254 } 255 } 256 } finally { 257 if (pairs instanceof Closeable) { 258 IOHelper.close((Closeable) pairs, "Splitter:ProcessorExchangePairs"); 259 } 260 } 261 262 return result; 263 } 264 265 @Override 266 protected void updateNewExchange(Exchange exchange, int index, Iterable<ProcessorExchangePair> allPairs, 267 Iterator<ProcessorExchangePair> it) { 268 // do not share unit of work 269 exchange.setUnitOfWork(null); 270 271 exchange.setProperty(Exchange.SPLIT_INDEX, index); 272 if (allPairs instanceof Collection) { 273 // non streaming mode, so we know the total size already 274 exchange.setProperty(Exchange.SPLIT_SIZE, ((Collection<?>) allPairs).size()); 275 } 276 if (it.hasNext()) { 277 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.FALSE); 278 } else { 279 exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.TRUE); 280 // streaming mode, so set total size when we are complete based on the index 281 exchange.setProperty(Exchange.SPLIT_SIZE, index + 1); 282 } 283 } 284 285 @Override 286 protected Integer getExchangeIndex(Exchange exchange) { 287 return exchange.getProperty(Exchange.SPLIT_INDEX, Integer.class); 288 } 289 290 public Expression getExpression() { 291 return expression; 292 } 293 294 private static Exchange copyExchangeNoAttachments(Exchange exchange, boolean preserveExchangeId) { 295 Exchange answer = ExchangeHelper.createCopy(exchange, preserveExchangeId); 296 // we do not want attachments for the splitted sub-messages 297 answer.getIn().setAttachmentObjects(null); 298 // we do not want to copy the message history for splitted sub-messages 299 answer.getProperties().remove(Exchange.MESSAGE_HISTORY); 300 return answer; 301 } 302}