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.model;
018
019import java.util.concurrent.ExecutorService;
020import javax.xml.bind.annotation.XmlAccessType;
021import javax.xml.bind.annotation.XmlAccessorType;
022import javax.xml.bind.annotation.XmlAttribute;
023import javax.xml.bind.annotation.XmlRootElement;
024import javax.xml.bind.annotation.XmlTransient;
025
026import org.apache.camel.CamelContextAware;
027import org.apache.camel.Expression;
028import org.apache.camel.Processor;
029import org.apache.camel.model.language.ExpressionDefinition;
030import org.apache.camel.processor.Splitter;
031import org.apache.camel.processor.aggregate.AggregationStrategy;
032import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter;
033import org.apache.camel.processor.aggregate.ShareUnitOfWorkAggregationStrategy;
034import org.apache.camel.spi.Metadata;
035import org.apache.camel.spi.RouteContext;
036import org.apache.camel.util.CamelContextHelper;
037
038/**
039 * Splits a single message into many sub-messages.
040 *
041 * @version 
042 */
043@Metadata(label = "eip,routing")
044@XmlRootElement(name = "split")
045@XmlAccessorType(XmlAccessType.FIELD)
046public class SplitDefinition extends ExpressionNode implements ExecutorServiceAwareDefinition<SplitDefinition> {
047    @XmlTransient
048    private AggregationStrategy aggregationStrategy;
049    @XmlTransient
050    private ExecutorService executorService;
051    @XmlAttribute
052    private Boolean parallelProcessing;
053    @XmlAttribute
054    private String strategyRef;
055    @XmlAttribute
056    private String strategyMethodName;
057    @XmlAttribute
058    private Boolean strategyMethodAllowNull;
059    @XmlAttribute
060    private String executorServiceRef;
061    @XmlAttribute
062    private Boolean streaming;
063    @XmlAttribute
064    private Boolean stopOnException;
065    @XmlAttribute @Metadata(defaultValue = "0")
066    private Long timeout;
067    @XmlAttribute
068    private String onPrepareRef;
069    @XmlTransient
070    private Processor onPrepare;
071    @XmlAttribute
072    private Boolean shareUnitOfWork;
073    @XmlAttribute
074    private Boolean parallelAggregate;
075    @XmlAttribute
076    private Boolean stopOnAggregateException;
077
078    public SplitDefinition() {
079    }
080
081    public SplitDefinition(Expression expression) {
082        super(expression);
083    }
084
085    public SplitDefinition(ExpressionDefinition expression) {
086        super(expression);
087    }
088
089    @Override
090    public String toString() {
091        return "Split[" + getExpression() + " -> " + getOutputs() + "]";
092    }
093
094    @Override
095    public String getLabel() {
096        return "split[" + getExpression() + "]";
097    }
098
099    @Override
100    public Processor createProcessor(RouteContext routeContext) throws Exception {
101        Processor childProcessor = this.createChildProcessor(routeContext, true);
102        aggregationStrategy = createAggregationStrategy(routeContext);
103
104        boolean isParallelProcessing = getParallelProcessing() != null && getParallelProcessing();
105        boolean isStreaming = getStreaming() != null && getStreaming();
106        boolean isShareUnitOfWork = getShareUnitOfWork() != null && getShareUnitOfWork();
107        boolean isParallelAggregate = getParallelAggregate() != null && getParallelAggregate();
108        boolean isStopOnAggregateException = getStopOnAggregateException() != null && getStopOnAggregateException();
109        boolean shutdownThreadPool = ProcessorDefinitionHelper.willCreateNewThreadPool(routeContext, this, isParallelProcessing);
110        ExecutorService threadPool = ProcessorDefinitionHelper.getConfiguredExecutorService(routeContext, "Split", this, isParallelProcessing);
111
112        long timeout = getTimeout() != null ? getTimeout() : 0;
113        if (timeout > 0 && !isParallelProcessing) {
114            throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled.");
115        }
116        if (onPrepareRef != null) {
117            onPrepare = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), onPrepareRef, Processor.class);
118        }
119
120        Expression exp = getExpression().createExpression(routeContext);
121
122        Splitter answer = new Splitter(routeContext.getCamelContext(), exp, childProcessor, aggregationStrategy,
123                            isParallelProcessing, threadPool, shutdownThreadPool, isStreaming, isStopOnException(),
124                            timeout, onPrepare, isShareUnitOfWork, isParallelAggregate, isStopOnAggregateException);
125        return answer;
126    }
127
128    private AggregationStrategy createAggregationStrategy(RouteContext routeContext) {
129        AggregationStrategy strategy = getAggregationStrategy();
130        if (strategy == null && strategyRef != null) {
131            Object aggStrategy = routeContext.lookup(strategyRef, Object.class);
132            if (aggStrategy instanceof AggregationStrategy) {
133                strategy = (AggregationStrategy) aggStrategy;
134            } else if (aggStrategy != null) {
135                AggregationStrategyBeanAdapter adapter = new AggregationStrategyBeanAdapter(aggStrategy, getStrategyMethodName());
136                if (getStrategyMethodAllowNull() != null) {
137                    adapter.setAllowNullNewExchange(getStrategyMethodAllowNull());
138                    adapter.setAllowNullOldExchange(getStrategyMethodAllowNull());
139                }
140                strategy = adapter;
141            } else {
142                throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + strategyRef);
143            }
144        }
145
146        if (strategy instanceof CamelContextAware) {
147            ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext());
148        }
149
150        if (strategy != null && shareUnitOfWork != null && shareUnitOfWork) {
151            // wrap strategy in share unit of work
152            strategy = new ShareUnitOfWorkAggregationStrategy(strategy);
153        }
154
155        return strategy;
156    }
157
158    // Fluent API
159    // -------------------------------------------------------------------------
160
161    /**
162     * Sets the AggregationStrategy to be used to assemble the replies from the splitted messages, into a single outgoing message from the Splitter.
163     * By default Camel will use the original incoming message to the splitter (leave it unchanged). You can also use a POJO as the AggregationStrategy
164     */
165    public SplitDefinition aggregationStrategy(AggregationStrategy aggregationStrategy) {
166        setAggregationStrategy(aggregationStrategy);
167        return this;
168    }
169
170    /**
171     * Sets a reference to the AggregationStrategy to be used to assemble the replies from the splitted messages, into a single outgoing message from the Splitter.
172     * By default Camel will use the original incoming message to the splitter (leave it unchanged). You can also use a POJO as the AggregationStrategy
173     */
174    public SplitDefinition aggregationStrategyRef(String aggregationStrategyRef) {
175        setStrategyRef(aggregationStrategyRef);
176        return this;
177    }
178
179    /**
180     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
181     *
182     * @param  methodName the method name to call
183     * @return the builder
184     */
185    public SplitDefinition aggregationStrategyMethodName(String methodName) {
186        setStrategyMethodName(methodName);
187        return this;
188    }
189
190    /**
191     * If this option is false then the aggregate method is not used if there was no data to enrich.
192     * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy
193     *
194     * @return the builder
195     */
196    public SplitDefinition aggregationStrategyMethodAllowNull() {
197        setStrategyMethodAllowNull(true);
198        return this;
199    }
200
201    /**
202     * If enabled then processing each splitted messages occurs concurrently.
203     * Note the caller thread will still wait until all messages has been fully processed, before it continues.
204     * Its only processing the sub messages from the splitter which happens concurrently.
205     *
206     * @return the builder
207     */
208    public SplitDefinition parallelProcessing() {
209        setParallelProcessing(true);
210        return this;
211    }
212
213    /**
214     * If enabled then processing each splitted messages occurs concurrently.
215     * Note the caller thread will still wait until all messages has been fully processed, before it continues.
216     * Its only processing the sub messages from the splitter which happens concurrently.
217     *
218     * @return the builder
219     */
220    public SplitDefinition parallelProcessing(boolean parallelProcessing) {
221        setParallelProcessing(parallelProcessing);
222        return this;
223    }
224
225    /**
226     * If enabled then the aggregate method on AggregationStrategy can be called concurrently.
227     * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe.
228     * By default this is false meaning that Camel synchronizes the call to the aggregate method.
229     * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.
230     *
231     * @return the builder
232     */
233    public SplitDefinition parallelAggregate() {
234        setParallelAggregate(true);
235        return this;
236    }
237    
238    /**
239     * If enabled, unwind exceptions occurring at aggregation time to the error handler when parallelProcessing is used.
240     * Currently, aggregation time exceptions do not stop the route processing when parallelProcessing is used.
241     * Enabling this option allows to work around this behavior.
242     *
243     * The default value is <code>false</code> for the sake of backward compatibility.
244     *
245     * @return the builder
246     */
247    public SplitDefinition stopOnAggregateException() {
248        setStopOnAggregateException(true);
249        return this;
250    }
251
252    /**
253     * When in streaming mode, then the splitter splits the original message on-demand, and each splitted
254     * message is processed one by one. This reduces memory usage as the splitter do not split all the messages first,
255     * but then we do not know the total size, and therefore the {@link org.apache.camel.Exchange#SPLIT_SIZE} is empty.
256     * <p/>
257     * In non-streaming mode (default) the splitter will split each message first, to know the total size, and then
258     * process each message one by one. This requires to keep all the splitted messages in memory and therefore requires
259     * more memory. The total size is provided in the {@link org.apache.camel.Exchange#SPLIT_SIZE} header.
260     * <p/>
261     * The streaming mode also affects the aggregation behavior.
262     * If enabled then Camel will process replies out-of-order, eg in the order they come back.
263     * If disabled, Camel will process replies in the same order as the messages was splitted.
264     *
265     * @return the builder
266     */
267    public SplitDefinition streaming() {
268        setStreaming(true);
269        return this;
270    }
271    
272    /**
273     * Will now stop further processing if an exception or failure occurred during processing of an
274     * {@link org.apache.camel.Exchange} and the caused exception will be thrown.
275     * <p/>
276     * Will also stop if processing the exchange failed (has a fault message) or an exception
277     * was thrown and handled by the error handler (such as using onException). In all situations
278     * the splitter will stop further processing. This is the same behavior as in pipeline, which
279     * is used by the routing engine.
280     * <p/>
281     * The default behavior is to <b>not</b> stop but continue processing till the end
282     *
283     * @return the builder
284     */
285    public SplitDefinition stopOnException() {
286        setStopOnException(true);
287        return this;
288    }
289
290    /**
291     * To use a custom Thread Pool to be used for parallel processing.
292     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
293     */
294    public SplitDefinition executorService(ExecutorService executorService) {
295        setExecutorService(executorService);
296        return this;
297    }
298
299    /**
300     * Refers to a custom Thread Pool to be used for parallel processing.
301     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
302     */
303    public SplitDefinition executorServiceRef(String executorServiceRef) {
304        setExecutorServiceRef(executorServiceRef);
305        return this;
306    }
307
308    /**
309     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send.
310     * This can be used to deep-clone messages that should be send, or any custom logic needed before
311     * the exchange is send.
312     *
313     * @param onPrepare the processor
314     * @return the builder
315     */
316    public SplitDefinition onPrepare(Processor onPrepare) {
317        setOnPrepare(onPrepare);
318        return this;
319    }
320
321    /**
322     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send.
323     * This can be used to deep-clone messages that should be send, or any custom logic needed before
324     * the exchange is send.
325     *
326     * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry}
327     * @return the builder
328     */
329    public SplitDefinition onPrepareRef(String onPrepareRef) {
330        setOnPrepareRef(onPrepareRef);
331        return this;
332    }
333
334    /**
335     * Sets a total timeout specified in millis, when using parallel processing.
336     * If the Splitter hasn't been able to split and process all the sub messages within the given timeframe,
337     * then the timeout triggers and the Splitter breaks out and continues.
338     * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out.
339     * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel
340     * to shut down in a graceful manner may continue to run. So use this option with a bit of care.
341     *
342     * @param timeout timeout in millis
343     * @return the builder
344     */
345    public SplitDefinition timeout(long timeout) {
346        setTimeout(timeout);
347        return this;
348    }
349
350    /**
351     * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages.
352     * Splitter will by default not share unit of work between the parent exchange and each splitted exchange.
353     * This means each splitted exchange has its own individual unit of work.
354     *
355     * @return the builder.
356     * @see org.apache.camel.spi.SubUnitOfWork
357     */
358    public SplitDefinition shareUnitOfWork() {
359        setShareUnitOfWork(true);
360        return this;
361    }
362
363    // Properties
364    //-------------------------------------------------------------------------
365
366    /**
367     * Expression of how to split the message body, such as as-is, using a tokenizer, or using an xpath.
368     */
369    @Override
370    public void setExpression(ExpressionDefinition expression) {
371        // override to include javadoc what the expression is used for
372        super.setExpression(expression);
373    }
374
375    public AggregationStrategy getAggregationStrategy() {
376        return aggregationStrategy;
377    }
378
379    /**
380     * Sets the AggregationStrategy to be used to assemble the replies from the splitted messages, into a single outgoing message from the Splitter.
381     * By default Camel will use the original incoming message to the splitter (leave it unchanged). You can also use a POJO as the AggregationStrategy
382     */
383    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
384        this.aggregationStrategy = aggregationStrategy;
385    }
386
387    public Boolean getParallelProcessing() {
388        return parallelProcessing;
389    }
390
391    public void setParallelProcessing(Boolean parallelProcessing) {
392        this.parallelProcessing = parallelProcessing;
393    }
394
395    public Boolean getStreaming() {
396        return streaming;
397    }
398
399    public void setStreaming(Boolean streaming) {
400        this.streaming = streaming;
401    }
402
403    public Boolean getParallelAggregate() {
404        return parallelAggregate;
405    }
406
407    public void setParallelAggregate(Boolean parallelAggregate) {
408        this.parallelAggregate = parallelAggregate;
409    }
410    
411    public Boolean getStopOnAggregateException() {
412        return this.stopOnAggregateException;
413    }
414
415    public void setStopOnAggregateException(Boolean stopOnAggregateException) {
416        this.stopOnAggregateException = stopOnAggregateException;
417    }
418
419    public Boolean getStopOnException() {
420        return stopOnException;
421    }
422
423    public void setStopOnException(Boolean stopOnException) {
424        this.stopOnException = stopOnException;
425    }
426
427    public Boolean isStopOnException() {
428        return stopOnException != null && stopOnException;
429    }
430
431    public ExecutorService getExecutorService() {
432        return executorService;
433    }
434
435    public void setExecutorService(ExecutorService executorService) {
436        this.executorService = executorService;
437    }
438
439    public String getStrategyRef() {
440        return strategyRef;
441    }
442
443    /**
444     * Sets a reference to the AggregationStrategy to be used to assemble the replies from the splitted messages, into a single outgoing message from the Splitter.
445     * By default Camel will use the original incoming message to the splitter (leave it unchanged). You can also use a POJO as the AggregationStrategy
446     */
447    public void setStrategyRef(String strategyRef) {
448        this.strategyRef = strategyRef;
449    }
450
451    public String getStrategyMethodName() {
452        return strategyMethodName;
453    }
454
455    /**
456     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
457     */
458    public void setStrategyMethodName(String strategyMethodName) {
459        this.strategyMethodName = strategyMethodName;
460    }
461
462    public Boolean getStrategyMethodAllowNull() {
463        return strategyMethodAllowNull;
464    }
465
466    /**
467     * If this option is false then the aggregate method is not used if there was no data to enrich.
468     * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy
469     */
470    public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) {
471        this.strategyMethodAllowNull = strategyMethodAllowNull;
472    }
473
474    public String getExecutorServiceRef() {
475        return executorServiceRef;
476    }
477
478    public void setExecutorServiceRef(String executorServiceRef) {
479        this.executorServiceRef = executorServiceRef;
480    }
481
482    public Long getTimeout() {
483        return timeout;
484    }
485
486    public void setTimeout(Long timeout) {
487        this.timeout = timeout;
488    }
489
490    public String getOnPrepareRef() {
491        return onPrepareRef;
492    }
493
494    public void setOnPrepareRef(String onPrepareRef) {
495        this.onPrepareRef = onPrepareRef;
496    }
497
498    public Processor getOnPrepare() {
499        return onPrepare;
500    }
501
502    public void setOnPrepare(Processor onPrepare) {
503        this.onPrepare = onPrepare;
504    }
505
506    public Boolean getShareUnitOfWork() {
507        return shareUnitOfWork;
508    }
509
510    public void setShareUnitOfWork(Boolean shareUnitOfWork) {
511        this.shareUnitOfWork = shareUnitOfWork;
512    }
513
514}