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}