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