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