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