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 java.util.concurrent.ScheduledExecutorService;
023import javax.xml.bind.annotation.XmlAccessType;
024import javax.xml.bind.annotation.XmlAccessorType;
025import javax.xml.bind.annotation.XmlAttribute;
026import javax.xml.bind.annotation.XmlElement;
027import javax.xml.bind.annotation.XmlElementRef;
028import javax.xml.bind.annotation.XmlRootElement;
029import javax.xml.bind.annotation.XmlTransient;
030
031import org.apache.camel.CamelContextAware;
032import org.apache.camel.Expression;
033import org.apache.camel.Predicate;
034import org.apache.camel.Processor;
035import org.apache.camel.builder.AggregationStrategyClause;
036import org.apache.camel.builder.ExpressionClause;
037import org.apache.camel.builder.PredicateClause;
038import org.apache.camel.model.language.ExpressionDefinition;
039import org.apache.camel.processor.CamelInternalProcessor;
040import org.apache.camel.processor.aggregate.AggregateController;
041import org.apache.camel.processor.aggregate.AggregateProcessor;
042import org.apache.camel.processor.aggregate.AggregationStrategy;
043import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter;
044import org.apache.camel.processor.aggregate.ClosedCorrelationKeyException;
045import org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy;
046import org.apache.camel.processor.aggregate.OptimisticLockRetryPolicy;
047import org.apache.camel.spi.AggregationRepository;
048import org.apache.camel.spi.AsPredicate;
049import org.apache.camel.spi.Metadata;
050import org.apache.camel.spi.RouteContext;
051import org.apache.camel.util.concurrent.SynchronousExecutorService;
052
053/**
054 * Aggregates many messages into a single message
055 *
056 * @version 
057 */
058@Metadata(label = "eip,routing")
059@XmlRootElement(name = "aggregate")
060@XmlAccessorType(XmlAccessType.FIELD)
061public class AggregateDefinition extends ProcessorDefinition<AggregateDefinition> implements ExecutorServiceAwareDefinition<AggregateDefinition> {
062    @XmlElement(name = "correlationExpression", required = true)
063    private ExpressionSubElementDefinition correlationExpression;
064    @XmlElement(name = "completionPredicate") @AsPredicate
065    private ExpressionSubElementDefinition completionPredicate;
066    @XmlElement(name = "completionTimeout")
067    private ExpressionSubElementDefinition completionTimeoutExpression;
068    @XmlElement(name = "completionSize")
069    private ExpressionSubElementDefinition completionSizeExpression;
070    @XmlElement(name = "optimisticLockRetryPolicy")
071    private OptimisticLockRetryPolicyDefinition optimisticLockRetryPolicyDefinition;
072    @XmlTransient
073    private ExpressionDefinition expression;
074    @XmlTransient
075    private AggregationStrategy aggregationStrategy;
076    @XmlTransient
077    private ExecutorService executorService;
078    @XmlTransient
079    private ScheduledExecutorService timeoutCheckerExecutorService;
080    @XmlTransient
081    private AggregationRepository aggregationRepository;
082    @XmlTransient
083    private OptimisticLockRetryPolicy optimisticLockRetryPolicy;
084    @XmlAttribute
085    private Boolean parallelProcessing;
086    @XmlAttribute
087    private Boolean optimisticLocking;
088    @XmlAttribute
089    private String executorServiceRef;
090    @XmlAttribute
091    private String timeoutCheckerExecutorServiceRef;
092    @XmlAttribute
093    private String aggregationRepositoryRef;
094    @XmlAttribute
095    private String strategyRef;
096    @XmlAttribute
097    private String strategyMethodName;
098    @XmlAttribute
099    private Boolean strategyMethodAllowNull;
100    @XmlAttribute
101    private Integer completionSize;
102    @XmlAttribute
103    private Long completionInterval;
104    @XmlAttribute
105    private Long completionTimeout;
106    @XmlAttribute @Metadata(defaultValue = "1000")
107    private Long completionTimeoutCheckerInterval = 1000L;
108    @XmlAttribute
109    private Boolean completionFromBatchConsumer;
110    @XmlAttribute
111    @Deprecated
112    private Boolean groupExchanges;
113    @XmlAttribute
114    private Boolean eagerCheckCompletion;
115    @XmlAttribute
116    private Boolean ignoreInvalidCorrelationKeys;
117    @XmlAttribute
118    private Integer closeCorrelationKeyOnCompletion;
119    @XmlAttribute
120    private Boolean discardOnCompletionTimeout;
121    @XmlAttribute
122    private Boolean forceCompletionOnStop;
123    @XmlAttribute
124    private Boolean completeAllOnStop;
125    @XmlTransient
126    private AggregateController aggregateController;
127    @XmlAttribute
128    private String aggregateControllerRef;
129    @XmlElementRef
130    private List<ProcessorDefinition<?>> outputs = new ArrayList<ProcessorDefinition<?>>();
131
132    public AggregateDefinition() {
133    }
134
135    public AggregateDefinition(@AsPredicate Predicate predicate) {
136        this(ExpressionNodeHelper.toExpressionDefinition(predicate));
137    }
138    
139    public AggregateDefinition(Expression expression) {
140        this(ExpressionNodeHelper.toExpressionDefinition(expression));
141    }
142
143    public AggregateDefinition(ExpressionDefinition correlationExpression) {
144        setExpression(correlationExpression);
145
146        ExpressionSubElementDefinition cor = new ExpressionSubElementDefinition();
147        cor.setExpressionType(correlationExpression);
148        setCorrelationExpression(cor);
149    }
150
151    public AggregateDefinition(Expression correlationExpression, AggregationStrategy aggregationStrategy) {
152        this(correlationExpression);
153        this.aggregationStrategy = aggregationStrategy;
154    }
155
156    @Override
157    public String toString() {
158        return "Aggregate[" + description() + " -> " + getOutputs() + "]";
159    }
160    
161    protected String description() {
162        return getExpression() != null ? getExpression().getLabel() : "";
163    }
164
165    @Override
166    public String getLabel() {
167        return "aggregate[" + description() + "]";
168    }
169
170    @Override
171    public Processor createProcessor(RouteContext routeContext) throws Exception {
172        return createAggregator(routeContext);
173    }
174
175    protected AggregateProcessor createAggregator(RouteContext routeContext) throws Exception {
176        Processor childProcessor = this.createChildProcessor(routeContext, true);
177
178        // wrap the aggregate route in a unit of work processor
179        CamelInternalProcessor internal = new CamelInternalProcessor(childProcessor);
180        internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext));
181
182        Expression correlation = getExpression().createExpression(routeContext);
183        AggregationStrategy strategy = createAggregationStrategy(routeContext);
184
185        boolean parallel = getParallelProcessing() != null && getParallelProcessing();
186        boolean shutdownThreadPool = ProcessorDefinitionHelper.willCreateNewThreadPool(routeContext, this, parallel);
187        ExecutorService threadPool = ProcessorDefinitionHelper.getConfiguredExecutorService(routeContext, "Aggregator", this, parallel);
188        if (threadPool == null && !parallel) {
189            // executor service is mandatory for the Aggregator
190            // we do not run in parallel mode, but use a synchronous executor, so we run in current thread
191            threadPool = new SynchronousExecutorService();
192            shutdownThreadPool = true;
193        }
194
195        AggregateProcessor answer = new AggregateProcessor(routeContext.getCamelContext(), internal,
196                correlation, strategy, threadPool, shutdownThreadPool);
197
198        AggregationRepository repository = createAggregationRepository(routeContext);
199        if (repository != null) {
200            answer.setAggregationRepository(repository);
201        }
202
203        if (getAggregateController() == null && getAggregateControllerRef() != null) {
204            setAggregateController(routeContext.mandatoryLookup(getAggregateControllerRef(), AggregateController.class));
205        }
206
207        // this EIP supports using a shared timeout checker thread pool or fallback to create a new thread pool
208        boolean shutdownTimeoutThreadPool = false;
209        ScheduledExecutorService timeoutThreadPool = timeoutCheckerExecutorService;
210        if (timeoutThreadPool == null && timeoutCheckerExecutorServiceRef != null) {
211            // lookup existing thread pool
212            timeoutThreadPool = routeContext.getCamelContext().getRegistry().lookupByNameAndType(timeoutCheckerExecutorServiceRef, ScheduledExecutorService.class);
213            if (timeoutThreadPool == null) {
214                // then create a thread pool assuming the ref is a thread pool profile id
215                timeoutThreadPool = routeContext.getCamelContext().getExecutorServiceManager().newScheduledThreadPool(this,
216                        AggregateProcessor.AGGREGATE_TIMEOUT_CHECKER, timeoutCheckerExecutorServiceRef);
217                if (timeoutThreadPool == null) {
218                    throw new IllegalArgumentException("ExecutorServiceRef " + timeoutCheckerExecutorServiceRef 
219                            + " not found in registry (as an ScheduledExecutorService instance) or as a thread pool profile.");
220                }
221                shutdownTimeoutThreadPool = true;
222            }
223        }
224        answer.setTimeoutCheckerExecutorService(timeoutThreadPool);
225        answer.setShutdownTimeoutCheckerExecutorService(shutdownTimeoutThreadPool);
226
227        // set other options
228        answer.setParallelProcessing(parallel);
229        if (getOptimisticLocking() != null) {
230            answer.setOptimisticLocking(getOptimisticLocking());
231        }
232        if (getCompletionPredicate() != null) {
233            Predicate predicate = getCompletionPredicate().createPredicate(routeContext);
234            answer.setCompletionPredicate(predicate);
235        } else if (strategy instanceof Predicate) {
236            // if aggregation strategy implements predicate and was not configured then use as fallback
237            log.debug("Using AggregationStrategy as completion predicate: {}", strategy);
238            answer.setCompletionPredicate((Predicate) strategy);
239        }
240        if (getCompletionTimeoutExpression() != null) {
241            Expression expression = getCompletionTimeoutExpression().createExpression(routeContext);
242            answer.setCompletionTimeoutExpression(expression);
243        }
244        if (getCompletionTimeout() != null) {
245            answer.setCompletionTimeout(getCompletionTimeout());
246        }
247        if (getCompletionInterval() != null) {
248            answer.setCompletionInterval(getCompletionInterval());
249        }
250        if (getCompletionSizeExpression() != null) {
251            Expression expression = getCompletionSizeExpression().createExpression(routeContext);
252            answer.setCompletionSizeExpression(expression);
253        }
254        if (getCompletionSize() != null) {
255            answer.setCompletionSize(getCompletionSize());
256        }
257        if (getCompletionFromBatchConsumer() != null) {
258            answer.setCompletionFromBatchConsumer(getCompletionFromBatchConsumer());
259        }
260        if (getEagerCheckCompletion() != null) {
261            answer.setEagerCheckCompletion(getEagerCheckCompletion());
262        }
263        if (getIgnoreInvalidCorrelationKeys() != null) {
264            answer.setIgnoreInvalidCorrelationKeys(getIgnoreInvalidCorrelationKeys());
265        }
266        if (getCloseCorrelationKeyOnCompletion() != null) {
267            answer.setCloseCorrelationKeyOnCompletion(getCloseCorrelationKeyOnCompletion());
268        }
269        if (getDiscardOnCompletionTimeout() != null) {
270            answer.setDiscardOnCompletionTimeout(getDiscardOnCompletionTimeout());
271        }
272        if (getForceCompletionOnStop() != null) {
273            answer.setForceCompletionOnStop(getForceCompletionOnStop());
274        }
275        if (getCompleteAllOnStop() != null) {
276            answer.setCompleteAllOnStop(getCompleteAllOnStop());
277        }
278        if (optimisticLockRetryPolicy == null) {
279            if (getOptimisticLockRetryPolicyDefinition() != null) {
280                answer.setOptimisticLockRetryPolicy(getOptimisticLockRetryPolicyDefinition().createOptimisticLockRetryPolicy());
281            }
282        } else {
283            answer.setOptimisticLockRetryPolicy(optimisticLockRetryPolicy);
284        }
285        if (getAggregateController() != null) {
286            answer.setAggregateController(getAggregateController());
287        }
288        if (getCompletionTimeoutCheckerInterval() != null) {
289            answer.setCompletionTimeoutCheckerInterval(getCompletionTimeoutCheckerInterval());
290        }
291        return answer;
292    }
293
294    @Override
295    public void configureChild(ProcessorDefinition<?> output) {
296        if (expression != null && expression instanceof ExpressionClause) {
297            ExpressionClause<?> clause = (ExpressionClause<?>) expression;
298            if (clause.getExpressionType() != null) {
299                // if using the Java DSL then the expression may have been set using the
300                // ExpressionClause which is a fancy builder to define expressions and predicates
301                // using fluent builders in the DSL. However we need afterwards a callback to
302                // reset the expression to the expression type the ExpressionClause did build for us
303                expression = clause.getExpressionType();
304                // set the correlation expression from the expression type, as the model definition
305                // would then be accurate
306                correlationExpression = new ExpressionSubElementDefinition();
307                correlationExpression.setExpressionType(clause.getExpressionType());
308            }
309        }
310    }
311
312    private AggregationStrategy createAggregationStrategy(RouteContext routeContext) {
313        AggregationStrategy strategy = getAggregationStrategy();
314        if (strategy == null && strategyRef != null) {
315            Object aggStrategy = routeContext.lookup(strategyRef, Object.class);
316            if (aggStrategy instanceof AggregationStrategy) {
317                strategy = (AggregationStrategy) aggStrategy;
318            } else if (aggStrategy != null) {
319                AggregationStrategyBeanAdapter adapter = new AggregationStrategyBeanAdapter(aggStrategy, getAggregationStrategyMethodName());
320                if (getStrategyMethodAllowNull() != null) {
321                    adapter.setAllowNullNewExchange(getStrategyMethodAllowNull());
322                    adapter.setAllowNullOldExchange(getStrategyMethodAllowNull());
323                }
324                strategy = adapter;
325            } else {
326                throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + strategyRef);
327            }
328        }
329
330        if (groupExchanges != null && groupExchanges) {
331            if (strategy != null || strategyRef != null) {
332                throw new IllegalArgumentException("Options groupExchanges and AggregationStrategy cannot be enabled at the same time");
333            }
334            if (eagerCheckCompletion != null && !eagerCheckCompletion) {
335                throw new IllegalArgumentException("Option eagerCheckCompletion cannot be false when groupExchanges has been enabled");
336            }
337            // set eager check to enabled by default when using grouped exchanges
338            setEagerCheckCompletion(true);
339            // if grouped exchange is enabled then use special strategy for that
340            strategy = new GroupedExchangeAggregationStrategy();
341        }
342
343        if (strategy == null) {
344            throw new IllegalArgumentException("AggregationStrategy or AggregationStrategyRef must be set on " + this);
345        }
346
347        if (strategy instanceof CamelContextAware) {
348            ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext());
349        }
350
351        return strategy;
352    }
353
354    private AggregationRepository createAggregationRepository(RouteContext routeContext) {
355        AggregationRepository repository = getAggregationRepository();
356        if (repository == null && aggregationRepositoryRef != null) {
357            repository = routeContext.mandatoryLookup(aggregationRepositoryRef, AggregationRepository.class);
358        }
359        return repository;
360    }
361
362    public AggregationStrategy getAggregationStrategy() {
363        return aggregationStrategy;
364    }
365
366    /**
367     * The AggregationStrategy to use.
368     * <p/>
369     * Configuring an AggregationStrategy is required, and is used to merge the incoming Exchange with the existing already merged exchanges.
370     * At first call the oldExchange parameter is null.
371     * On subsequent invocations the oldExchange contains the merged exchanges and newExchange is of course the new incoming Exchange.
372     */
373    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
374        this.aggregationStrategy = aggregationStrategy;
375    }
376
377    public String getAggregationStrategyRef() {
378        return strategyRef;
379    }
380
381    /**
382     * A reference to lookup the AggregationStrategy in the Registry.
383     * <p/>
384     * Configuring an AggregationStrategy is required, and is used to merge the incoming Exchange with the existing already merged exchanges.
385     * At first call the oldExchange parameter is null.
386     * On subsequent invocations the oldExchange contains the merged exchanges and newExchange is of course the new incoming Exchange.
387     */
388    public void setAggregationStrategyRef(String aggregationStrategyRef) {
389        this.strategyRef = aggregationStrategyRef;
390    }
391
392    public String getStrategyRef() {
393        return strategyRef;
394    }
395
396    /**
397     * A reference to lookup the AggregationStrategy in the Registry.
398     * <p/>
399     * Configuring an AggregationStrategy is required, and is used to merge the incoming Exchange with the existing already merged exchanges.
400     * At first call the oldExchange parameter is null.
401     * On subsequent invocations the oldExchange contains the merged exchanges and newExchange is of course the new incoming Exchange.
402     */
403    public void setStrategyRef(String strategyRef) {
404        this.strategyRef = strategyRef;
405    }
406
407    public String getAggregationStrategyMethodName() {
408        return strategyMethodName;
409    }
410
411    /**
412     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
413     */
414    public void setAggregationStrategyMethodName(String strategyMethodName) {
415        this.strategyMethodName = strategyMethodName;
416    }
417
418    public Boolean getStrategyMethodAllowNull() {
419        return strategyMethodAllowNull;
420    }
421
422    public String getStrategyMethodName() {
423        return strategyMethodName;
424    }
425
426    /**
427     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
428     */
429    public void setStrategyMethodName(String strategyMethodName) {
430        this.strategyMethodName = strategyMethodName;
431    }
432
433    /**
434     * If this option is false then the aggregate method is not used for the very first aggregation.
435     * If this option is true then null values is used as the oldExchange (at the very first aggregation),
436     * when using POJOs as the AggregationStrategy.
437     */
438    public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) {
439        this.strategyMethodAllowNull = strategyMethodAllowNull;
440    }
441
442    /**
443     * The expression used to calculate the correlation key to use for aggregation.
444     * The Exchange which has the same correlation key is aggregated together.
445     * If the correlation key could not be evaluated an Exception is thrown.
446     * You can disable this by using the ignoreBadCorrelationKeys option.
447     */
448    public void setCorrelationExpression(ExpressionSubElementDefinition correlationExpression) {
449        this.correlationExpression = correlationExpression;
450    }
451
452    public ExpressionSubElementDefinition getCorrelationExpression() {
453        return correlationExpression;
454    }
455
456    public Integer getCompletionSize() {
457        return completionSize;
458    }
459
460    public void setCompletionSize(Integer completionSize) {
461        this.completionSize = completionSize;
462    }
463
464    public OptimisticLockRetryPolicyDefinition getOptimisticLockRetryPolicyDefinition() {
465        return optimisticLockRetryPolicyDefinition;
466    }
467
468    public void setOptimisticLockRetryPolicyDefinition(OptimisticLockRetryPolicyDefinition optimisticLockRetryPolicyDefinition) {
469        this.optimisticLockRetryPolicyDefinition = optimisticLockRetryPolicyDefinition;
470    }
471
472    public OptimisticLockRetryPolicy getOptimisticLockRetryPolicy() {
473        return optimisticLockRetryPolicy;
474    }
475
476    public void setOptimisticLockRetryPolicy(OptimisticLockRetryPolicy optimisticLockRetryPolicy) {
477        this.optimisticLockRetryPolicy = optimisticLockRetryPolicy;
478    }
479
480    public Long getCompletionInterval() {
481        return completionInterval;
482    }
483
484    public void setCompletionInterval(Long completionInterval) {
485        this.completionInterval = completionInterval;
486    }
487
488    public Long getCompletionTimeout() {
489        return completionTimeout;
490    }
491
492    public void setCompletionTimeout(Long completionTimeout) {
493        this.completionTimeout = completionTimeout;
494    }
495
496    public Long getCompletionTimeoutCheckerInterval() {
497        return completionTimeoutCheckerInterval;
498    }
499
500    public void setCompletionTimeoutCheckerInterval(Long completionTimeoutCheckerInterval) {
501        this.completionTimeoutCheckerInterval = completionTimeoutCheckerInterval;
502    }
503
504    public ExpressionSubElementDefinition getCompletionPredicate() {
505        return completionPredicate;
506    }
507
508    public void setCompletionPredicate(ExpressionSubElementDefinition completionPredicate) {
509        this.completionPredicate = completionPredicate;
510    }
511
512    public ExpressionSubElementDefinition getCompletionTimeoutExpression() {
513        return completionTimeoutExpression;
514    }
515
516    public void setCompletionTimeoutExpression(ExpressionSubElementDefinition completionTimeoutExpression) {
517        this.completionTimeoutExpression = completionTimeoutExpression;
518    }
519
520    public ExpressionSubElementDefinition getCompletionSizeExpression() {
521        return completionSizeExpression;
522    }
523
524    public void setCompletionSizeExpression(ExpressionSubElementDefinition completionSizeExpression) {
525        this.completionSizeExpression = completionSizeExpression;
526    }
527
528    public Boolean getGroupExchanges() {
529        return groupExchanges;
530    }
531
532    public void setGroupExchanges(Boolean groupExchanges) {
533        this.groupExchanges = groupExchanges;
534    }
535
536    public Boolean getCompletionFromBatchConsumer() {
537        return completionFromBatchConsumer;
538    }
539
540    public void setCompletionFromBatchConsumer(Boolean completionFromBatchConsumer) {
541        this.completionFromBatchConsumer = completionFromBatchConsumer;
542    }
543
544    public ExecutorService getExecutorService() {
545        return executorService;
546    }
547
548    public void setExecutorService(ExecutorService executorService) {
549        this.executorService = executorService;
550    }
551
552    public Boolean getOptimisticLocking() {
553        return optimisticLocking;
554    }
555
556    public void setOptimisticLocking(boolean optimisticLocking) {
557        this.optimisticLocking = optimisticLocking;
558    }
559
560    public Boolean getParallelProcessing() {
561        return parallelProcessing;
562    }
563
564    public void setParallelProcessing(boolean parallelProcessing) {
565        this.parallelProcessing = parallelProcessing;
566    }
567
568    public String getExecutorServiceRef() {
569        return executorServiceRef;
570    }
571
572    public void setExecutorServiceRef(String executorServiceRef) {
573        this.executorServiceRef = executorServiceRef;
574    }
575
576    public Boolean getEagerCheckCompletion() {
577        return eagerCheckCompletion;
578    }
579
580    public void setEagerCheckCompletion(Boolean eagerCheckCompletion) {
581        this.eagerCheckCompletion = eagerCheckCompletion;
582    }
583
584    public Boolean getIgnoreInvalidCorrelationKeys() {
585        return ignoreInvalidCorrelationKeys;
586    }
587
588    public void setIgnoreInvalidCorrelationKeys(Boolean ignoreInvalidCorrelationKeys) {
589        this.ignoreInvalidCorrelationKeys = ignoreInvalidCorrelationKeys;
590    }
591
592    public Integer getCloseCorrelationKeyOnCompletion() {
593        return closeCorrelationKeyOnCompletion;
594    }
595
596    public void setCloseCorrelationKeyOnCompletion(Integer closeCorrelationKeyOnCompletion) {
597        this.closeCorrelationKeyOnCompletion = closeCorrelationKeyOnCompletion;
598    }
599
600    public AggregationRepository getAggregationRepository() {
601        return aggregationRepository;
602    }
603
604    public void setAggregationRepository(AggregationRepository aggregationRepository) {
605        this.aggregationRepository = aggregationRepository;
606    }
607
608    public String getAggregationRepositoryRef() {
609        return aggregationRepositoryRef;
610    }
611
612    public void setAggregationRepositoryRef(String aggregationRepositoryRef) {
613        this.aggregationRepositoryRef = aggregationRepositoryRef;
614    }
615
616    public Boolean getDiscardOnCompletionTimeout() {
617        return discardOnCompletionTimeout;
618    }
619
620    public void setDiscardOnCompletionTimeout(Boolean discardOnCompletionTimeout) {
621        this.discardOnCompletionTimeout = discardOnCompletionTimeout;
622    }
623    
624    public void setTimeoutCheckerExecutorService(ScheduledExecutorService timeoutCheckerExecutorService) {
625        this.timeoutCheckerExecutorService = timeoutCheckerExecutorService;
626    }
627
628    public ScheduledExecutorService getTimeoutCheckerExecutorService() {
629        return timeoutCheckerExecutorService;
630    }
631
632    public void setTimeoutCheckerExecutorServiceRef(String timeoutCheckerExecutorServiceRef) {
633        this.timeoutCheckerExecutorServiceRef = timeoutCheckerExecutorServiceRef;
634    }
635
636    public String getTimeoutCheckerExecutorServiceRef() {
637        return timeoutCheckerExecutorServiceRef;
638    }
639
640    public Boolean getForceCompletionOnStop() {
641        return forceCompletionOnStop;
642    }
643
644    public void setForceCompletionOnStop(Boolean forceCompletionOnStop) {
645        this.forceCompletionOnStop = forceCompletionOnStop;
646    }
647
648    public Boolean getCompleteAllOnStop() {
649        return completeAllOnStop;
650    }
651
652    public void setCompleteAllOnStop(Boolean completeAllOnStop) {
653        this.completeAllOnStop = completeAllOnStop;
654    }
655
656    public AggregateController getAggregateController() {
657        return aggregateController;
658    }
659
660    public void setAggregateController(AggregateController aggregateController) {
661        this.aggregateController = aggregateController;
662    }
663
664    public String getAggregateControllerRef() {
665        return aggregateControllerRef;
666    }
667
668    /**
669     * To use a {@link org.apache.camel.processor.aggregate.AggregateController} to allow external sources to control
670     * this aggregator.
671     */
672    public void setAggregateControllerRef(String aggregateControllerRef) {
673        this.aggregateControllerRef = aggregateControllerRef;
674    }
675
676    // Fluent API
677    //-------------------------------------------------------------------------
678
679    /**
680     * Use eager completion checking which means that the {{completionPredicate}} will use the incoming Exchange.
681     * As opposed to without eager completion checking the {{completionPredicate}} will use the aggregated Exchange.
682     *
683     * @return builder
684     */
685    public AggregateDefinition eagerCheckCompletion() {
686        setEagerCheckCompletion(true);
687        return this;
688    }
689
690    /**
691     * If a correlation key cannot be successfully evaluated it will be ignored by logging a {{DEBUG}} and then just
692     * ignore the incoming Exchange.
693     *
694     * @return builder
695     */
696    public AggregateDefinition ignoreInvalidCorrelationKeys() {
697        setIgnoreInvalidCorrelationKeys(true);
698        return this;
699    }
700
701    /**
702     * Closes a correlation key when its complete. Any <i>late</i> received exchanges which has a correlation key
703     * that has been closed, it will be defined and a {@link ClosedCorrelationKeyException}
704     * is thrown.
705     *
706     * @param capacity the maximum capacity of the closed correlation key cache.
707     *                 Use <tt>0</tt> or negative value for unbounded capacity.
708     * @return builder
709     */
710    public AggregateDefinition closeCorrelationKeyOnCompletion(int capacity) {
711        setCloseCorrelationKeyOnCompletion(capacity);
712        return this;
713    }
714
715    /**
716     * Discards the aggregated message on completion timeout.
717     * <p/>
718     * This means on timeout the aggregated message is dropped and not sent out of the aggregator.
719     *
720     * @return builder
721     */
722    public AggregateDefinition discardOnCompletionTimeout() {
723        setDiscardOnCompletionTimeout(true);
724        return this;
725    }
726
727    /**
728     * Enables the batch completion mode where we aggregate from a {@link org.apache.camel.BatchConsumer}
729     * and aggregate the total number of exchanges the {@link org.apache.camel.BatchConsumer} has reported
730     * as total by checking the exchange property {@link org.apache.camel.Exchange#BATCH_COMPLETE} when its complete.
731     *
732     * @return builder
733     */
734    public AggregateDefinition completionFromBatchConsumer() {
735        setCompletionFromBatchConsumer(true);
736        return this;
737    }
738
739    /**
740     * Number of messages aggregated before the aggregation is complete. This option can be set as either
741     * a fixed value or using an Expression which allows you to evaluate a size dynamically - will use Integer as result.
742     * If both are set Camel will fallback to use the fixed value if the Expression result was null or 0.
743     *
744     * @param completionSize  the completion size, must be a positive number
745     * @return builder
746     */
747    public AggregateDefinition completionSize(int completionSize) {
748        setCompletionSize(completionSize);
749        return this;
750    }
751
752    /**
753     * Number of messages aggregated before the aggregation is complete. This option can be set as either
754     * a fixed value or using an Expression which allows you to evaluate a size dynamically - will use Integer as result.
755     * If both are set Camel will fallback to use the fixed value if the Expression result was null or 0.
756     *
757     * @param completionSize  the completion size as an {@link org.apache.camel.Expression} which is evaluated as a {@link Integer} type
758     * @return builder
759     */
760    public AggregateDefinition completionSize(Expression completionSize) {
761        setCompletionSizeExpression(new ExpressionSubElementDefinition(completionSize));
762        return this;
763    }
764
765    /**
766     * A repeating period in millis by which the aggregator will complete all current aggregated exchanges.
767     * Camel has a background task which is triggered every period. You cannot use this option together
768     * with completionTimeout, only one of them can be used.
769     *
770     * @param completionInterval  the interval in millis, must be a positive value
771     * @return the builder
772     */
773    public AggregateDefinition completionInterval(long completionInterval) {
774        setCompletionInterval(completionInterval);
775        return this;
776    }
777
778    /**
779     * Time in millis that an aggregated exchange should be inactive before its complete (timeout).
780     * This option can be set as either a fixed value or using an Expression which allows you to evaluate
781     * a timeout dynamically - will use Long as result.
782     * If both are set Camel will fallback to use the fixed value if the Expression result was null or 0.
783     * You cannot use this option together with completionInterval, only one of the two can be used.
784     * <p/>
785     * By default the timeout checker runs every second, you can use the completionTimeoutCheckerInterval option
786     * to configure how frequently to run the checker.
787     * The timeout is an approximation and there is no guarantee that the a timeout is triggered exactly after the timeout value.
788     * It is not recommended to use very low timeout values or checker intervals.
789     *
790     * @param completionTimeout  the timeout in millis, must be a positive value
791     * @return the builder
792     */
793    public AggregateDefinition completionTimeout(long completionTimeout) {
794        setCompletionTimeout(completionTimeout);
795        return this;
796    }
797
798    /**
799     * Time in millis that an aggregated exchange should be inactive before its complete (timeout).
800     * This option can be set as either a fixed value or using an Expression which allows you to evaluate
801     * a timeout dynamically - will use Long as result.
802     * If both are set Camel will fallback to use the fixed value if the Expression result was null or 0.
803     * You cannot use this option together with completionInterval, only one of the two can be used.
804     * <p/>
805     * By default the timeout checker runs every second, you can use the completionTimeoutCheckerInterval option
806     * to configure how frequently to run the checker.
807     * The timeout is an approximation and there is no guarantee that the a timeout is triggered exactly after the timeout value.
808     * It is not recommended to use very low timeout values or checker intervals.
809     *
810     * @param completionTimeout  the timeout as an {@link Expression} which is evaluated as a {@link Long} type
811     * @return the builder
812     */
813    public AggregateDefinition completionTimeout(Expression completionTimeout) {
814        setCompletionTimeoutExpression(new ExpressionSubElementDefinition(completionTimeout));
815        return this;
816    }
817
818    /**
819     * Interval in millis that is used by the background task that checks for timeouts ({@link org.apache.camel.TimeoutMap}).
820     * <p/>
821     * By default the timeout checker runs every second.
822     * The timeout is an approximation and there is no guarantee that the a timeout is triggered exactly after the timeout value.
823     * It is not recommended to use very low timeout values or checker intervals.
824     *
825     * @param completionTimeoutCheckerInterval  the interval in millis, must be a positive value
826     * @return the builder
827     */
828    public AggregateDefinition completionTimeoutCheckerInterval(long completionTimeoutCheckerInterval) {
829        setCompletionTimeoutCheckerInterval(completionTimeoutCheckerInterval);
830        return this;
831    }
832
833    /**
834     * Sets the AggregationStrategy to use with a fluent builder.
835     */
836    public AggregationStrategyClause<AggregateDefinition> aggregationStrategy() {
837        AggregationStrategyClause<AggregateDefinition> clause = new AggregationStrategyClause<>(this);
838        setAggregationStrategy(clause);
839        return clause;
840    }
841
842    /**
843     * Sets the AggregationStrategy to use with a fluent builder.
844     */
845    public AggregationStrategyClause<AggregateDefinition> strategy() {
846        return aggregationStrategy();
847    }
848
849    /**
850     * Sets the aggregate strategy to use
851     *
852     * @param aggregationStrategy  the aggregate strategy to use
853     * @return the builder
854     */
855    public AggregateDefinition strategy(AggregationStrategy aggregationStrategy) {
856        return aggregationStrategy(aggregationStrategy);
857    }
858
859    /**
860     * Sets the aggregate strategy to use
861     *
862     * @param aggregationStrategy  the aggregate strategy to use
863     * @return the builder
864     */
865    public AggregateDefinition aggregationStrategy(AggregationStrategy aggregationStrategy) {
866        setAggregationStrategy(aggregationStrategy);
867        return this;
868    }
869
870    /**
871     * Sets the aggregate strategy to use
872     *
873     * @param aggregationStrategyRef  reference to the strategy to lookup in the registry
874     * @return the builder
875     */
876    public AggregateDefinition aggregationStrategyRef(String aggregationStrategyRef) {
877        setAggregationStrategyRef(aggregationStrategyRef);
878        return this;
879    }
880
881    /**
882     * Sets the method name to use when using a POJO as {@link AggregationStrategy}.
883     *
884     * @param  methodName the method name to call
885     * @return the builder
886     */
887    public AggregateDefinition aggregationStrategyMethodName(String methodName) {
888        setAggregationStrategyMethodName(methodName);
889        return this;
890    }
891
892    /**
893     * Sets allowing null when using a POJO as {@link AggregationStrategy}.
894     *
895     * @return the builder
896     */
897    public AggregateDefinition aggregationStrategyMethodAllowNull() {
898        setStrategyMethodAllowNull(true);
899        return this;
900    }
901
902    /**
903     * Sets the custom aggregate repository to use.
904     * <p/>
905     * Will by default use {@link org.apache.camel.processor.aggregate.MemoryAggregationRepository}
906     *
907     * @param aggregationRepository  the aggregate repository to use
908     * @return the builder
909     */
910    public AggregateDefinition aggregationRepository(AggregationRepository aggregationRepository) {
911        setAggregationRepository(aggregationRepository);
912        return this;
913    }
914
915    /**
916     * Sets the custom aggregate repository to use
917     * <p/>
918     * Will by default use {@link org.apache.camel.processor.aggregate.MemoryAggregationRepository}
919     *
920     * @param aggregationRepositoryRef  reference to the repository to lookup in the registry
921     * @return the builder
922     */
923    public AggregateDefinition aggregationRepositoryRef(String aggregationRepositoryRef) {
924        setAggregationRepositoryRef(aggregationRepositoryRef);
925        return this;
926    }
927
928    /**
929     * Enables grouped exchanges, so the aggregator will group all aggregated exchanges into a single
930     * combined Exchange holding all the aggregated exchanges in a {@link java.util.List}.
931     *
932     * @deprecated use {@link GroupedExchangeAggregationStrategy} as aggregation strategy instead.
933     */
934    @Deprecated
935    public AggregateDefinition groupExchanges() {
936        setGroupExchanges(true);
937        // must use eager check when using grouped exchanges
938        setEagerCheckCompletion(true);
939        return this;
940    }
941
942    /**
943     * A Predicate to indicate when an aggregated exchange is complete.
944     * If this is not specified and the AggregationStrategy object implements Predicate,
945     * the aggregationStrategy object will be used as the completionPredicate.
946     */
947    public AggregateDefinition completionPredicate(@AsPredicate Predicate predicate) {
948        checkNoCompletedPredicate();
949        setCompletionPredicate(new ExpressionSubElementDefinition(predicate));
950        return this;
951    }
952
953    /**
954     * A Predicate to indicate when an aggregated exchange is complete.
955     * If this is not specified and the AggregationStrategy object implements Predicate,
956     * the aggregationStrategy object will be used as the completionPredicate.
957     */
958    @AsPredicate
959    public PredicateClause<AggregateDefinition> completionPredicate() {
960        PredicateClause<AggregateDefinition> clause = new PredicateClause<>(this);
961        completionPredicate(clause);
962        return clause;
963    }
964
965    /**
966     * A Predicate to indicate when an aggregated exchange is complete.
967     * If this is not specified and the AggregationStrategy object implements Predicate,
968     * the aggregationStrategy object will be used as the completionPredicate.
969     */
970    @AsPredicate
971    public PredicateClause<AggregateDefinition> completion() {
972        return completionPredicate();
973    }
974
975    /**
976     * A Predicate to indicate when an aggregated exchange is complete.
977     * If this is not specified and the AggregationStrategy object implements Predicate,
978     * the aggregationStrategy object will be used as the completionPredicate.
979     */
980    public AggregateDefinition completion(@AsPredicate Predicate predicate) {
981        return completionPredicate(predicate);
982    }
983
984    /**
985     * Indicates to complete all current aggregated exchanges when the context is stopped
986     */
987    public AggregateDefinition forceCompletionOnStop() {
988        setForceCompletionOnStop(true);
989        return this;
990    }
991
992    /**
993     * Indicates to wait to complete all current and partial (pending) aggregated exchanges when the context is stopped.
994     * <p/>
995     * This also means that we will wait for all pending exchanges which are stored in the aggregation repository
996     * to complete so the repository is empty before we can stop.
997     * <p/>
998     * You may want to enable this when using the memory based aggregation repository that is memory based only,
999     * and do not store data on disk. When this option is enabled, then the aggregator is waiting to complete
1000     * all those exchanges before its stopped, when stopping CamelContext or the route using it.
1001     */
1002    public AggregateDefinition completeAllOnStop() {
1003        setCompleteAllOnStop(true);
1004        return this;
1005    }
1006
1007    /**
1008     * When aggregated are completed they are being send out of the aggregator.
1009     * This option indicates whether or not Camel should use a thread pool with multiple threads for concurrency.
1010     * If no custom thread pool has been specified then Camel creates a default pool with 10 concurrent threads.
1011     */
1012    public AggregateDefinition parallelProcessing() {
1013        setParallelProcessing(true);
1014        return this;
1015    }
1016
1017    /**
1018     * When aggregated are completed they are being send out of the aggregator.
1019     * This option indicates whether or not Camel should use a thread pool with multiple threads for concurrency.
1020     * If no custom thread pool has been specified then Camel creates a default pool with 10 concurrent threads.
1021     */
1022    public AggregateDefinition parallelProcessing(boolean parallelProcessing) {
1023        setParallelProcessing(parallelProcessing);
1024        return this;
1025    }
1026
1027    /**
1028     * Turns on using optimistic locking, which requires the aggregationRepository being used,
1029     * is supporting this by implementing {@link org.apache.camel.spi.OptimisticLockingAggregationRepository}.
1030     */
1031    public AggregateDefinition optimisticLocking() {
1032        setOptimisticLocking(true);
1033        return this;
1034    }
1035
1036    /**
1037     * Allows to configure retry settings when using optimistic locking.
1038     */
1039    public AggregateDefinition optimisticLockRetryPolicy(OptimisticLockRetryPolicy policy) {
1040        setOptimisticLockRetryPolicy(policy);
1041        return this;
1042    }
1043
1044    /**
1045     * If using parallelProcessing you can specify a custom thread pool to be used.
1046     * In fact also if you are not using parallelProcessing this custom thread pool is used to send out aggregated exchanges as well.
1047     */
1048    public AggregateDefinition executorService(ExecutorService executorService) {
1049        setExecutorService(executorService);
1050        return this;
1051    }
1052
1053    /**
1054     * If using parallelProcessing you can specify a custom thread pool to be used.
1055     * In fact also if you are not using parallelProcessing this custom thread pool is used to send out aggregated exchanges as well.
1056     */
1057    public AggregateDefinition executorServiceRef(String executorServiceRef) {
1058        setExecutorServiceRef(executorServiceRef);
1059        return this;
1060    }
1061
1062    /**
1063     * If using either of the completionTimeout, completionTimeoutExpression, or completionInterval options a
1064     * background thread is created to check for the completion for every aggregator.
1065     * Set this option to provide a custom thread pool to be used rather than creating a new thread for every aggregator.
1066     */
1067    public AggregateDefinition timeoutCheckerExecutorService(ScheduledExecutorService executorService) {
1068        setTimeoutCheckerExecutorService(executorService);
1069        return this;
1070    }
1071
1072    /**
1073     * If using either of the completionTimeout, completionTimeoutExpression, or completionInterval options a
1074     * background thread is created to check for the completion for every aggregator.
1075     * Set this option to provide a custom thread pool to be used rather than creating a new thread for every aggregator.
1076     */
1077    public AggregateDefinition timeoutCheckerExecutorServiceRef(String executorServiceRef) {
1078        setTimeoutCheckerExecutorServiceRef(executorServiceRef);
1079        return this;
1080    }
1081
1082    /**
1083     * To use a {@link org.apache.camel.processor.aggregate.AggregateController} to allow external sources to control
1084     * this aggregator.
1085     */
1086    public AggregateDefinition aggregateController(AggregateController aggregateController) {
1087        setAggregateController(aggregateController);
1088        return this;
1089    }
1090
1091    // Section - Methods from ExpressionNode
1092    // Needed to copy methods from ExpressionNode here so that I could specify the
1093    // correlation expression as optional in JAXB
1094
1095    public ExpressionDefinition getExpression() {
1096        if (expression == null && correlationExpression != null) {
1097            expression = correlationExpression.getExpressionType();            
1098        }
1099        return expression;
1100    }
1101
1102    public void setExpression(ExpressionDefinition expression) {
1103        this.expression = expression;
1104    }
1105
1106    protected void checkNoCompletedPredicate() {
1107        if (getCompletionPredicate() != null) {
1108            throw new IllegalArgumentException("There is already a completionPredicate defined for this aggregator: " + this);
1109        }
1110    }
1111
1112    @Override
1113    public List<ProcessorDefinition<?>> getOutputs() {
1114        return outputs;
1115    }
1116
1117    public boolean isOutputSupported() {
1118        return true;
1119    }
1120
1121    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
1122        this.outputs = outputs;
1123    }
1124
1125}