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}