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     */
017    package org.apache.camel.processor;
018    
019    import java.io.Closeable;
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Scanner;
026    import java.util.concurrent.ExecutorService;
027    
028    import org.apache.camel.AsyncCallback;
029    import org.apache.camel.AsyncProcessor;
030    import org.apache.camel.CamelContext;
031    import org.apache.camel.Exchange;
032    import org.apache.camel.Expression;
033    import org.apache.camel.Message;
034    import org.apache.camel.Processor;
035    import org.apache.camel.Traceable;
036    import org.apache.camel.processor.aggregate.AggregationStrategy;
037    import org.apache.camel.processor.aggregate.UseOriginalAggregationStrategy;
038    import org.apache.camel.spi.RouteContext;
039    import org.apache.camel.util.ExchangeHelper;
040    import org.apache.camel.util.IOHelper;
041    import org.apache.camel.util.ObjectHelper;
042    import org.slf4j.Logger;
043    import org.slf4j.LoggerFactory;
044    
045    import 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     */
055    public class Splitter extends MulticastProcessor implements AsyncProcessor, Traceable {
056        private static final transient Logger LOG = LoggerFactory.getLogger(Splitter.class);
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        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            super(camelContext, Collections.singleton(destination), aggregationStrategy, parallelProcessing, executorService,
068                    shutdownExecutorService, streaming, stopOnException, timeout, onPrepare, useSubUnitOfWork);
069            this.expression = expression;
070            notNull(expression, "expression");
071            notNull(destination, "destination");
072        }
073    
074        @Override
075        public String toString() {
076            return "Splitter[on: " + expression + " to: " + getProcessors().iterator().next() + " aggregate: " + getAggregationStrategy() + "]";
077        }
078    
079        @Override
080        public String getTraceLabel() {
081            return "split[" + expression + "]";
082        }
083    
084        @Override
085        public boolean process(Exchange exchange, final AsyncCallback callback) {
086            final AggregationStrategy strategy = getAggregationStrategy();
087    
088            // if no custom aggregation strategy is being used then fallback to keep the original
089            // and propagate exceptions which is done by a per exchange specific aggregation strategy
090            // to ensure it supports async routing
091            if (strategy == null) {
092                UseOriginalAggregationStrategy original = new UseOriginalAggregationStrategy(exchange, true);
093                setAggregationStrategyOnExchange(exchange, original);
094            }
095    
096            return super.process(exchange, callback);
097        }
098    
099        @Override
100        protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) throws Exception {
101            Object value = expression.evaluate(exchange, Object.class);
102            if (exchange.getException() != null) {
103                // force any exceptions occurred during evaluation to be thrown
104                throw exchange.getException();
105            }
106    
107            Iterable<ProcessorExchangePair> answer;
108            if (isStreaming()) {
109                answer = createProcessorExchangePairsIterable(exchange, value);
110            } else {
111                answer = createProcessorExchangePairsList(exchange, value);
112            }
113            if (exchange.getException() != null) {
114                // force any exceptions occurred during creation of exchange paris to be thrown
115                // before returning the answer;
116                throw exchange.getException();
117            }
118    
119            return answer;
120        }
121    
122        private Iterable<ProcessorExchangePair> createProcessorExchangePairsIterable(final Exchange exchange, final Object value) {
123            final Iterator<?> iterator = ObjectHelper.createIterator(value);
124            return new Iterable<ProcessorExchangePair>() {
125                // create a copy which we use as master to copy during splitting
126                // this avoids any side effect reflected upon the incoming exchange
127                private final Exchange copy = copyExchangeNoAttachments(exchange, true);
128                private final RouteContext routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null;
129    
130                public Iterator<ProcessorExchangePair> iterator() {
131                    return new Iterator<ProcessorExchangePair>() {
132                        private int index;
133                        private boolean closed;
134    
135                        public boolean hasNext() {
136                            if (closed) {
137                                return false;
138                            }
139    
140                            boolean answer = iterator.hasNext();
141                            if (!answer) {
142                                // we are now closed
143                                closed = true;
144                                // nothing more so we need to close the expression value in case it needs to be
145                                if (value instanceof Closeable) {
146                                    IOHelper.close((Closeable) value, value.getClass().getName(), LOG);
147                                } else if (value instanceof Scanner) {
148                                    // special for Scanner as it does not implement Closeable
149                                    ((Scanner) value).close();
150                                }
151                            }
152                            return answer;
153                        }
154    
155                        public ProcessorExchangePair next() {
156                            Object part = iterator.next();
157                            // create a correlated copy as the new exchange to be routed in the splitter from the copy
158                            // and do not share the unit of work
159                            Exchange newExchange = ExchangeHelper.createCorrelatedCopy(copy, false);
160                            // if we share unit of work, we need to prepare the child exchange
161                            if (isShareUnitOfWork()) {
162                                prepareSharedUnitOfWork(newExchange, copy);
163                            }
164                            if (part instanceof Message) {
165                                newExchange.setIn((Message) part);
166                            } else {
167                                Message in = newExchange.getIn();
168                                in.setBody(part);
169                            }
170                            return createProcessorExchangePair(index++, getProcessors().iterator().next(), newExchange, routeContext);
171                        }
172    
173                        public void remove() {
174                            throw new UnsupportedOperationException("Remove is not supported by this iterator");
175                        }
176                    };
177                }
178    
179            };
180        }
181    
182        private Iterable<ProcessorExchangePair> createProcessorExchangePairsList(Exchange exchange, Object value) {
183            List<ProcessorExchangePair> result = new ArrayList<ProcessorExchangePair>();
184    
185            // reuse iterable and add it to the result list
186            Iterable<ProcessorExchangePair> pairs = createProcessorExchangePairsIterable(exchange, value);
187            for (ProcessorExchangePair pair : pairs) {
188                result.add(pair);
189            }
190    
191            return result;
192        }
193    
194        @Override
195        protected void updateNewExchange(Exchange exchange, int index, Iterable<ProcessorExchangePair> allPairs,
196                                         Iterator<ProcessorExchangePair> it) {
197            // do not share unit of work
198            exchange.setUnitOfWork(null);
199    
200            exchange.setProperty(Exchange.SPLIT_INDEX, index);
201            if (allPairs instanceof Collection) {
202                // non streaming mode, so we know the total size already
203                exchange.setProperty(Exchange.SPLIT_SIZE, ((Collection<?>) allPairs).size());
204            }
205            if (it.hasNext()) {
206                exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.FALSE);
207            } else {
208                exchange.setProperty(Exchange.SPLIT_COMPLETE, Boolean.TRUE);
209                // streaming mode, so set total size when we are complete based on the index
210                exchange.setProperty(Exchange.SPLIT_SIZE, index + 1);
211            }
212        }
213    
214        @Override
215        protected Integer getExchangeIndex(Exchange exchange) {
216            return exchange.getProperty(Exchange.SPLIT_INDEX, Integer.class);
217        }
218    
219        public Expression getExpression() {
220            return expression;
221        }
222        
223        private static Exchange copyExchangeNoAttachments(Exchange exchange, boolean preserveExchangeId) {
224            Exchange answer = ExchangeHelper.createCopy(exchange, preserveExchangeId);
225            // we do not want attachments for the splitted sub-messages
226            answer.getIn().setAttachments(null);
227            return answer;
228        }
229    }