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