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.processor;
018
019import java.io.Closeable;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.concurrent.Callable;
026import java.util.concurrent.CompletionService;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.ConcurrentMap;
029import java.util.concurrent.CountDownLatch;
030import java.util.concurrent.ExecutionException;
031import java.util.concurrent.ExecutorCompletionService;
032import java.util.concurrent.ExecutorService;
033import java.util.concurrent.Future;
034import java.util.concurrent.TimeUnit;
035import java.util.concurrent.atomic.AtomicBoolean;
036import java.util.concurrent.atomic.AtomicInteger;
037
038import org.apache.camel.AsyncCallback;
039import org.apache.camel.AsyncProcessor;
040import org.apache.camel.CamelContext;
041import org.apache.camel.CamelContextAware;
042import org.apache.camel.CamelExchangeException;
043import org.apache.camel.Endpoint;
044import org.apache.camel.ErrorHandlerFactory;
045import org.apache.camel.Exchange;
046import org.apache.camel.Navigate;
047import org.apache.camel.Processor;
048import org.apache.camel.Producer;
049import org.apache.camel.StreamCache;
050import org.apache.camel.Traceable;
051import org.apache.camel.processor.aggregate.AggregationStrategy;
052import org.apache.camel.processor.aggregate.CompletionAwareAggregationStrategy;
053import org.apache.camel.processor.aggregate.DelegateAggregationStrategy;
054import org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy;
055import org.apache.camel.spi.IdAware;
056import org.apache.camel.spi.RouteContext;
057import org.apache.camel.spi.TracedRouteNodes;
058import org.apache.camel.spi.UnitOfWork;
059import org.apache.camel.support.ServiceSupport;
060import org.apache.camel.util.AsyncProcessorConverterHelper;
061import org.apache.camel.util.AsyncProcessorHelper;
062import org.apache.camel.util.CastUtils;
063import org.apache.camel.util.EventHelper;
064import org.apache.camel.util.ExchangeHelper;
065import org.apache.camel.util.IOHelper;
066import org.apache.camel.util.KeyValueHolder;
067import org.apache.camel.util.ObjectHelper;
068import org.apache.camel.util.ServiceHelper;
069import org.apache.camel.util.StopWatch;
070import org.apache.camel.util.concurrent.AtomicException;
071import org.apache.camel.util.concurrent.AtomicExchange;
072import org.apache.camel.util.concurrent.SubmitOrderedCompletionService;
073import org.slf4j.Logger;
074import org.slf4j.LoggerFactory;
075
076import static org.apache.camel.util.ObjectHelper.notNull;
077
078
079/**
080 * Implements the Multicast pattern to send a message exchange to a number of
081 * endpoints, each endpoint receiving a copy of the message exchange.
082 *
083 * @version
084 * @see Pipeline
085 */
086public class MulticastProcessor extends ServiceSupport implements AsyncProcessor, Navigate<Processor>, Traceable, IdAware {
087
088    private static final Logger LOG = LoggerFactory.getLogger(MulticastProcessor.class);
089
090    /**
091     * Class that represent each step in the multicast route to do
092     */
093    static final class DefaultProcessorExchangePair implements ProcessorExchangePair {
094        private final int index;
095        private final Processor processor;
096        private final Processor prepared;
097        private final Exchange exchange;
098
099        private DefaultProcessorExchangePair(int index, Processor processor, Processor prepared, Exchange exchange) {
100            this.index = index;
101            this.processor = processor;
102            this.prepared = prepared;
103            this.exchange = exchange;
104        }
105
106        public int getIndex() {
107            return index;
108        }
109
110        public Exchange getExchange() {
111            return exchange;
112        }
113
114        public Producer getProducer() {
115            if (processor instanceof Producer) {
116                return (Producer) processor;
117            }
118            return null;
119        }
120
121        public Processor getProcessor() {
122            return prepared;
123        }
124
125        public void begin() {
126            // noop
127        }
128
129        public void done() {
130            // noop
131        }
132
133    }
134
135    /**
136     * Class that represents prepared fine grained error handlers when processing multicasted/splitted exchanges
137     * <p/>
138     * See the <tt>createProcessorExchangePair</tt> and <tt>createErrorHandler</tt> methods.
139     */
140    static final class PreparedErrorHandler extends KeyValueHolder<RouteContext, Processor> {
141
142        PreparedErrorHandler(RouteContext key, Processor value) {
143            super(key, value);
144        }
145
146    }
147
148    protected final Processor onPrepare;
149    private final CamelContext camelContext;
150    private String id;
151    private Collection<Processor> processors;
152    private final AggregationStrategy aggregationStrategy;
153    private final boolean parallelProcessing;
154    private final boolean streaming;
155    private final boolean parallelAggregate;
156    private final boolean stopOnAggregateException;
157    private final boolean stopOnException;
158    private final ExecutorService executorService;
159    private final boolean shutdownExecutorService;
160    private ExecutorService aggregateExecutorService;
161    private final long timeout;
162    private final ConcurrentMap<PreparedErrorHandler, Processor> errorHandlers = new ConcurrentHashMap<>();
163    private final boolean shareUnitOfWork;
164
165    public MulticastProcessor(CamelContext camelContext, Collection<Processor> processors) {
166        this(camelContext, processors, null);
167    }
168
169    public MulticastProcessor(CamelContext camelContext, Collection<Processor> processors, AggregationStrategy aggregationStrategy) {
170        this(camelContext, processors, aggregationStrategy, false, null, false, false, false, 0, null, false, false);
171    }
172
173    @Deprecated
174    public MulticastProcessor(CamelContext camelContext, Collection<Processor> processors, AggregationStrategy aggregationStrategy,
175                              boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService,
176                              boolean streaming, boolean stopOnException, long timeout, Processor onPrepare, boolean shareUnitOfWork) {
177        this(camelContext, processors, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService,
178                streaming, stopOnException, timeout, onPrepare, shareUnitOfWork, false);
179    }
180
181    public MulticastProcessor(CamelContext camelContext, Collection<Processor> processors, AggregationStrategy aggregationStrategy, boolean parallelProcessing,
182                              ExecutorService executorService, boolean shutdownExecutorService, boolean streaming, boolean stopOnException, long timeout, Processor onPrepare,
183                              boolean shareUnitOfWork, boolean parallelAggregate) {
184        this(camelContext, processors, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService, streaming, stopOnException, timeout, onPrepare,
185             shareUnitOfWork, false, false);
186    }
187    
188    public MulticastProcessor(CamelContext camelContext, Collection<Processor> processors, AggregationStrategy aggregationStrategy,
189                              boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, boolean streaming,
190                              boolean stopOnException, long timeout, Processor onPrepare, boolean shareUnitOfWork,
191                              boolean parallelAggregate, boolean stopOnAggregateException) {
192        notNull(camelContext, "camelContext");
193        this.camelContext = camelContext;
194        this.processors = processors;
195        this.aggregationStrategy = aggregationStrategy;
196        this.executorService = executorService;
197        this.shutdownExecutorService = shutdownExecutorService;
198        this.streaming = streaming;
199        this.stopOnException = stopOnException;
200        // must enable parallel if executor service is provided
201        this.parallelProcessing = parallelProcessing || executorService != null;
202        this.timeout = timeout;
203        this.onPrepare = onPrepare;
204        this.shareUnitOfWork = shareUnitOfWork;
205        this.parallelAggregate = parallelAggregate;
206        this.stopOnAggregateException = stopOnAggregateException;
207    }
208
209    @Override
210    public String toString() {
211        return "Multicast[" + getProcessors() + "]";
212    }
213
214    public String getId() {
215        return id;
216    }
217
218    public void setId(String id) {
219        this.id = id;
220    }
221
222    public String getTraceLabel() {
223        return "multicast";
224    }
225
226    public CamelContext getCamelContext() {
227        return camelContext;
228    }
229
230    public void process(Exchange exchange) throws Exception {
231        AsyncProcessorHelper.process(this, exchange);
232    }
233
234    public boolean process(Exchange exchange, AsyncCallback callback) {
235        final AtomicExchange result = new AtomicExchange();
236        Iterable<ProcessorExchangePair> pairs = null;
237
238        try {
239            boolean sync = true;
240
241            pairs = createProcessorExchangePairs(exchange);
242
243            if (isParallelProcessing()) {
244                // ensure an executor is set when running in parallel
245                ObjectHelper.notNull(executorService, "executorService", this);
246                doProcessParallel(exchange, result, pairs, isStreaming(), callback);
247            } else {
248                sync = doProcessSequential(exchange, result, pairs, callback);
249            }
250
251            if (!sync) {
252                // the remainder of the multicast will be completed async
253                // so we break out now, then the callback will be invoked which then continue routing from where we left here
254                return false;
255            }
256        } catch (Throwable e) {
257            exchange.setException(e);
258            // unexpected exception was thrown, maybe from iterator etc. so do not regard as exhausted
259            // and do the done work
260            doDone(exchange, null, pairs, callback, true, false);
261            return true;
262        }
263
264        // multicasting was processed successfully
265        // and do the done work
266        Exchange subExchange = result.get() != null ? result.get() : null;
267        doDone(exchange, subExchange, pairs, callback, true, true);
268        return true;
269    }
270
271    protected void doProcessParallel(final Exchange original, final AtomicExchange result, final Iterable<ProcessorExchangePair> pairs,
272                                     final boolean streaming, final AsyncCallback callback) throws Exception {
273
274        ObjectHelper.notNull(executorService, "ExecutorService", this);
275        ObjectHelper.notNull(aggregateExecutorService, "AggregateExecutorService", this);
276
277        final CompletionService<Exchange> completion;
278        if (streaming) {
279            // execute tasks in parallel+streaming and aggregate in the order they are finished (out of order sequence)
280            completion = new ExecutorCompletionService<>(executorService);
281        } else {
282            // execute tasks in parallel and aggregate in the order the tasks are submitted (in order sequence)
283            completion = new SubmitOrderedCompletionService<>(executorService);
284        }
285
286        final AtomicInteger total = new AtomicInteger(0);
287        final Iterator<ProcessorExchangePair> it = pairs.iterator();
288
289        if (it.hasNext()) {
290            // when parallel then aggregate on the fly
291            final AtomicBoolean running = new AtomicBoolean(true);
292            final AtomicBoolean allTasksSubmitted = new AtomicBoolean();
293            final CountDownLatch aggregationOnTheFlyDone = new CountDownLatch(1);
294            final AtomicException executionException = new AtomicException();
295
296            // issue task to execute in separate thread so it can aggregate on-the-fly
297            // while we submit new tasks, and those tasks complete concurrently
298            // this allows us to optimize work and reduce memory consumption
299            final AggregateOnTheFlyTask aggregateOnTheFlyTask = new AggregateOnTheFlyTask(result, original, total, completion, running,
300                    aggregationOnTheFlyDone, allTasksSubmitted, executionException);
301            final AtomicBoolean aggregationTaskSubmitted = new AtomicBoolean();
302
303            LOG.trace("Starting to submit parallel tasks");
304            
305            try {
306                while (it.hasNext()) {
307                    final ProcessorExchangePair pair = it.next();
308                    // in case the iterator returns null then continue to next
309                    if (pair == null) {
310                        continue;
311                    }
312    
313                    final Exchange subExchange = pair.getExchange();
314                    updateNewExchange(subExchange, total.intValue(), pairs, it);
315    
316                    completion.submit(new Callable<Exchange>() {
317                        public Exchange call() throws Exception {
318                            // start the aggregation task at this stage only in order not to pile up too many threads
319                            if (aggregationTaskSubmitted.compareAndSet(false, true)) {
320                                // but only submit the aggregation task once
321                                aggregateExecutorService.submit(aggregateOnTheFlyTask);
322                            }
323    
324                            if (!running.get()) {
325                                // do not start processing the task if we are not running
326                                return subExchange;
327                            }
328    
329                            try {
330                                doProcessParallel(pair);
331                            } catch (Throwable e) {
332                                subExchange.setException(e);
333                            }
334    
335                            // Decide whether to continue with the multicast or not; similar logic to the Pipeline
336                            Integer number = getExchangeIndex(subExchange);
337                            boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, "Parallel processing failed for number " + number, LOG);
338                            if (stopOnException && !continueProcessing) {
339                                // signal to stop running
340                                running.set(false);
341                                // throw caused exception
342                                if (subExchange.getException() != null) {
343                                    // wrap in exception to explain where it failed
344                                    CamelExchangeException cause = new CamelExchangeException("Parallel processing failed for number " + number, subExchange, subExchange.getException());
345                                    subExchange.setException(cause);
346                                }
347                            }
348    
349                            LOG.trace("Parallel processing complete for exchange: {}", subExchange);
350                            return subExchange;
351                        }
352                    });
353    
354                    total.incrementAndGet();
355                }
356            } catch (Throwable e) {
357                // The methods it.hasNext and it.next can throw RuntimeExceptions when custom iterators are implemented.
358                // We have to catch the exception here otherwise the aggregator threads would pile up.
359                if (e instanceof Exception) {
360                    executionException.set((Exception) e);
361                } else {
362                    executionException.set(ObjectHelper.wrapRuntimeCamelException(e));
363                }
364                // and because of the exception we must signal we are done so the latch can open and let the other thread continue processing
365                LOG.debug("Signaling we are done aggregating on the fly for exchangeId: {}", original.getExchangeId());
366                LOG.trace("Aggregate on the fly task done for exchangeId: {}", original.getExchangeId());
367                aggregationOnTheFlyDone.countDown();
368            }
369
370            // signal all tasks has been submitted
371            LOG.trace("Signaling that all {} tasks has been submitted.", total.get());
372            allTasksSubmitted.set(true);
373
374            // its to hard to do parallel async routing so we let the caller thread be synchronously
375            // and have it pickup the replies and do the aggregation (eg we use a latch to wait)
376            // wait for aggregation to be done
377            LOG.debug("Waiting for on-the-fly aggregation to complete aggregating {} responses for exchangeId: {}", total.get(), original.getExchangeId());
378            aggregationOnTheFlyDone.await();
379
380            // did we fail for whatever reason, if so throw that caused exception
381            if (executionException.get() != null) {
382                if (LOG.isDebugEnabled()) {
383                    LOG.debug("Parallel processing failed due {}", executionException.get().getMessage());
384                }
385                throw executionException.get();
386            }
387        }
388
389        // no everything is okay so we are done
390        LOG.debug("Done parallel processing {} exchanges", total);
391    }
392
393    /**
394     * Boss worker to control aggregate on-the-fly for completed tasks when using parallel processing.
395     * <p/>
396     * This ensures lower memory consumption as we do not need to keep all completed tasks in memory
397     * before we perform aggregation. Instead this separate thread will run and aggregate when new
398     * completed tasks is done.
399     * <p/>
400     * The logic is fairly complex as this implementation has to keep track how far it got, and also
401     * signal back to the <i>main</t> thread when its done, so the <i>main</t> thread can continue
402     * processing when the entire splitting is done.
403     */
404    private final class AggregateOnTheFlyTask implements Runnable {
405
406        private final AtomicExchange result;
407        private final Exchange original;
408        private final AtomicInteger total;
409        private final CompletionService<Exchange> completion;
410        private final AtomicBoolean running;
411        private final CountDownLatch aggregationOnTheFlyDone;
412        private final AtomicBoolean allTasksSubmitted;
413        private final AtomicException executionException;
414
415        private AggregateOnTheFlyTask(AtomicExchange result, Exchange original, AtomicInteger total,
416                                      CompletionService<Exchange> completion, AtomicBoolean running,
417                                      CountDownLatch aggregationOnTheFlyDone, AtomicBoolean allTasksSubmitted,
418                                      AtomicException executionException) {
419            this.result = result;
420            this.original = original;
421            this.total = total;
422            this.completion = completion;
423            this.running = running;
424            this.aggregationOnTheFlyDone = aggregationOnTheFlyDone;
425            this.allTasksSubmitted = allTasksSubmitted;
426            this.executionException = executionException;
427        }
428
429        public void run() {
430            LOG.trace("Aggregate on the fly task started for exchangeId: {}", original.getExchangeId());
431
432            try {
433                aggregateOnTheFly();
434            } catch (Throwable e) {
435                if (e instanceof Exception) {
436                    executionException.set((Exception) e);
437                } else {
438                    executionException.set(ObjectHelper.wrapRuntimeCamelException(e));
439                }
440            } finally {
441                // must signal we are done so the latch can open and let the other thread continue processing
442                LOG.debug("Signaling we are done aggregating on the fly for exchangeId: {}", original.getExchangeId());
443                LOG.trace("Aggregate on the fly task done for exchangeId: {}", original.getExchangeId());
444                aggregationOnTheFlyDone.countDown();
445            }
446        }
447
448        private void aggregateOnTheFly() throws InterruptedException, ExecutionException {
449            final AtomicBoolean timedOut = new AtomicBoolean();
450            boolean stoppedOnException = false;
451            final StopWatch watch = new StopWatch();
452            final AtomicInteger aggregated = new AtomicInteger();
453            boolean done = false;
454            // not a for loop as on the fly may still run
455            while (!done) {
456                // check if we have already aggregate everything
457                if (allTasksSubmitted.get() && aggregated.intValue() >= total.get()) {
458                    LOG.debug("Done aggregating {} exchanges on the fly.", aggregated);
459                    break;
460                }
461
462                Future<Exchange> future;
463                if (timedOut.get()) {
464                    // we are timed out but try to grab if some tasks has been completed
465                    // poll will return null if no tasks is present
466                    future = completion.poll();
467                    LOG.trace("Polled completion task #{} after timeout to grab already completed tasks: {}", aggregated, future);
468                } else if (timeout > 0) {
469                    long left = timeout - watch.taken();
470                    if (left < 0) {
471                        left = 0;
472                    }
473                    LOG.trace("Polling completion task #{} using timeout {} millis.", aggregated, left);
474                    future = completion.poll(left, TimeUnit.MILLISECONDS);
475                } else {
476                    LOG.trace("Polling completion task #{}", aggregated);
477                    // we must not block so poll every second
478                    future = completion.poll(1, TimeUnit.SECONDS);
479                    if (future == null) {
480                        // and continue loop which will recheck if we are done
481                        continue;
482                    }
483                }
484
485                if (future == null) {
486                    ParallelAggregateTimeoutTask task = new ParallelAggregateTimeoutTask(original, result, completion, aggregated, total, timedOut);
487                    if (parallelAggregate) {
488                        aggregateExecutorService.submit(task);
489                    } else {
490                        // in non parallel mode then just run the task
491                        task.run();
492                    }
493                } else {
494                    // there is a result to aggregate
495                    Exchange subExchange = future.get();
496
497                    // Decide whether to continue with the multicast or not; similar logic to the Pipeline
498                    Integer number = getExchangeIndex(subExchange);
499                    boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, "Parallel processing failed for number " + number, LOG);
500                    if (stopOnException && !continueProcessing) {
501                        // we want to stop on exception and an exception or failure occurred
502                        // this is similar to what the pipeline does, so we should do the same to not surprise end users
503                        // so we should set the failed exchange as the result and break out
504                        result.set(subExchange);
505                        stoppedOnException = true;
506                        break;
507                    }
508
509                    // we got a result so aggregate it
510                    ParallelAggregateTask task = new ParallelAggregateTask(result, subExchange, aggregated);
511                    if (parallelAggregate) {
512                        aggregateExecutorService.submit(task);
513                    } else {
514                        // in non parallel mode then just run the task
515                        task.run();
516                    }
517                }
518            }
519
520            if (timedOut.get() || stoppedOnException) {
521                if (timedOut.get()) {
522                    LOG.debug("Cancelling tasks due timeout after {} millis.", timeout);
523                }
524                if (stoppedOnException) {
525                    LOG.debug("Cancelling tasks due stopOnException.");
526                }
527                // cancel tasks as we timed out (its safe to cancel done tasks)
528                running.set(false);
529            }
530        }
531    }
532
533    /**
534     * Worker task to aggregate the old and new exchange on-the-fly for completed tasks when using parallel processing.
535     */
536    private final class ParallelAggregateTask implements Runnable {
537
538        private final AtomicExchange result;
539        private final Exchange subExchange;
540        private final AtomicInteger aggregated;
541
542        private ParallelAggregateTask(AtomicExchange result, Exchange subExchange, AtomicInteger aggregated) {
543            this.result = result;
544            this.subExchange = subExchange;
545            this.aggregated = aggregated;
546        }
547
548        @Override
549        public void run() {
550            try {
551                if (parallelAggregate) {
552                    doAggregateInternal(getAggregationStrategy(subExchange), result, subExchange);
553                } else {
554                    doAggregate(getAggregationStrategy(subExchange), result, subExchange);
555                }
556            } catch (Throwable e) {
557                if (isStopOnAggregateException()) {
558                    throw e;
559                } else {
560                    // wrap in exception to explain where it failed
561                    CamelExchangeException cex = new CamelExchangeException("Parallel processing failed for number " + aggregated.get(), subExchange, e);
562                    subExchange.setException(cex);
563                    LOG.debug(cex.getMessage(), cex);
564                }
565            } finally {
566                aggregated.incrementAndGet();
567            }
568        }
569    }
570
571    /**
572     * Worker task to aggregate the old and new exchange on-the-fly for completed tasks when using parallel processing.
573     */
574    private final class ParallelAggregateTimeoutTask implements Runnable {
575
576        private final Exchange original;
577        private final AtomicExchange result;
578        private final CompletionService<Exchange> completion;
579        private final AtomicInteger aggregated;
580        private final AtomicInteger total;
581        private final AtomicBoolean timedOut;
582
583        private ParallelAggregateTimeoutTask(Exchange original, AtomicExchange result, CompletionService<Exchange> completion,
584                                             AtomicInteger aggregated, AtomicInteger total, AtomicBoolean timedOut) {
585            this.original = original;
586            this.result = result;
587            this.completion = completion;
588            this.aggregated = aggregated;
589            this.total = total;
590            this.timedOut = timedOut;
591        }
592
593        @Override
594        public void run() {
595            AggregationStrategy strategy = getAggregationStrategy(null);
596            if (strategy instanceof DelegateAggregationStrategy) {
597                strategy = ((DelegateAggregationStrategy) strategy).getDelegate();
598            }
599            if (strategy instanceof TimeoutAwareAggregationStrategy) {
600                // notify the strategy we timed out
601                Exchange oldExchange = result.get();
602                if (oldExchange == null) {
603                    // if they all timed out the result may not have been set yet, so use the original exchange
604                    oldExchange = original;
605                }
606                ((TimeoutAwareAggregationStrategy) strategy).timeout(oldExchange, aggregated.intValue(), total.intValue(), timeout);
607            } else {
608                // log a WARN we timed out since it will not be aggregated and the Exchange will be lost
609                LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated.intValue());
610            }
611            LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated.intValue());
612            timedOut.set(true);
613
614            // mark that index as timed out, which allows us to try to retrieve
615            // any already completed tasks in the next loop
616            if (completion instanceof SubmitOrderedCompletionService) {
617                ((SubmitOrderedCompletionService<?>) completion).timeoutTask();
618            }
619
620            // we timed out so increment the counter
621            aggregated.incrementAndGet();
622        }
623    }
624
625    protected boolean doProcessSequential(Exchange original, AtomicExchange result, Iterable<ProcessorExchangePair> pairs, AsyncCallback callback) throws Exception {
626        AtomicInteger total = new AtomicInteger();
627        Iterator<ProcessorExchangePair> it = pairs.iterator();
628
629        while (it.hasNext()) {
630            ProcessorExchangePair pair = it.next();
631            // in case the iterator returns null then continue to next
632            if (pair == null) {
633                continue;
634            }
635            Exchange subExchange = pair.getExchange();
636            updateNewExchange(subExchange, total.get(), pairs, it);
637
638            boolean sync = doProcessSequential(original, result, pairs, it, pair, callback, total);
639            if (!sync) {
640                if (LOG.isTraceEnabled()) {
641                    LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", pair.getExchange().getExchangeId());
642                }
643                // the remainder of the multicast will be completed async
644                // so we break out now, then the callback will be invoked which then continue routing from where we left here
645                return false;
646            }
647
648            if (LOG.isTraceEnabled()) {
649                LOG.trace("Processing exchangeId: {} is continued being processed synchronously", pair.getExchange().getExchangeId());
650            }
651
652            // Decide whether to continue with the multicast or not; similar logic to the Pipeline
653            // remember to test for stop on exception and aggregate before copying back results
654            boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, "Sequential processing failed for number " + total.get(), LOG);
655            if (stopOnException && !continueProcessing) {
656                if (subExchange.getException() != null) {
657                    // wrap in exception to explain where it failed
658                    CamelExchangeException cause = new CamelExchangeException("Sequential processing failed for number " + total.get(), subExchange, subExchange.getException());
659                    subExchange.setException(cause);
660                }
661                // we want to stop on exception, and the exception was handled by the error handler
662                // this is similar to what the pipeline does, so we should do the same to not surprise end users
663                // so we should set the failed exchange as the result and be done
664                result.set(subExchange);
665                return true;
666            }
667
668            LOG.trace("Sequential processing complete for number {} exchange: {}", total, subExchange);
669
670            if (parallelAggregate) {
671                doAggregateInternal(getAggregationStrategy(subExchange), result, subExchange);
672            } else {
673                doAggregate(getAggregationStrategy(subExchange), result, subExchange);
674            }
675
676            total.incrementAndGet();
677        }
678
679        LOG.debug("Done sequential processing {} exchanges", total);
680
681        return true;
682    }
683
684    private boolean doProcessSequential(final Exchange original, final AtomicExchange result,
685                                        final Iterable<ProcessorExchangePair> pairs, final Iterator<ProcessorExchangePair> it,
686                                        final ProcessorExchangePair pair, final AsyncCallback callback, final AtomicInteger total) {
687        boolean sync = true;
688
689        final Exchange exchange = pair.getExchange();
690        Processor processor = pair.getProcessor();
691        final Producer producer = pair.getProducer();
692
693        TracedRouteNodes traced = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getTracedRouteNodes() : null;
694
695        try {
696            // prepare tracing starting from a new block
697            if (traced != null) {
698                traced.pushBlock();
699            }
700
701            StopWatch sw = null;
702            if (producer != null) {
703                boolean sending = EventHelper.notifyExchangeSending(exchange.getContext(), exchange, producer.getEndpoint());
704                if (sending) {
705                    sw = new StopWatch();
706                }
707            }
708
709            // compute time taken if sending to another endpoint
710            final StopWatch watch = sw;
711
712            // let the prepared process it, remember to begin the exchange pair
713            AsyncProcessor async = AsyncProcessorConverterHelper.convert(processor);
714            pair.begin();
715            sync = async.process(exchange, new AsyncCallback() {
716                public void done(boolean doneSync) {
717                    // we are done with the exchange pair
718                    pair.done();
719
720                    // okay we are done, so notify the exchange was sent
721                    if (producer != null && watch != null) {
722                        long timeTaken = watch.taken();
723                        Endpoint endpoint = producer.getEndpoint();
724                        // emit event that the exchange was sent to the endpoint
725                        EventHelper.notifyExchangeSent(exchange.getContext(), exchange, endpoint, timeTaken);
726                    }
727
728                    // we only have to handle async completion of the routing slip
729                    if (doneSync) {
730                        return;
731                    }
732
733                    // continue processing the multicast asynchronously
734                    Exchange subExchange = exchange;
735
736                    // Decide whether to continue with the multicast or not; similar logic to the Pipeline
737                    // remember to test for stop on exception and aggregate before copying back results
738                    boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, "Sequential processing failed for number " + total.get(), LOG);
739                    if (stopOnException && !continueProcessing) {
740                        if (subExchange.getException() != null) {
741                            // wrap in exception to explain where it failed
742                            subExchange.setException(new CamelExchangeException("Sequential processing failed for number " + total, subExchange, subExchange.getException()));
743                        } else {
744                            // we want to stop on exception, and the exception was handled by the error handler
745                            // this is similar to what the pipeline does, so we should do the same to not surprise end users
746                            // so we should set the failed exchange as the result and be done
747                            result.set(subExchange);
748                        }
749                        // and do the done work
750                        doDone(original, subExchange, pairs, callback, false, true);
751                        return;
752                    }
753
754                    try {
755                        if (parallelAggregate) {
756                            doAggregateInternal(getAggregationStrategy(subExchange), result, subExchange);
757                        } else {
758                            doAggregate(getAggregationStrategy(subExchange), result, subExchange);
759                        }
760                    } catch (Throwable e) {
761                        original.setException(e);
762                        // and do the done work
763                        doDone(original, null, pairs, callback, false, true);
764                        return;
765                    }
766
767                    total.incrementAndGet();
768
769                    // maybe there are more processors to multicast
770                    while (it.hasNext()) {
771
772                        // prepare and run the next
773                        ProcessorExchangePair pair = it.next();
774                        subExchange = pair.getExchange();
775                        updateNewExchange(subExchange, total.get(), pairs, it);
776                        boolean sync = doProcessSequential(original, result, pairs, it, pair, callback, total);
777
778                        if (!sync) {
779                            LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", original.getExchangeId());
780                            return;
781                        }
782
783                        // Decide whether to continue with the multicast or not; similar logic to the Pipeline
784                        // remember to test for stop on exception and aggregate before copying back results
785                        continueProcessing = PipelineHelper.continueProcessing(subExchange, "Sequential processing failed for number " + total.get(), LOG);
786                        if (stopOnException && !continueProcessing) {
787                            if (subExchange.getException() != null) {
788                                // wrap in exception to explain where it failed
789                                subExchange.setException(new CamelExchangeException("Sequential processing failed for number " + total, subExchange, subExchange.getException()));
790                            } else {
791                                // we want to stop on exception, and the exception was handled by the error handler
792                                // this is similar to what the pipeline does, so we should do the same to not surprise end users
793                                // so we should set the failed exchange as the result and be done
794                                result.set(subExchange);
795                            }
796                            // and do the done work
797                            doDone(original, subExchange, pairs, callback, false, true);
798                            return;
799                        }
800
801                        // must catch any exceptions from aggregation
802                        try {
803                            if (parallelAggregate) {
804                                doAggregateInternal(getAggregationStrategy(subExchange), result, subExchange);
805                            } else {
806                                doAggregate(getAggregationStrategy(subExchange), result, subExchange);
807                            }
808                        } catch (Throwable e) {
809                            // wrap in exception to explain where it failed
810                            subExchange.setException(new CamelExchangeException("Sequential processing failed for number " + total, subExchange, e));
811                            // and do the done work
812                            doDone(original, subExchange, pairs, callback, false, true);
813                            return;
814                        }
815
816                        total.incrementAndGet();
817                    }
818
819                    // do the done work
820                    subExchange = result.get() != null ? result.get() : null;
821                    doDone(original, subExchange, pairs, callback, false, true);
822                }
823            });
824        } finally {
825            // pop the block so by next round we have the same staring point and thus the tracing looks accurate
826            if (traced != null) {
827                traced.popBlock();
828            }
829        }
830
831        return sync;
832    }
833
834    private void doProcessParallel(final ProcessorExchangePair pair) throws Exception {
835        final Exchange exchange = pair.getExchange();
836        Processor processor = pair.getProcessor();
837        Producer producer = pair.getProducer();
838
839        TracedRouteNodes traced = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getTracedRouteNodes() : null;
840
841        // compute time taken if sending to another endpoint
842        StopWatch watch = null;
843        try {
844            // prepare tracing starting from a new block
845            if (traced != null) {
846                traced.pushBlock();
847            }
848
849            if (producer != null) {
850                boolean sending = EventHelper.notifyExchangeSending(exchange.getContext(), exchange, producer.getEndpoint());
851                if (sending) {
852                    watch = new StopWatch();
853                }
854            }
855            // let the prepared process it, remember to begin the exchange pair
856            AsyncProcessor async = AsyncProcessorConverterHelper.convert(processor);
857            pair.begin();
858            // we invoke it synchronously as parallel async routing is too hard
859            AsyncProcessorHelper.process(async, exchange);
860        } finally {
861            pair.done();
862            // pop the block so by next round we have the same staring point and thus the tracing looks accurate
863            if (traced != null) {
864                traced.popBlock();
865            }
866            if (producer != null && watch != null) {
867                Endpoint endpoint = producer.getEndpoint();
868                long timeTaken = watch.taken();
869                // emit event that the exchange was sent to the endpoint
870                // this is okay to do here in the finally block, as the processing is not using the async routing engine
871                //( we invoke it synchronously as parallel async routing is too hard)
872                EventHelper.notifyExchangeSent(exchange.getContext(), exchange, endpoint, timeTaken);
873            }
874        }
875    }
876
877    /**
878     * Common work which must be done when we are done multicasting.
879     * <p/>
880     * This logic applies for both running synchronous and asynchronous as there are multiple exist points
881     * when using the asynchronous routing engine. And therefore we want the logic in one method instead
882     * of being scattered.
883     *
884     * @param original     the original exchange
885     * @param subExchange  the current sub exchange, can be <tt>null</tt> for the synchronous part
886     * @param pairs        the pairs with the exchanges to process
887     * @param callback     the callback
888     * @param doneSync     the <tt>doneSync</tt> parameter to call on callback
889     * @param forceExhaust whether or not error handling is exhausted
890     */
891    protected void doDone(Exchange original, Exchange subExchange, final Iterable<ProcessorExchangePair> pairs,
892                          AsyncCallback callback, boolean doneSync, boolean forceExhaust) {
893
894        // we are done so close the pairs iterator
895        if (pairs instanceof Closeable) {
896            IOHelper.close((Closeable) pairs, "pairs", LOG);
897        }
898
899        AggregationStrategy strategy = getAggregationStrategy(subExchange);
900        if (strategy instanceof DelegateAggregationStrategy) {
901            strategy = ((DelegateAggregationStrategy) strategy).getDelegate();
902        }
903        // invoke the on completion callback
904        if (strategy instanceof CompletionAwareAggregationStrategy) {
905            ((CompletionAwareAggregationStrategy) strategy).onCompletion(subExchange);
906        }
907
908        // cleanup any per exchange aggregation strategy
909        removeAggregationStrategyFromExchange(original);
910
911        // we need to know if there was an exception, and if the stopOnException option was enabled
912        // also we would need to know if any error handler has attempted redelivery and exhausted
913        boolean stoppedOnException = false;
914        boolean exception = false;
915        boolean exhaust = forceExhaust || subExchange != null && (subExchange.getException() != null || ExchangeHelper.isRedeliveryExhausted(subExchange));
916        if (original.getException() != null || subExchange != null && subExchange.getException() != null) {
917            // there was an exception and we stopped
918            stoppedOnException = isStopOnException();
919            exception = true;
920        }
921
922        // must copy results at this point
923        if (subExchange != null) {
924            if (stoppedOnException) {
925                // if we stopped due an exception then only propagate the exception
926                original.setException(subExchange.getException());
927            } else {
928                // copy the current result to original so it will contain this result of this eip
929                ExchangeHelper.copyResults(original, subExchange);
930            }
931        }
932
933        // .. and then if there was an exception we need to configure the redelivery exhaust
934        // for example the noErrorHandler will not cause redelivery exhaust so if this error
935        // handled has been in use, then the exhaust would be false (if not forced)
936        if (exception) {
937            // multicast uses error handling on its output processors and they have tried to redeliver
938            // so we shall signal back to the other error handlers that we are exhausted and they should not
939            // also try to redeliver as we will then do that twice
940            original.setProperty(Exchange.REDELIVERY_EXHAUSTED, exhaust);
941        }
942
943        callback.done(doneSync);
944    }
945
946    /**
947     * Aggregate the {@link Exchange} with the current result.
948     * This method is synchronized and is called directly when parallelAggregate is disabled (by default).
949     *
950     * @param strategy the aggregation strategy to use
951     * @param result   the current result
952     * @param exchange the exchange to be added to the result
953     * @see #doAggregateInternal(org.apache.camel.processor.aggregate.AggregationStrategy, org.apache.camel.util.concurrent.AtomicExchange, org.apache.camel.Exchange)
954     */
955    protected synchronized void doAggregate(AggregationStrategy strategy, AtomicExchange result, Exchange exchange) {
956        doAggregateInternal(strategy, result, exchange);
957    }
958
959    /**
960     * Aggregate the {@link Exchange} with the current result.
961     * This method is unsynchronized and is called directly when parallelAggregate is enabled.
962     * In all other cases, this method is called from the doAggregate which is a synchronized method
963     *
964     * @param strategy the aggregation strategy to use
965     * @param result   the current result
966     * @param exchange the exchange to be added to the result
967     * @see #doAggregate(org.apache.camel.processor.aggregate.AggregationStrategy, org.apache.camel.util.concurrent.AtomicExchange, org.apache.camel.Exchange)
968     */
969    protected void doAggregateInternal(AggregationStrategy strategy, AtomicExchange result, Exchange exchange) {
970        if (strategy != null) {
971            // prepare the exchanges for aggregation
972            Exchange oldExchange = result.get();
973            ExchangeHelper.prepareAggregation(oldExchange, exchange);
974            result.set(strategy.aggregate(oldExchange, exchange));
975        }
976    }
977
978    protected void updateNewExchange(Exchange exchange, int index, Iterable<ProcessorExchangePair> allPairs,
979                                     Iterator<ProcessorExchangePair> it) {
980        exchange.setProperty(Exchange.MULTICAST_INDEX, index);
981        if (it.hasNext()) {
982            exchange.setProperty(Exchange.MULTICAST_COMPLETE, Boolean.FALSE);
983        } else {
984            exchange.setProperty(Exchange.MULTICAST_COMPLETE, Boolean.TRUE);
985        }
986    }
987
988    protected Integer getExchangeIndex(Exchange exchange) {
989        return exchange.getProperty(Exchange.MULTICAST_INDEX, Integer.class);
990    }
991
992    protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) throws Exception {
993        List<ProcessorExchangePair> result = new ArrayList<>(processors.size());
994
995        StreamCache streamCache = null;
996        if (isParallelProcessing() && exchange.getIn().getBody() instanceof StreamCache) {
997            // in parallel processing case, the stream must be copied, therefore get the stream
998            streamCache = (StreamCache) exchange.getIn().getBody();
999        }
1000
1001        int index = 0;
1002        for (Processor processor : processors) {
1003            // copy exchange, and do not share the unit of work
1004            Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, false);
1005
1006            if (streamCache != null) {
1007                if (index > 0) {
1008                    // copy it otherwise parallel processing is not possible,
1009                    // because streams can only be read once
1010                    StreamCache copiedStreamCache = streamCache.copy(copy);
1011                    if (copiedStreamCache != null) {
1012                        copy.getIn().setBody(copiedStreamCache);
1013                    }
1014                }
1015            }
1016
1017            // If the multi-cast processor has an aggregation strategy
1018            // then the StreamCache created by the child routes must not be 
1019            // closed by the unit of work of the child route, but by the unit of 
1020            // work of the parent route or grand parent route or grand grand parent route ...(in case of nesting).
1021            // Set therefore the unit of work of the  parent route as stream cache unit of work, 
1022            // if it is not already set.
1023            if (copy.getProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK) == null) {
1024                copy.setProperty(Exchange.STREAM_CACHE_UNIT_OF_WORK, exchange.getUnitOfWork());
1025            }
1026            // if we share unit of work, we need to prepare the child exchange
1027            if (isShareUnitOfWork()) {
1028                prepareSharedUnitOfWork(copy, exchange);
1029            }
1030
1031            // and add the pair
1032            RouteContext routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null;
1033            result.add(createProcessorExchangePair(index++, processor, copy, routeContext));
1034        }
1035
1036        if (exchange.getException() != null) {
1037            // force any exceptions occurred during creation of exchange paris to be thrown
1038            // before returning the answer;
1039            throw exchange.getException();
1040        }
1041
1042        return result;
1043    }
1044
1045    /**
1046     * Creates the {@link ProcessorExchangePair} which holds the processor and exchange to be send out.
1047     * <p/>
1048     * You <b>must</b> use this method to create the instances of {@link ProcessorExchangePair} as they
1049     * need to be specially prepared before use.
1050     *
1051     * @param index        the index
1052     * @param processor    the processor
1053     * @param exchange     the exchange
1054     * @param routeContext the route context
1055     * @return prepared for use
1056     */
1057    protected ProcessorExchangePair createProcessorExchangePair(int index, Processor processor, Exchange exchange,
1058                                                                RouteContext routeContext) {
1059        Processor prepared = processor;
1060
1061        // set property which endpoint we send to
1062        setToEndpoint(exchange, prepared);
1063
1064        // rework error handling to support fine grained error handling
1065        prepared = createErrorHandler(routeContext, exchange, prepared);
1066
1067        // invoke on prepare on the exchange if specified
1068        if (onPrepare != null) {
1069            try {
1070                onPrepare.process(exchange);
1071            } catch (Exception e) {
1072                exchange.setException(e);
1073            }
1074        }
1075        return new DefaultProcessorExchangePair(index, processor, prepared, exchange);
1076    }
1077
1078    protected Processor createErrorHandler(RouteContext routeContext, Exchange exchange, Processor processor) {
1079        Processor answer;
1080
1081        boolean tryBlock = exchange.getProperty(Exchange.TRY_ROUTE_BLOCK, false, boolean.class);
1082
1083        // do not wrap in error handler if we are inside a try block
1084        if (!tryBlock && routeContext != null) {
1085            // wrap the producer in error handler so we have fine grained error handling on
1086            // the output side instead of the input side
1087            // this is needed to support redelivery on that output alone and not doing redelivery
1088            // for the entire multicast block again which will start from scratch again
1089
1090            // create key for cache
1091            final PreparedErrorHandler key = new PreparedErrorHandler(routeContext, processor);
1092
1093            // lookup cached first to reuse and preserve memory
1094            answer = errorHandlers.get(key);
1095            if (answer != null) {
1096                LOG.trace("Using existing error handler for: {}", processor);
1097                return answer;
1098            }
1099
1100            LOG.trace("Creating error handler for: {}", processor);
1101            ErrorHandlerFactory builder = routeContext.getRoute().getErrorHandlerBuilder();
1102            // create error handler (create error handler directly to keep it light weight,
1103            // instead of using ProcessorDefinition.wrapInErrorHandler)
1104            try {
1105                processor = builder.createErrorHandler(routeContext, processor);
1106
1107                // and wrap in unit of work processor so the copy exchange also can run under UoW
1108                answer = createUnitOfWorkProcessor(routeContext, processor, exchange);
1109
1110                boolean child = exchange.getProperty(Exchange.PARENT_UNIT_OF_WORK, UnitOfWork.class) != null;
1111
1112                // must start the error handler
1113                ServiceHelper.startServices(answer);
1114
1115                // here we don't cache the child unit of work
1116                if (!child) {
1117                    // add to cache
1118                    errorHandlers.putIfAbsent(key, answer);
1119                }
1120
1121            } catch (Exception e) {
1122                throw ObjectHelper.wrapRuntimeCamelException(e);
1123            }
1124        } else {
1125            // and wrap in unit of work processor so the copy exchange also can run under UoW
1126            answer = createUnitOfWorkProcessor(routeContext, processor, exchange);
1127        }
1128
1129        return answer;
1130    }
1131
1132    /**
1133     * Strategy to create the unit of work to be used for the sub route
1134     *
1135     * @param routeContext the route context
1136     * @param processor    the processor
1137     * @param exchange     the exchange
1138     * @return the unit of work processor
1139     */
1140    protected Processor createUnitOfWorkProcessor(RouteContext routeContext, Processor processor, Exchange exchange) {
1141        CamelInternalProcessor internal = new CamelInternalProcessor(processor);
1142
1143        // and wrap it in a unit of work so the UoW is on the top, so the entire route will be in the same UoW
1144        UnitOfWork parent = exchange.getProperty(Exchange.PARENT_UNIT_OF_WORK, UnitOfWork.class);
1145        if (parent != null) {
1146            internal.addAdvice(new CamelInternalProcessor.ChildUnitOfWorkProcessorAdvice(routeContext, parent));
1147        } else {
1148            internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext));
1149        }
1150
1151        return internal;
1152    }
1153
1154    /**
1155     * Prepares the exchange for participating in a shared unit of work
1156     * <p/>
1157     * This ensures a child exchange can access its parent {@link UnitOfWork} when it participate
1158     * in a shared unit of work.
1159     *
1160     * @param childExchange  the child exchange
1161     * @param parentExchange the parent exchange
1162     */
1163    protected void prepareSharedUnitOfWork(Exchange childExchange, Exchange parentExchange) {
1164        childExchange.setProperty(Exchange.PARENT_UNIT_OF_WORK, parentExchange.getUnitOfWork());
1165    }
1166
1167    protected void doStart() throws Exception {
1168        if (isParallelProcessing() && executorService == null) {
1169            throw new IllegalArgumentException("ParallelProcessing is enabled but ExecutorService has not been set");
1170        }
1171        if (timeout > 0 && !isParallelProcessing()) {
1172            throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled");
1173        }
1174        if (isParallelProcessing() && aggregateExecutorService == null) {
1175            // use unbounded thread pool so we ensure the aggregate on-the-fly task always will have assigned a thread
1176            // and run the tasks when the task is submitted. If not then the aggregate task may not be able to run
1177            // and signal completion during processing, which would lead to what would appear as a dead-lock or a slow processing
1178            String name = getClass().getSimpleName() + "-AggregateTask";
1179            aggregateExecutorService = createAggregateExecutorService(name);
1180        }
1181        if (aggregationStrategy instanceof CamelContextAware) {
1182            ((CamelContextAware) aggregationStrategy).setCamelContext(camelContext);
1183        }
1184
1185        ServiceHelper.startServices(aggregationStrategy, processors);
1186    }
1187
1188    /**
1189     * Strategy to create the thread pool for the aggregator background task which waits for and aggregates
1190     * completed tasks when running in parallel mode.
1191     *
1192     * @param name  the suggested name for the background thread
1193     * @return the thread pool
1194     */
1195    protected synchronized ExecutorService createAggregateExecutorService(String name) {
1196        // use a cached thread pool so we each on-the-fly task has a dedicated thread to process completions as they come in
1197        return camelContext.getExecutorServiceManager().newCachedThreadPool(this, name);
1198    }
1199
1200    @Override
1201    protected void doStop() throws Exception {
1202        ServiceHelper.stopServices(processors, errorHandlers, aggregationStrategy);
1203    }
1204
1205    @Override
1206    protected void doShutdown() throws Exception {
1207        ServiceHelper.stopAndShutdownServices(processors, errorHandlers, aggregationStrategy);
1208        // only clear error handlers when shutting down
1209        errorHandlers.clear();
1210
1211        if (shutdownExecutorService && executorService != null) {
1212            getCamelContext().getExecutorServiceManager().shutdownNow(executorService);
1213        }
1214        if (aggregateExecutorService != null) {
1215            getCamelContext().getExecutorServiceManager().shutdownNow(aggregateExecutorService);
1216        }
1217    }
1218
1219    protected static void setToEndpoint(Exchange exchange, Processor processor) {
1220        if (processor instanceof Producer) {
1221            Producer producer = (Producer) processor;
1222            exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri());
1223        }
1224    }
1225
1226    protected AggregationStrategy getAggregationStrategy(Exchange exchange) {
1227        AggregationStrategy answer = null;
1228
1229        // prefer to use per Exchange aggregation strategy over a global strategy
1230        if (exchange != null) {
1231            Map<?, ?> property = exchange.getProperty(Exchange.AGGREGATION_STRATEGY, Map.class);
1232            Map<Object, AggregationStrategy> map = CastUtils.cast(property);
1233            if (map != null) {
1234                answer = map.get(this);
1235            }
1236        }
1237        if (answer == null) {
1238            // fallback to global strategy
1239            answer = getAggregationStrategy();
1240        }
1241        return answer;
1242    }
1243
1244    /**
1245     * Sets the given {@link org.apache.camel.processor.aggregate.AggregationStrategy} on the {@link Exchange}.
1246     *
1247     * @param exchange            the exchange
1248     * @param aggregationStrategy the strategy
1249     */
1250    protected void setAggregationStrategyOnExchange(Exchange exchange, AggregationStrategy aggregationStrategy) {
1251        Map<?, ?> property = exchange.getProperty(Exchange.AGGREGATION_STRATEGY, Map.class);
1252        Map<Object, AggregationStrategy> map = CastUtils.cast(property);
1253        if (map == null) {
1254            map = new ConcurrentHashMap<>();
1255        } else {
1256            // it is not safe to use the map directly as the exchange doesn't have the deep copy of it's properties
1257            // we just create a new copy if we need to change the map
1258            map = new ConcurrentHashMap<>(map);
1259        }
1260        // store the strategy using this processor as the key
1261        // (so we can store multiple strategies on the same exchange)
1262        map.put(this, aggregationStrategy);
1263        exchange.setProperty(Exchange.AGGREGATION_STRATEGY, map);
1264    }
1265
1266    /**
1267     * Removes the associated {@link org.apache.camel.processor.aggregate.AggregationStrategy} from the {@link Exchange}
1268     * which must be done after use.
1269     *
1270     * @param exchange the current exchange
1271     */
1272    protected void removeAggregationStrategyFromExchange(Exchange exchange) {
1273        Map<?, ?> property = exchange.getProperty(Exchange.AGGREGATION_STRATEGY, Map.class);
1274        Map<Object, AggregationStrategy> map = CastUtils.cast(property);
1275        if (map == null) {
1276            return;
1277        }
1278        // remove the strategy using this processor as the key
1279        map.remove(this);
1280    }
1281
1282    /**
1283     * Is the multicast processor working in streaming mode?
1284     * <p/>
1285     * In streaming mode:
1286     * <ul>
1287     * <li>we use {@link Iterable} to ensure we can send messages as soon as the data becomes available</li>
1288     * <li>for parallel processing, we start aggregating responses as they get send back to the processor;
1289     * this means the {@link org.apache.camel.processor.aggregate.AggregationStrategy} has to take care of handling out-of-order arrival of exchanges</li>
1290     * </ul>
1291     */
1292    public boolean isStreaming() {
1293        return streaming;
1294    }
1295
1296    /**
1297     * Should the multicast processor stop processing further exchanges in case of an exception occurred?
1298     */
1299    public boolean isStopOnException() {
1300        return stopOnException;
1301    }
1302
1303    /**
1304     * Returns the producers to multicast to
1305     */
1306    public Collection<Processor> getProcessors() {
1307        return processors;
1308    }
1309
1310    /**
1311     * An optional timeout in millis when using parallel processing
1312     */
1313    public long getTimeout() {
1314        return timeout;
1315    }
1316
1317    /**
1318     * Use {@link #getAggregationStrategy(org.apache.camel.Exchange)} instead.
1319     */
1320    public AggregationStrategy getAggregationStrategy() {
1321        return aggregationStrategy;
1322    }
1323
1324    public boolean isParallelProcessing() {
1325        return parallelProcessing;
1326    }
1327
1328    public boolean isParallelAggregate() {
1329        return parallelAggregate;
1330    }
1331
1332    public boolean isStopOnAggregateException() {
1333        return stopOnAggregateException;
1334    }
1335
1336    public boolean isShareUnitOfWork() {
1337        return shareUnitOfWork;
1338    }
1339
1340    public List<Processor> next() {
1341        if (!hasNext()) {
1342            return null;
1343        }
1344        return new ArrayList<>(processors);
1345    }
1346
1347    public boolean hasNext() {
1348        return processors != null && !processors.isEmpty();
1349    }
1350}