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 sending messages to the recipients occurs concurrently.
294     * Note the caller thread will still wait until all messages has been fully processed, before it continues.
295     * Its only the sending and processing the replies from the recipients which happens concurrently.
296     *
297     * @return the builder
298     */
299    public RecipientListDefinition<Type> parallelProcessing(boolean parallelProcessing) {
300        setParallelProcessing(parallelProcessing);
301        return this;
302    }
303
304    /**
305     * If enabled then the aggregate method on AggregationStrategy can be called concurrently.
306     * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe.
307     * By default this is false meaning that Camel synchronizes the call to the aggregate method.
308     * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.
309     *
310     * @return the builder
311     */
312    public RecipientListDefinition<Type> parallelAggregate() {
313        setParallelAggregate(true);
314        return this;
315    }
316
317    /**
318     * If enabled then Camel will process replies out-of-order, eg in the order they come back.
319     * If disabled, Camel will process replies in the same order as defined by the recipient list.
320     *
321     * @return the builder
322     */
323    public RecipientListDefinition<Type> streaming() {
324        setStreaming(true);
325        return this;
326    }
327
328    /**
329     * Will now stop further processing if an exception or failure occurred during processing of an
330     * {@link org.apache.camel.Exchange} and the caused exception will be thrown.
331     * <p/>
332     * Will also stop if processing the exchange failed (has a fault message) or an exception
333     * was thrown and handled by the error handler (such as using onException). In all situations
334     * the recipient list will stop further processing. This is the same behavior as in pipeline, which
335     * is used by the routing engine.
336     * <p/>
337     * The default behavior is to <b>not</b> stop but continue processing till the end
338     *
339     * @return the builder
340     */
341    public RecipientListDefinition<Type> stopOnException() {
342        setStopOnException(true);
343        return this;
344    }
345
346    /**
347     * To use a custom Thread Pool to be used for parallel processing.
348     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
349     */
350    public RecipientListDefinition<Type> executorService(ExecutorService executorService) {
351        setExecutorService(executorService);
352        return this;
353    }
354
355    /**
356     * Refers to a custom Thread Pool to be used for parallel processing.
357     * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.
358     */
359    public RecipientListDefinition<Type> executorServiceRef(String executorServiceRef) {
360        setExecutorServiceRef(executorServiceRef);
361        return this;
362    }
363
364    /**
365     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be used send.
366     * This can be used to deep-clone messages that should be send, or any custom logic needed before
367     * the exchange is send.
368     *
369     * @param onPrepare the processor
370     * @return the builder
371     */
372    public RecipientListDefinition<Type> onPrepare(Processor onPrepare) {
373        setOnPrepare(onPrepare);
374        return this;
375    }
376
377    /**
378     * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send.
379     * This can be used to deep-clone messages that should be send, or any custom logic needed before
380     * the exchange is send.
381     *
382     * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry}
383     * @return the builder
384     */
385    public RecipientListDefinition<Type> onPrepareRef(String onPrepareRef) {
386        setOnPrepareRef(onPrepareRef);
387        return this;
388    }
389
390    /**
391     * Sets a total timeout specified in millis, when using parallel processing.
392     * If the Recipient List hasn't been able to send and process all replies within the given timeframe,
393     * then the timeout triggers and the Recipient List breaks out and continues.
394     * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out.
395     * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel
396     * to shut down in a graceful manner may continue to run. So use this option with a bit of care.
397     *
398     * @param timeout timeout in millis
399     * @return the builder
400     */
401    public RecipientListDefinition<Type> timeout(long timeout) {
402        setTimeout(timeout);
403        return this;
404    }
405
406    /**
407     * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages.
408     * Recipient List will by default not share unit of work between the parent exchange and each recipient exchange.
409     * This means each sub exchange has its own individual unit of work.
410     *
411     * @return the builder.
412     * @see org.apache.camel.spi.SubUnitOfWork
413     */
414    public RecipientListDefinition<Type> shareUnitOfWork() {
415        setShareUnitOfWork(true);
416        return this;
417    }
418
419    /**
420     * Sets the maximum size used by the {@link org.apache.camel.impl.ProducerCache} which is used
421     * to cache and reuse producers when using this recipient list, when uris are reused.
422     *
423     * @param cacheSize  the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off.
424     * @return the builder
425     */
426    public RecipientListDefinition<Type> cacheSize(int cacheSize) {
427        setCacheSize(cacheSize);
428        return this;
429    }
430
431    // Properties
432    //-------------------------------------------------------------------------
433
434
435    /**
436     * Expression that returns which endpoints (url) to send the message to (the recipients).
437     * If the expression return an empty value then the message is not sent to any recipients.
438     */
439    @Override
440    public void setExpression(ExpressionDefinition expression) {
441        // override to include javadoc what the expression is used for
442        super.setExpression(expression);
443    }
444
445    public String getDelimiter() {
446        return delimiter;
447    }
448
449    public void setDelimiter(String delimiter) {
450        this.delimiter = delimiter;
451    }
452
453    public Boolean getParallelProcessing() {
454        return parallelProcessing;
455    }
456
457    public void setParallelProcessing(Boolean parallelProcessing) {
458        this.parallelProcessing = parallelProcessing;
459    }
460
461    public String getStrategyRef() {
462        return strategyRef;
463    }
464
465    /**
466     * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
467     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
468     */
469    public void setStrategyRef(String strategyRef) {
470        this.strategyRef = strategyRef;
471    }
472
473    public String getStrategyMethodName() {
474        return strategyMethodName;
475    }
476
477    /**
478     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
479     */
480    public void setStrategyMethodName(String strategyMethodName) {
481        this.strategyMethodName = strategyMethodName;
482    }
483
484    public Boolean getStrategyMethodAllowNull() {
485        return strategyMethodAllowNull;
486    }
487
488    /**
489     * If this option is false then the aggregate method is not used if there was no data to enrich.
490     * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy
491     */
492    public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) {
493        this.strategyMethodAllowNull = strategyMethodAllowNull;
494    }
495
496    public String getExecutorServiceRef() {
497        return executorServiceRef;
498    }
499
500    public void setExecutorServiceRef(String executorServiceRef) {
501        this.executorServiceRef = executorServiceRef;
502    }
503
504    public Boolean getIgnoreInvalidEndpoints() {
505        return ignoreInvalidEndpoints;
506    }
507
508    public void setIgnoreInvalidEndpoints(Boolean ignoreInvalidEndpoints) {
509        this.ignoreInvalidEndpoints = ignoreInvalidEndpoints;
510    }
511
512    public Boolean getStopOnException() {
513        return stopOnException;
514    }
515
516    public void setStopOnException(Boolean stopOnException) {
517        this.stopOnException = stopOnException;
518    }
519
520    public AggregationStrategy getAggregationStrategy() {
521        return aggregationStrategy;
522    }
523
524    /**
525     * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList.
526     * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy
527     */
528    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
529        this.aggregationStrategy = aggregationStrategy;
530    }
531
532    public ExecutorService getExecutorService() {
533        return executorService;
534    }
535
536    public void setExecutorService(ExecutorService executorService) {
537        this.executorService = executorService;
538    }
539
540    public Boolean getStreaming() {
541        return streaming;
542    }
543
544    public void setStreaming(Boolean streaming) {
545        this.streaming = streaming;
546    }
547
548    public Long getTimeout() {
549        return timeout;
550    }
551
552    public void setTimeout(Long timeout) {
553        this.timeout = timeout;
554    }
555
556    public String getOnPrepareRef() {
557        return onPrepareRef;
558    }
559
560    public void setOnPrepareRef(String onPrepareRef) {
561        this.onPrepareRef = onPrepareRef;
562    }
563
564    public Processor getOnPrepare() {
565        return onPrepare;
566    }
567
568    public void setOnPrepare(Processor onPrepare) {
569        this.onPrepare = onPrepare;
570    }
571
572    public Boolean getShareUnitOfWork() {
573        return shareUnitOfWork;
574    }
575
576    public void setShareUnitOfWork(Boolean shareUnitOfWork) {
577        this.shareUnitOfWork = shareUnitOfWork;
578    }
579
580    public Integer getCacheSize() {
581        return cacheSize;
582    }
583
584    public void setCacheSize(Integer cacheSize) {
585        this.cacheSize = cacheSize;
586    }
587
588    public Boolean getParallelAggregate() {
589        return parallelAggregate;
590    }
591
592    public void setParallelAggregate(Boolean parallelAggregate) {
593        this.parallelAggregate = parallelAggregate;
594    }
595}