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