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.Expression;
030import org.apache.camel.Processor;
031import org.apache.camel.model.language.ExpressionDefinition;
032import org.apache.camel.processor.EvaluateExpressionProcessor;
033import org.apache.camel.processor.Pipeline;
034import org.apache.camel.processor.RecipientList;
035import org.apache.camel.processor.aggregate.AggregationStrategy;
036import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter;
037import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy;
038import org.apache.camel.spi.Metadata;
039import org.apache.camel.spi.RouteContext;
040import org.apache.camel.util.CamelContextHelper;
041
042/**
043 * Routes messages to a number of dynamically specified recipients (dynamic to)
044 *
045 * @version 
046 */
047@Metadata(label = "eip,endpoint,routing")
048@XmlRootElement(name = "recipientList")
049@XmlAccessorType(XmlAccessType.FIELD)
050public class RecipientListDefinition<Type extends ProcessorDefinition<Type>> extends NoOutputExpressionNode implements ExecutorServiceAwareDefinition<RecipientListDefinition<Type>> {
051    @XmlTransient
052    private AggregationStrategy aggregationStrategy;
053    @XmlTransient
054    private ExecutorService executorService;
055    @XmlAttribute @Metadata(defaultValue = ",")
056    private String delimiter;
057    @XmlAttribute
058    private Boolean parallelProcessing;
059    @XmlAttribute
060    private String strategyRef;
061    @XmlAttribute
062    private String strategyMethodName;
063    @XmlAttribute
064    private Boolean strategyMethodAllowNull;
065    @XmlAttribute
066    private String executorServiceRef;
067    @XmlAttribute
068    private Boolean stopOnException;
069    @XmlAttribute
070    private Boolean ignoreInvalidEndpoints;
071    @XmlAttribute
072    private Boolean streaming;
073    @XmlAttribute @Metadata(defaultValue = "0")
074    private Long timeout;
075    @XmlAttribute
076    private String onPrepareRef;
077    @XmlTransient
078    private Processor onPrepare;
079    @XmlAttribute
080    private Boolean shareUnitOfWork;
081    @XmlAttribute
082    private Integer cacheSize;
083    @XmlAttribute
084    private Boolean parallelAggregate;
085
086    public RecipientListDefinition() {
087    }
088
089    public RecipientListDefinition(ExpressionDefinition expression) {
090        super(expression);
091    }
092
093    public RecipientListDefinition(Expression expression) {
094        super(expression);
095    }
096
097    @Override
098    public String toString() {
099        return "RecipientList[" + getExpression() + "]";
100    }
101
102    @Override
103    public String getLabel() {
104        return "recipientList[" + getExpression() + "]";
105    }
106
107    @Override
108    public Processor createProcessor(RouteContext routeContext) throws Exception {
109        final Expression expression = getExpression().createExpression(routeContext);
110
111        boolean isParallelProcessing = getParallelProcessing() != null && getParallelProcessing();
112        boolean isStreaming = getStreaming() != null && getStreaming();
113        boolean isParallelAggregate = getParallelAggregate() != null && getParallelAggregate();
114        boolean isShareUnitOfWork = getShareUnitOfWork() != null && getShareUnitOfWork();
115        boolean isStopOnException = getStopOnException() != null && getStopOnException();
116        boolean isIgnoreInvalidEndpoints = getIgnoreInvalidEndpoints() != null && getIgnoreInvalidEndpoints();
117
118        RecipientList answer;
119        if (delimiter != null) {
120            answer = new RecipientList(routeContext.getCamelContext(), expression, delimiter);
121        } else {
122            answer = new RecipientList(routeContext.getCamelContext(), expression);
123        }
124        answer.setAggregationStrategy(createAggregationStrategy(routeContext));
125        answer.setParallelProcessing(isParallelProcessing);
126        answer.setParallelAggregate(isParallelAggregate);
127        answer.setStreaming(isStreaming);
128        answer.setShareUnitOfWork(isShareUnitOfWork);
129        answer.setStopOnException(isStopOnException);
130        answer.setIgnoreInvalidEndpoints(isIgnoreInvalidEndpoints);
131        if (getCacheSize() != null) {
132            answer.setCacheSize(getCacheSize());
133        }
134        if (onPrepareRef != null) {
135            onPrepare = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), onPrepareRef, Processor.class);
136        }
137        if (onPrepare != null) {
138            answer.setOnPrepare(onPrepare);
139        }
140        if (getTimeout() != null) {
141            answer.setTimeout(getTimeout());
142        }
143
144        boolean shutdownThreadPool = ProcessorDefinitionHelper.willCreateNewThreadPool(routeContext, this, isParallelProcessing);
145        ExecutorService threadPool = ProcessorDefinitionHelper.getConfiguredExecutorService(routeContext, "RecipientList", this, isParallelProcessing);
146        answer.setExecutorService(threadPool);
147        answer.setShutdownExecutorService(shutdownThreadPool);
148        long timeout = getTimeout() != null ? getTimeout() : 0;
149        if (timeout > 0 && !isParallelProcessing) {
150            throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled.");
151        }
152
153        // create a pipeline with two processors
154        // the first is the eval processor which evaluates the expression to use
155        // the second is the recipient list
156        List<Processor> pipe = new ArrayList<Processor>(2);
157
158        // the eval processor must be wrapped in error handler, so in case there was an
159        // error during evaluation, the error handler can deal with it
160        // the recipient list is not in error handler, as its has its own special error handling
161        // when sending to the recipients individually
162        Processor evalProcessor = new EvaluateExpressionProcessor(expression);
163        evalProcessor = super.wrapInErrorHandler(routeContext, evalProcessor);
164
165        pipe.add(evalProcessor);
166        pipe.add(answer);
167
168        // wrap in nested pipeline so this appears as one processor
169        // (threads definition does this as well)
170        return new Pipeline(routeContext.getCamelContext(), pipe) {
171            @Override
172            public String toString() {
173                return "RecipientList[" + expression + "]";
174            }
175        };
176    }
177
178    private AggregationStrategy createAggregationStrategy(RouteContext routeContext) {
179        AggregationStrategy strategy = getAggregationStrategy();
180        if (strategy == null && strategyRef != null) {
181            Object aggStrategy = routeContext.lookup(strategyRef, Object.class);
182            if (aggStrategy instanceof AggregationStrategy) {
183                strategy = (AggregationStrategy) aggStrategy;
184            } else if (aggStrategy != null) {
185                AggregationStrategyBeanAdapter adapter = new AggregationStrategyBeanAdapter(aggStrategy, getStrategyMethodName());
186                if (getStrategyMethodAllowNull() != null) {
187                    adapter.setAllowNullNewExchange(getStrategyMethodAllowNull());
188                    adapter.setAllowNullOldExchange(getStrategyMethodAllowNull());
189                }
190                strategy = adapter;
191            } else {
192                throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + strategyRef);
193            }
194        }
195        if (strategy == null) {
196            // fallback to use latest
197            strategy = new UseLatestAggregationStrategy();
198        }
199
200        if (strategy instanceof CamelContextAware) {
201            ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext());
202        }
203
204        return strategy;
205    }
206
207    // Fluent API
208    // -------------------------------------------------------------------------
209
210    @Override
211    @SuppressWarnings("unchecked")
212    public Type end() {
213        // allow end() to return to previous type so you can continue in the DSL
214        return (Type) super.end();
215    }
216
217    /**
218     * Delimiter used if the Expression returned multiple endpoints. Can be turned off using the value <tt>false</tt>.
219     * <p/>
220     * The default value is ,
221     *
222     * @param delimiter the delimiter
223     * @return the builder
224     */
225    public RecipientListDefinition<Type> delimiter(String delimiter) {
226        setDelimiter(delimiter);
227        return this;
228    }
229
230    /**
231     * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
232     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
233     */
234    public RecipientListDefinition<Type> aggregationStrategy(AggregationStrategy aggregationStrategy) {
235        setAggregationStrategy(aggregationStrategy);
236        return this;
237    }
238
239    /**
240     * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
241     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
242     */
243    public RecipientListDefinition<Type> aggregationStrategyRef(String aggregationStrategyRef) {
244        setStrategyRef(aggregationStrategyRef);
245        return this;
246    }
247
248    /**
249     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
250     *
251     * @param  methodName the method name to call
252     * @return the builder
253     */
254    public RecipientListDefinition<Type> aggregationStrategyMethodName(String methodName) {
255        setStrategyMethodName(methodName);
256        return this;
257    }
258
259    /**
260     * If this option is false then the aggregate method is not used if there was no data to enrich.
261     * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy
262     *
263     * @return the builder
264     */
265    public RecipientListDefinition<Type> aggregationStrategyMethodAllowNull() {
266        setStrategyMethodAllowNull(true);
267        return this;
268    }
269
270    /**
271     * Ignore the invalidate endpoint exception when try to create a producer with that endpoint
272     *
273     * @return the builder
274     */
275    public RecipientListDefinition<Type> ignoreInvalidEndpoints() {
276        setIgnoreInvalidEndpoints(true);
277        return this;
278    }
279
280    /**
281     * If enabled then sending messages to the recipients occurs concurrently.
282     * Note the caller thread will still wait until all messages has been fully processed, before it continues.
283     * Its only the sending and processing the replies from the recipients which happens concurrently.
284     *
285     * @return the builder
286     */
287    public RecipientListDefinition<Type> parallelProcessing() {
288        setParallelProcessing(true);
289        return this;
290    }
291
292    /**
293     * If enabled then the aggregate method on AggregationStrategy can be called concurrently.
294     * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe.
295     * By default this is false meaning that Camel synchronizes the call to the aggregate method.
296     * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.
297     *
298     * @return the builder
299     */
300    public RecipientListDefinition<Type> parallelAggregate() {
301        setParallelAggregate(true);
302        return this;
303    }
304
305    /**
306     * If enabled then Camel will process replies out-of-order, eg in the order they come back.
307     * If disabled, Camel will process replies in the same order as defined by the recipient list.
308     *
309     * @return the builder
310     */
311    public RecipientListDefinition<Type> streaming() {
312        setStreaming(true);
313        return this;
314    }
315
316    /**
317     * Will now stop further processing if an exception or failure occurred during processing of an
318     * {@link org.apache.camel.Exchange} and the caused exception will be thrown.
319     * <p/>
320     * Will also stop if processing the exchange failed (has a fault message) or an exception
321     * was thrown and handled by the error handler (such as using onException). In all situations
322     * the recipient list will stop further processing. This is the same behavior as in pipeline, which
323     * is used by the routing engine.
324     * <p/>
325     * The default behavior is to <b>not</b> stop but continue processing till the end
326     *
327     * @return the builder
328     */
329    public RecipientListDefinition<Type> stopOnException() {
330        setStopOnException(true);
331        return this;
332    }
333
334    /**
335     * To use a custom Thread Pool to be used for parallel processing.
336     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
337     */
338    public RecipientListDefinition<Type> executorService(ExecutorService executorService) {
339        setExecutorService(executorService);
340        return this;
341    }
342
343    /**
344     * Refers to a custom Thread Pool to be used for parallel processing.
345     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
346     */
347    public RecipientListDefinition<Type> executorServiceRef(String executorServiceRef) {
348        setExecutorServiceRef(executorServiceRef);
349        return this;
350    }
351
352    /**
353     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be used send.
354     * This can be used to deep-clone messages that should be send, or any custom logic needed before
355     * the exchange is send.
356     *
357     * @param onPrepare the processor
358     * @return the builder
359     */
360    public RecipientListDefinition<Type> onPrepare(Processor onPrepare) {
361        setOnPrepare(onPrepare);
362        return this;
363    }
364
365    /**
366     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send.
367     * This can be used to deep-clone messages that should be send, or any custom logic needed before
368     * the exchange is send.
369     *
370     * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry}
371     * @return the builder
372     */
373    public RecipientListDefinition<Type> onPrepareRef(String onPrepareRef) {
374        setOnPrepareRef(onPrepareRef);
375        return this;
376    }
377
378    /**
379     * Sets a total timeout specified in millis, when using parallel processing.
380     * If the Recipient List hasn't been able to send and process all replies within the given timeframe,
381     * then the timeout triggers and the Recipient List breaks out and continues.
382     * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out.
383     * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel
384     * to shut down in a graceful manner may continue to run. So use this option with a bit of care.
385     *
386     * @param timeout timeout in millis
387     * @return the builder
388     */
389    public RecipientListDefinition<Type> timeout(long timeout) {
390        setTimeout(timeout);
391        return this;
392    }
393
394    /**
395     * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages.
396     * Recipient List will by default not share unit of work between the parent exchange and each recipient exchange.
397     * This means each sub exchange has its own individual unit of work.
398     *
399     * @return the builder.
400     * @see org.apache.camel.spi.SubUnitOfWork
401     */
402    public RecipientListDefinition<Type> shareUnitOfWork() {
403        setShareUnitOfWork(true);
404        return this;
405    }
406
407    /**
408     * Sets the maximum size used by the {@link org.apache.camel.impl.ProducerCache} which is used
409     * to cache and reuse producers when using this recipient list, when uris are reused.
410     *
411     * @param cacheSize  the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off.
412     * @return the builder
413     */
414    public RecipientListDefinition<Type> cacheSize(int cacheSize) {
415        setCacheSize(cacheSize);
416        return this;
417    }
418
419    // Properties
420    //-------------------------------------------------------------------------
421
422
423    /**
424     * Expression that returns which endpoints (url) to send the message to (the recipients).
425     * If the expression return an empty value then the message is not sent to any recipients.
426     */
427    @Override
428    public void setExpression(ExpressionDefinition expression) {
429        // override to include javadoc what the expression is used for
430        super.setExpression(expression);
431    }
432
433    public String getDelimiter() {
434        return delimiter;
435    }
436
437    public void setDelimiter(String delimiter) {
438        this.delimiter = delimiter;
439    }
440
441    public Boolean getParallelProcessing() {
442        return parallelProcessing;
443    }
444
445    public void setParallelProcessing(Boolean parallelProcessing) {
446        this.parallelProcessing = parallelProcessing;
447    }
448
449    public String getStrategyRef() {
450        return strategyRef;
451    }
452
453    /**
454     * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
455     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
456     */
457    public void setStrategyRef(String strategyRef) {
458        this.strategyRef = strategyRef;
459    }
460
461    public String getStrategyMethodName() {
462        return strategyMethodName;
463    }
464
465    /**
466     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
467     */
468    public void setStrategyMethodName(String strategyMethodName) {
469        this.strategyMethodName = strategyMethodName;
470    }
471
472    public Boolean getStrategyMethodAllowNull() {
473        return strategyMethodAllowNull;
474    }
475
476    /**
477     * If this option is false then the aggregate method is not used if there was no data to enrich.
478     * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy
479     */
480    public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) {
481        this.strategyMethodAllowNull = strategyMethodAllowNull;
482    }
483
484    public String getExecutorServiceRef() {
485        return executorServiceRef;
486    }
487
488    public void setExecutorServiceRef(String executorServiceRef) {
489        this.executorServiceRef = executorServiceRef;
490    }
491
492    public Boolean getIgnoreInvalidEndpoints() {
493        return ignoreInvalidEndpoints;
494    }
495
496    public void setIgnoreInvalidEndpoints(Boolean ignoreInvalidEndpoints) {
497        this.ignoreInvalidEndpoints = ignoreInvalidEndpoints;
498    }
499
500    public Boolean getStopOnException() {
501        return stopOnException;
502    }
503
504    public void setStopOnException(Boolean stopOnException) {
505        this.stopOnException = stopOnException;
506    }
507
508    public AggregationStrategy getAggregationStrategy() {
509        return aggregationStrategy;
510    }
511
512    /**
513     * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
514     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
515     */
516    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
517        this.aggregationStrategy = aggregationStrategy;
518    }
519
520    public ExecutorService getExecutorService() {
521        return executorService;
522    }
523
524    public void setExecutorService(ExecutorService executorService) {
525        this.executorService = executorService;
526    }
527
528    public Boolean getStreaming() {
529        return streaming;
530    }
531
532    public void setStreaming(Boolean streaming) {
533        this.streaming = streaming;
534    }
535
536    public Long getTimeout() {
537        return timeout;
538    }
539
540    public void setTimeout(Long timeout) {
541        this.timeout = timeout;
542    }
543
544    public String getOnPrepareRef() {
545        return onPrepareRef;
546    }
547
548    public void setOnPrepareRef(String onPrepareRef) {
549        this.onPrepareRef = onPrepareRef;
550    }
551
552    public Processor getOnPrepare() {
553        return onPrepare;
554    }
555
556    public void setOnPrepare(Processor onPrepare) {
557        this.onPrepare = onPrepare;
558    }
559
560    public Boolean getShareUnitOfWork() {
561        return shareUnitOfWork;
562    }
563
564    public void setShareUnitOfWork(Boolean shareUnitOfWork) {
565        this.shareUnitOfWork = shareUnitOfWork;
566    }
567
568    public Integer getCacheSize() {
569        return cacheSize;
570    }
571
572    public void setCacheSize(Integer cacheSize) {
573        this.cacheSize = cacheSize;
574    }
575
576    public Boolean getParallelAggregate() {
577        return parallelAggregate;
578    }
579
580    public void setParallelAggregate(Boolean parallelAggregate) {
581        this.parallelAggregate = parallelAggregate;
582    }
583}