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