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 }