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}