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