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