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