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