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