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.util.ArrayList;
020import java.util.List;
021import java.util.concurrent.Callable;
022import java.util.concurrent.CountDownLatch;
023import java.util.concurrent.RejectedExecutionException;
024import java.util.concurrent.ScheduledExecutorService;
025import java.util.concurrent.ThreadPoolExecutor;
026import java.util.concurrent.TimeUnit;
027import java.util.concurrent.atomic.AtomicInteger;
028
029import org.apache.camel.AsyncCallback;
030import org.apache.camel.AsyncProcessor;
031import org.apache.camel.CamelContext;
032import org.apache.camel.Exchange;
033import org.apache.camel.LoggingLevel;
034import org.apache.camel.Message;
035import org.apache.camel.Navigate;
036import org.apache.camel.Predicate;
037import org.apache.camel.Processor;
038import org.apache.camel.model.OnExceptionDefinition;
039import org.apache.camel.spi.AsyncProcessorAwaitManager;
040import org.apache.camel.spi.ExchangeFormatter;
041import org.apache.camel.spi.ShutdownPrepared;
042import org.apache.camel.spi.SubUnitOfWorkCallback;
043import org.apache.camel.spi.UnitOfWork;
044import org.apache.camel.util.AsyncProcessorConverterHelper;
045import org.apache.camel.util.CamelContextHelper;
046import org.apache.camel.util.CamelLogger;
047import org.apache.camel.util.EventHelper;
048import org.apache.camel.util.ExchangeHelper;
049import org.apache.camel.util.MessageHelper;
050import org.apache.camel.util.ObjectHelper;
051import org.apache.camel.util.ServiceHelper;
052import org.apache.camel.util.StopWatch;
053import org.apache.camel.util.URISupport;
054
055/**
056 * Base redeliverable error handler that also supports a final dead letter queue in case
057 * all redelivery attempts fail.
058 * <p/>
059 * This implementation should contain all the error handling logic and the sub classes
060 * should only configure it according to what they support.
061 *
062 * @version
063 */
064public abstract class RedeliveryErrorHandler extends ErrorHandlerSupport implements AsyncProcessor, ShutdownPrepared, Navigate<Processor> {
065
066    protected final AtomicInteger redeliverySleepCounter = new AtomicInteger();
067    protected ScheduledExecutorService executorService;
068    protected final CamelContext camelContext;
069    protected final AsyncProcessorAwaitManager awaitManager;
070    protected final Processor deadLetter;
071    protected final String deadLetterUri;
072    protected final boolean deadLetterHandleNewException;
073    protected final Processor output;
074    protected final AsyncProcessor outputAsync;
075    protected final Processor redeliveryProcessor;
076    protected final RedeliveryPolicy redeliveryPolicy;
077    protected final Predicate retryWhilePolicy;
078    protected final CamelLogger logger;
079    protected final boolean useOriginalMessagePolicy;
080    protected boolean redeliveryEnabled;
081    protected volatile boolean preparingShutdown;
082    protected final ExchangeFormatter exchangeFormatter;
083    protected final boolean customExchangeFormatter;
084    protected final Processor onPrepareProcessor;
085    protected final Processor onExceptionProcessor;
086
087    /**
088     * Contains the current redelivery data
089     */
090    protected class RedeliveryData {
091        Exchange original;
092        boolean sync = true;
093        int redeliveryCounter;
094        long redeliveryDelay;
095        Predicate retryWhilePredicate;
096        boolean redeliverFromSync;
097
098        // default behavior which can be overloaded on a per exception basis
099        RedeliveryPolicy currentRedeliveryPolicy;
100        Processor deadLetterProcessor;
101        Processor failureProcessor;
102        Processor onRedeliveryProcessor;
103        Processor onPrepareProcessor;
104        Processor onExceptionProcessor;
105        Predicate handledPredicate;
106        Predicate continuedPredicate;
107        boolean useOriginalInMessage;
108        boolean handleNewException;
109
110        public RedeliveryData() {
111            // init with values from the error handler
112            this.retryWhilePredicate = retryWhilePolicy;
113            this.currentRedeliveryPolicy = redeliveryPolicy;
114            this.deadLetterProcessor = deadLetter;
115            this.onRedeliveryProcessor = redeliveryProcessor;
116            this.onPrepareProcessor = RedeliveryErrorHandler.this.onPrepareProcessor;
117            this.onExceptionProcessor = RedeliveryErrorHandler.this.onExceptionProcessor;
118            this.handledPredicate = getDefaultHandledPredicate();
119            this.useOriginalInMessage = useOriginalMessagePolicy;
120            this.handleNewException = deadLetterHandleNewException;
121        }
122    }
123
124    /**
125     * Task for sleeping during redelivery attempts.
126     * <p/>
127     * This task is for the synchronous blocking. If using async delayed then a scheduled thread pool
128     * is used for sleeping and trigger redeliveries.
129     */
130    private final class RedeliverSleepTask {
131
132        private final RedeliveryPolicy policy;
133        private final long delay;
134
135        RedeliverSleepTask(RedeliveryPolicy policy, long delay) {
136            this.policy = policy;
137            this.delay = delay;
138        }
139
140        public boolean sleep() throws InterruptedException {
141            // for small delays then just sleep
142            if (delay < 1000) {
143                policy.sleep(delay);
144                return true;
145            }
146
147            StopWatch watch = new StopWatch();
148
149            log.debug("Sleeping for: {} millis until attempting redelivery", delay);
150            while (watch.taken() < delay) {
151                // sleep using 1 sec interval
152
153                long delta = delay - watch.taken();
154                long max = Math.min(1000, delta);
155                if (max > 0) {
156                    log.trace("Sleeping for: {} millis until waking up for re-check", max);
157                    Thread.sleep(max);
158                }
159
160                // are we preparing for shutdown then only do redelivery if allowed
161                if (preparingShutdown && !policy.isAllowRedeliveryWhileStopping()) {
162                    log.debug("Rejected redelivery while stopping");
163                    return false;
164                }
165            }
166
167            return true;
168        }
169    }
170
171    /**
172     * Tasks which performs asynchronous redelivery attempts, and being triggered by a
173     * {@link java.util.concurrent.ScheduledExecutorService} to avoid having any threads blocking if a task
174     * has to be delayed before a redelivery attempt is performed.
175     */
176    private final class AsyncRedeliveryTask implements Callable<Boolean> {
177
178        private final Exchange exchange;
179        private final AsyncCallback callback;
180        private final RedeliveryData data;
181
182        AsyncRedeliveryTask(Exchange exchange, AsyncCallback callback, RedeliveryData data) {
183            this.exchange = exchange;
184            this.callback = callback;
185            this.data = data;
186        }
187
188        public Boolean call() throws Exception {
189            // prepare for redelivery
190            prepareExchangeForRedelivery(exchange, data);
191
192            // letting onRedeliver be executed at first
193            deliverToOnRedeliveryProcessor(exchange, data);
194
195            if (log.isTraceEnabled()) {
196                log.trace("Redelivering exchangeId: {} -> {} for Exchange: {}", new Object[]{exchange.getExchangeId(), outputAsync, exchange});
197            }
198
199            // emmit event we are doing redelivery
200            EventHelper.notifyExchangeRedelivery(exchange.getContext(), exchange, data.redeliveryCounter);
201
202            // process the exchange (also redelivery)
203            boolean sync;
204            if (data.redeliverFromSync) {
205                // this redelivery task was scheduled from synchronous, which we forced to be asynchronous from
206                // this error handler, which means we have to invoke the callback with false, to have the callback
207                // be notified when we are done
208                sync = outputAsync.process(exchange, new AsyncCallback() {
209                    public void done(boolean doneSync) {
210                        log.trace("Redelivering exchangeId: {} done sync: {}", exchange.getExchangeId(), doneSync);
211
212                        // mark we are in sync mode now
213                        data.sync = false;
214
215                        // only process if the exchange hasn't failed
216                        // and it has not been handled by the error processor
217                        if (isDone(exchange)) {
218                            callback.done(false);
219                            return;
220                        }
221
222                        // error occurred so loop back around which we do by invoking the processAsyncErrorHandler
223                        processAsyncErrorHandler(exchange, callback, data);
224                    }
225                });
226            } else {
227                // this redelivery task was scheduled from asynchronous, which means we should only
228                // handle when the asynchronous task was done
229                sync = outputAsync.process(exchange, new AsyncCallback() {
230                    public void done(boolean doneSync) {
231                        log.trace("Redelivering exchangeId: {} done sync: {}", exchange.getExchangeId(), doneSync);
232
233                        // this callback should only handle the async case
234                        if (doneSync) {
235                            return;
236                        }
237
238                        // mark we are in async mode now
239                        data.sync = false;
240
241                        // only process if the exchange hasn't failed
242                        // and it has not been handled by the error processor
243                        if (isDone(exchange)) {
244                            callback.done(doneSync);
245                            return;
246                        }
247                        // error occurred so loop back around which we do by invoking the processAsyncErrorHandler
248                        processAsyncErrorHandler(exchange, callback, data);
249                    }
250                });
251            }
252
253            return sync;
254        }
255    }
256
257    public RedeliveryErrorHandler(CamelContext camelContext, Processor output, CamelLogger logger,
258                                  Processor redeliveryProcessor, RedeliveryPolicy redeliveryPolicy, Processor deadLetter,
259                                  String deadLetterUri, boolean deadLetterHandleNewException, boolean useOriginalMessagePolicy,
260                                  Predicate retryWhile, ScheduledExecutorService executorService, Processor onPrepareProcessor, Processor onExceptionProcessor) {
261
262        ObjectHelper.notNull(camelContext, "CamelContext", this);
263        ObjectHelper.notNull(redeliveryPolicy, "RedeliveryPolicy", this);
264
265        this.camelContext = camelContext;
266        this.awaitManager = camelContext.getAsyncProcessorAwaitManager();
267        this.redeliveryProcessor = redeliveryProcessor;
268        this.deadLetter = deadLetter;
269        this.output = output;
270        this.outputAsync = AsyncProcessorConverterHelper.convert(output);
271        this.redeliveryPolicy = redeliveryPolicy;
272        this.logger = logger;
273        this.deadLetterUri = deadLetterUri;
274        this.deadLetterHandleNewException = deadLetterHandleNewException;
275        this.useOriginalMessagePolicy = useOriginalMessagePolicy;
276        this.retryWhilePolicy = retryWhile;
277        this.executorService = executorService;
278        this.onPrepareProcessor = onPrepareProcessor;
279        this.onExceptionProcessor = onExceptionProcessor;
280
281        if (ObjectHelper.isNotEmpty(redeliveryPolicy.getExchangeFormatterRef())) {
282            ExchangeFormatter formatter = camelContext.getRegistry().lookupByNameAndType(redeliveryPolicy.getExchangeFormatterRef(), ExchangeFormatter.class);
283            if (formatter != null) {
284                this.exchangeFormatter = formatter;
285                this.customExchangeFormatter = true;
286            } else {
287                throw new IllegalArgumentException("Cannot find the exchangeFormatter by using reference id " + redeliveryPolicy.getExchangeFormatterRef());
288            }
289        } else {
290            this.customExchangeFormatter = false;
291            // setup exchange formatter to be used for message history dump
292            DefaultExchangeFormatter formatter = new DefaultExchangeFormatter();
293            formatter.setShowExchangeId(true);
294            formatter.setMultiline(true);
295            formatter.setShowHeaders(true);
296            formatter.setStyle(DefaultExchangeFormatter.OutputStyle.Fixed);
297            try {
298                Integer maxChars = CamelContextHelper.parseInteger(camelContext, camelContext.getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS));
299                if (maxChars != null) {
300                    formatter.setMaxChars(maxChars);
301                }
302            } catch (Exception e) {
303                throw ObjectHelper.wrapRuntimeCamelException(e);
304            }
305            this.exchangeFormatter = formatter;
306        }
307    }
308
309    public boolean supportTransacted() {
310        return false;
311    }
312
313    @Override
314    public boolean hasNext() {
315        return output != null;
316    }
317
318    @Override
319    public List<Processor> next() {
320        if (!hasNext()) {
321            return null;
322        }
323        List<Processor> answer = new ArrayList<Processor>(1);
324        answer.add(output);
325        return answer;
326    }
327
328    protected boolean isRunAllowed(RedeliveryData data) {
329        // if camel context is forcing a shutdown then do not allow running
330        boolean forceShutdown = camelContext.getShutdownStrategy().forceShutdown(this);
331        if (forceShutdown) {
332            log.trace("isRunAllowed() -> false (Run not allowed as ShutdownStrategy is forcing shutting down)");
333            return false;
334        }
335
336        // redelivery policy can control if redelivery is allowed during stopping/shutdown
337        // but this only applies during a redelivery (counter must > 0)
338        if (data.redeliveryCounter > 0) {
339            if (data.currentRedeliveryPolicy.allowRedeliveryWhileStopping) {
340                log.trace("isRunAllowed() -> true (Run allowed as RedeliverWhileStopping is enabled)");
341                return true;
342            } else if (preparingShutdown) {
343                // we are preparing for shutdown, now determine if we can still run
344                boolean answer = isRunAllowedOnPreparingShutdown();
345                log.trace("isRunAllowed() -> {} (Run not allowed as we are preparing for shutdown)", answer);
346                return answer;
347            }
348        }
349
350        // we cannot run if we are stopping/stopped
351        boolean answer = !isStoppingOrStopped();
352        log.trace("isRunAllowed() -> {} (Run allowed if we are not stopped/stopping)", answer);
353        return answer;
354    }
355
356    protected boolean isRunAllowedOnPreparingShutdown() {
357        return false;
358    }
359
360    protected boolean isRedeliveryAllowed(RedeliveryData data) {
361        // redelivery policy can control if redelivery is allowed during stopping/shutdown
362        // but this only applies during a redelivery (counter must > 0)
363        if (data.redeliveryCounter > 0) {
364            boolean stopping = isStoppingOrStopped();
365            if (!preparingShutdown && !stopping) {
366                log.trace("isRedeliveryAllowed() -> true (we are not stopping/stopped)");
367                return true;
368            } else {
369                // we are stopping or preparing to shutdown
370                if (data.currentRedeliveryPolicy.allowRedeliveryWhileStopping) {
371                    log.trace("isRedeliveryAllowed() -> true (Redelivery allowed as RedeliverWhileStopping is enabled)");
372                    return true;
373                } else {
374                    log.trace("isRedeliveryAllowed() -> false (Redelivery not allowed as RedeliverWhileStopping is disabled)");
375                    return false;
376                }
377            }
378        }
379
380        return true;
381    }
382
383    @Override
384    public void prepareShutdown(boolean suspendOnly, boolean forced) {
385        // prepare for shutdown, eg do not allow redelivery if configured
386        log.trace("Prepare shutdown on error handler {}", this);
387        preparingShutdown = true;
388    }
389
390    public void process(Exchange exchange) throws Exception {
391        if (output == null) {
392            // no output then just return
393            return;
394        }
395
396        // inline org.apache.camel.util.AsyncProcessorHelper.process(org.apache.camel.AsyncProcessor, org.apache.camel.Exchange)
397        // to optimize and reduce stacktrace lengths
398        final CountDownLatch latch = new CountDownLatch(1);
399        boolean sync = process(exchange, new AsyncCallback() {
400            public void done(boolean doneSync) {
401                if (!doneSync) {
402                    awaitManager.countDown(exchange, latch);
403                }
404            }
405        });
406        if (!sync) {
407            awaitManager.await(exchange, latch);
408        }
409    }
410
411    /**
412     * Process the exchange using redelivery error handling.
413     */
414    public boolean process(final Exchange exchange, final AsyncCallback callback) {
415        final RedeliveryData data = new RedeliveryData();
416
417        // do a defensive copy of the original Exchange, which is needed for redelivery so we can ensure the
418        // original Exchange is being redelivered, and not a mutated Exchange
419        data.original = defensiveCopyExchangeIfNeeded(exchange);
420
421        // use looping to have redelivery attempts
422        while (true) {
423
424            // can we still run
425            if (!isRunAllowed(data)) {
426                log.trace("Run not allowed, will reject executing exchange: {}", exchange);
427                if (exchange.getException() == null) {
428                    exchange.setException(new RejectedExecutionException());
429                }
430                // we cannot process so invoke callback
431                callback.done(data.sync);
432                return data.sync;
433            }
434
435            // did previous processing cause an exception?
436            boolean handle = shouldHandleException(exchange);
437            if (handle) {
438                handleException(exchange, data, isDeadLetterChannel());
439                onExceptionOccurred(exchange, data);
440            }
441
442            // compute if we are exhausted, and whether redelivery is allowed
443            boolean exhausted = isExhausted(exchange, data);
444            boolean redeliverAllowed = isRedeliveryAllowed(data);
445
446            // if we are exhausted or redelivery is not allowed, then deliver to failure processor (eg such as DLC)
447            if (!redeliverAllowed || exhausted) {
448                Processor target = null;
449                boolean deliver = true;
450
451                // the unit of work may have an optional callback associated we need to leverage
452                SubUnitOfWorkCallback uowCallback = exchange.getUnitOfWork().getSubUnitOfWorkCallback();
453                if (uowCallback != null) {
454                    // signal to the callback we are exhausted
455                    uowCallback.onExhausted(exchange);
456                    // do not deliver to the failure processor as its been handled by the callback instead
457                    deliver = false;
458                }
459
460                if (deliver) {
461                    // should deliver to failure processor (either from onException or the dead letter channel)
462                    target = data.failureProcessor != null ? data.failureProcessor : data.deadLetterProcessor;
463                }
464                // we should always invoke the deliverToFailureProcessor as it prepares, logs and does a fair
465                // bit of work for exhausted exchanges (its only the target processor which may be null if handled by a savepoint)
466                boolean isDeadLetterChannel = isDeadLetterChannel() && (target == null || target == data.deadLetterProcessor);
467                boolean sync = deliverToFailureProcessor(target, isDeadLetterChannel, exchange, data, callback);
468                // we are breaking out
469                return sync;
470            }
471
472            if (data.redeliveryCounter > 0) {
473                // calculate delay
474                data.redeliveryDelay = determineRedeliveryDelay(exchange, data.currentRedeliveryPolicy, data.redeliveryDelay, data.redeliveryCounter);
475
476                if (data.redeliveryDelay > 0) {
477                    // okay there is a delay so create a scheduled task to have it executed in the future
478
479                    if (data.currentRedeliveryPolicy.isAsyncDelayedRedelivery() && !exchange.isTransacted()) {
480
481                        // we are doing a redelivery then a thread pool must be configured (see the doStart method)
482                        ObjectHelper.notNull(executorService, "Redelivery is enabled but ExecutorService has not been configured.", this);
483
484                        // let the RedeliverTask be the logic which tries to redeliver the Exchange which we can used a scheduler to
485                        // have it being executed in the future, or immediately
486                        // we are continuing asynchronously
487
488                        // mark we are routing async from now and that this redelivery task came from a synchronous routing
489                        data.sync = false;
490                        data.redeliverFromSync = true;
491                        AsyncRedeliveryTask task = new AsyncRedeliveryTask(exchange, callback, data);
492
493                        // schedule the redelivery task
494                        if (log.isTraceEnabled()) {
495                            log.trace("Scheduling redelivery task to run in {} millis for exchangeId: {}", data.redeliveryDelay, exchange.getExchangeId());
496                        }
497                        executorService.schedule(task, data.redeliveryDelay, TimeUnit.MILLISECONDS);
498
499                        return false;
500                    } else {
501                        // async delayed redelivery was disabled or we are transacted so we must be synchronous
502                        // as the transaction manager requires to execute in the same thread context
503                        try {
504                            // we are doing synchronous redelivery and use thread sleep, so we keep track using a counter how many are sleeping
505                            redeliverySleepCounter.incrementAndGet();
506                            RedeliverSleepTask task = new RedeliverSleepTask(data.currentRedeliveryPolicy, data.redeliveryDelay);
507                            boolean complete = task.sleep();
508                            redeliverySleepCounter.decrementAndGet();
509                            if (!complete) {
510                                // the task was rejected
511                                exchange.setException(new RejectedExecutionException("Redelivery not allowed while stopping"));
512                                // mark the exchange as redelivery exhausted so the failure processor / dead letter channel can process the exchange
513                                exchange.setProperty(Exchange.REDELIVERY_EXHAUSTED, Boolean.TRUE);
514                                // jump to start of loop which then detects that we are failed and exhausted
515                                continue;
516                            }
517                        } catch (InterruptedException e) {
518                            redeliverySleepCounter.decrementAndGet();
519                            // we was interrupted so break out
520                            exchange.setException(e);
521                            // mark the exchange to stop continue routing when interrupted
522                            // as we do not want to continue routing (for example a task has been cancelled)
523                            exchange.setProperty(Exchange.ROUTE_STOP, Boolean.TRUE);
524                            callback.done(data.sync);
525                            return data.sync;
526                        }
527                    }
528                }
529
530                // prepare for redelivery
531                prepareExchangeForRedelivery(exchange, data);
532
533                // letting onRedeliver be executed
534                deliverToOnRedeliveryProcessor(exchange, data);
535
536                // emmit event we are doing redelivery
537                EventHelper.notifyExchangeRedelivery(exchange.getContext(), exchange, data.redeliveryCounter);
538            }
539
540            // process the exchange (also redelivery)
541            boolean sync = outputAsync.process(exchange, new AsyncCallback() {
542                public void done(boolean sync) {
543                    // this callback should only handle the async case
544                    if (sync) {
545                        return;
546                    }
547
548                    // mark we are in async mode now
549                    data.sync = false;
550
551                    // if we are done then notify callback and exit
552                    if (isDone(exchange)) {
553                        callback.done(sync);
554                        return;
555                    }
556
557                    // error occurred so loop back around which we do by invoking the processAsyncErrorHandler
558                    // method which takes care of this in a asynchronous manner
559                    processAsyncErrorHandler(exchange, callback, data);
560                }
561            });
562
563            if (!sync) {
564                // the remainder of the Exchange is being processed asynchronously so we should return
565                return false;
566            }
567            // we continue to route synchronously
568
569            // if we are done then notify callback and exit
570            boolean done = isDone(exchange);
571            if (done) {
572                callback.done(true);
573                return true;
574            }
575
576            // error occurred so loop back around.....
577        }
578    }
579
580    /**
581     * <p>Determines the redelivery delay time by first inspecting the Message header {@link Exchange#REDELIVERY_DELAY}
582     * and if not present, defaulting to {@link RedeliveryPolicy#calculateRedeliveryDelay(long, int)}</p>
583     *
584     * <p>In order to prevent manipulation of the RedeliveryData state, the values of {@link RedeliveryData#redeliveryDelay}
585     * and {@link RedeliveryData#redeliveryCounter} are copied in.</p>
586     *
587     * @param exchange The current exchange in question.
588     * @param redeliveryPolicy The RedeliveryPolicy to use in the calculation.
589     * @param redeliveryDelay The default redelivery delay from RedeliveryData
590     * @param redeliveryCounter The redeliveryCounter
591     * @return The time to wait before the next redelivery.
592     */
593    protected long determineRedeliveryDelay(Exchange exchange, RedeliveryPolicy redeliveryPolicy, long redeliveryDelay, int redeliveryCounter) {
594        Message message = exchange.getIn();
595        Long delay = message.getHeader(Exchange.REDELIVERY_DELAY, Long.class);
596        if (delay == null) {
597            delay = redeliveryPolicy.calculateRedeliveryDelay(redeliveryDelay, redeliveryCounter);
598            log.debug("Redelivery delay calculated as {}", delay);
599        } else {
600            log.debug("Redelivery delay is {} from Message Header [{}]", delay, Exchange.REDELIVERY_DELAY);
601        }
602        return delay;
603    }
604
605    /**
606     * This logic is only executed if we have to retry redelivery asynchronously, which have to be done from the callback.
607     * <p/>
608     * And therefore the logic is a bit different than the synchronous <tt>processErrorHandler</tt> method which can use
609     * a loop based redelivery technique. However this means that these two methods in overall have to be in <b>sync</b>
610     * in terms of logic.
611     */
612    protected void processAsyncErrorHandler(final Exchange exchange, final AsyncCallback callback, final RedeliveryData data) {
613        // can we still run
614        if (!isRunAllowed(data)) {
615            log.trace("Run not allowed, will reject executing exchange: {}", exchange);
616            if (exchange.getException() == null) {
617                exchange.setException(new RejectedExecutionException());
618            }
619            callback.done(data.sync);
620            return;
621        }
622
623        // did previous processing cause an exception?
624        boolean handle = shouldHandleException(exchange);
625        if (handle) {
626            handleException(exchange, data, isDeadLetterChannel());
627            onExceptionOccurred(exchange, data);
628        }
629
630        // compute if we are exhausted or not
631        boolean exhausted = isExhausted(exchange, data);
632        if (exhausted) {
633            Processor target = null;
634            boolean deliver = true;
635
636            // the unit of work may have an optional callback associated we need to leverage
637            UnitOfWork uow = exchange.getUnitOfWork();
638            if (uow != null) {
639                SubUnitOfWorkCallback uowCallback = uow.getSubUnitOfWorkCallback();
640                if (uowCallback != null) {
641                    // signal to the callback we are exhausted
642                    uowCallback.onExhausted(exchange);
643                    // do not deliver to the failure processor as its been handled by the callback instead
644                    deliver = false;
645                }
646            }
647
648            if (deliver) {
649                // should deliver to failure processor (either from onException or the dead letter channel)
650                target = data.failureProcessor != null ? data.failureProcessor : data.deadLetterProcessor;
651            }
652            // we should always invoke the deliverToFailureProcessor as it prepares, logs and does a fair
653            // bit of work for exhausted exchanges (its only the target processor which may be null if handled by a savepoint)
654            boolean isDeadLetterChannel = isDeadLetterChannel() && target == data.deadLetterProcessor;
655            deliverToFailureProcessor(target, isDeadLetterChannel, exchange, data, callback);
656            // we are breaking out
657            return;
658        }
659
660        if (data.redeliveryCounter > 0) {
661            // we are doing a redelivery then a thread pool must be configured (see the doStart method)
662            ObjectHelper.notNull(executorService, "Redelivery is enabled but ExecutorService has not been configured.", this);
663
664            // let the RedeliverTask be the logic which tries to redeliver the Exchange which we can used a scheduler to
665            // have it being executed in the future, or immediately
666            // Note: the data.redeliverFromSync should be kept as is, in case it was enabled previously
667            // to ensure the callback will continue routing from where we left
668            AsyncRedeliveryTask task = new AsyncRedeliveryTask(exchange, callback, data);
669
670            // calculate the redelivery delay
671            data.redeliveryDelay = determineRedeliveryDelay(exchange, data.currentRedeliveryPolicy, data.redeliveryDelay, data.redeliveryCounter);
672
673            if (data.redeliveryDelay > 0) {
674                // schedule the redelivery task
675                if (log.isTraceEnabled()) {
676                    log.trace("Scheduling redelivery task to run in {} millis for exchangeId: {}", data.redeliveryDelay, exchange.getExchangeId());
677                }
678                executorService.schedule(task, data.redeliveryDelay, TimeUnit.MILLISECONDS);
679            } else {
680                // execute the task immediately
681                executorService.submit(task);
682            }
683        }
684    }
685
686    /**
687     * Performs a defensive copy of the exchange if needed
688     *
689     * @param exchange the exchange
690     * @return the defensive copy, or <tt>null</tt> if not needed (redelivery is not enabled).
691     */
692    protected Exchange defensiveCopyExchangeIfNeeded(Exchange exchange) {
693        // only do a defensive copy if redelivery is enabled
694        if (redeliveryEnabled) {
695            return ExchangeHelper.createCopy(exchange, true);
696        } else {
697            return null;
698        }
699    }
700
701    /**
702     * Strategy whether the exchange has an exception that we should try to handle.
703     * <p/>
704     * Standard implementations should just look for an exception.
705     */
706    protected boolean shouldHandleException(Exchange exchange) {
707        return exchange.getException() != null;
708    }
709
710    /**
711     * Strategy to determine if the exchange is done so we can continue
712     */
713    protected boolean isDone(Exchange exchange) {
714        boolean answer = isCancelledOrInterrupted(exchange);
715
716        // only done if the exchange hasn't failed
717        // and it has not been handled by the failure processor
718        // or we are exhausted
719        if (!answer) {
720            answer = exchange.getException() == null
721                || ExchangeHelper.isFailureHandled(exchange)
722                || ExchangeHelper.isRedeliveryExhausted(exchange);
723        }
724
725        log.trace("Is exchangeId: {} done? {}", exchange.getExchangeId(), answer);
726        return answer;
727    }
728
729    /**
730     * Strategy to determine if the exchange was cancelled or interrupted
731     */
732    protected boolean isCancelledOrInterrupted(Exchange exchange) {
733        boolean answer = false;
734
735        if (ExchangeHelper.isInterrupted(exchange)) {
736            // mark the exchange to stop continue routing when interrupted
737            // as we do not want to continue routing (for example a task has been cancelled)
738            exchange.setProperty(Exchange.ROUTE_STOP, Boolean.TRUE);
739            answer = true;
740        }
741
742        log.trace("Is exchangeId: {} interrupted? {}", exchange.getExchangeId(), answer);
743        return answer;
744    }
745
746    /**
747     * Returns the output processor
748     */
749    public Processor getOutput() {
750        return output;
751    }
752
753    /**
754     * Returns the dead letter that message exchanges will be sent to if the
755     * redelivery attempts fail
756     */
757    public Processor getDeadLetter() {
758        return deadLetter;
759    }
760
761    public String getDeadLetterUri() {
762        return deadLetterUri;
763    }
764
765    public boolean isUseOriginalMessagePolicy() {
766        return useOriginalMessagePolicy;
767    }
768
769    public boolean isDeadLetterHandleNewException() {
770        return deadLetterHandleNewException;
771    }
772
773    public RedeliveryPolicy getRedeliveryPolicy() {
774        return redeliveryPolicy;
775    }
776
777    public CamelLogger getLogger() {
778        return logger;
779    }
780
781    protected Predicate getDefaultHandledPredicate() {
782        // Default is not not handle errors
783        return null;
784    }
785
786    protected void prepareExchangeForContinue(Exchange exchange, RedeliveryData data, boolean isDeadLetterChannel) {
787        Exception caught = exchange.getException();
788
789        // we continue so clear any exceptions
790        exchange.setException(null);
791        // clear rollback flags
792        exchange.setProperty(Exchange.ROLLBACK_ONLY, null);
793        // reset cached streams so they can be read again
794        MessageHelper.resetStreamCache(exchange.getIn());
795
796        // its continued then remove traces of redelivery attempted and caught exception
797        exchange.getIn().removeHeader(Exchange.REDELIVERED);
798        exchange.getIn().removeHeader(Exchange.REDELIVERY_COUNTER);
799        exchange.getIn().removeHeader(Exchange.REDELIVERY_MAX_COUNTER);
800        exchange.removeProperty(Exchange.FAILURE_HANDLED);
801        // keep the Exchange.EXCEPTION_CAUGHT as property so end user knows the caused exception
802
803        // create log message
804        String msg = "Failed delivery for " + ExchangeHelper.logIds(exchange);
805        msg = msg + ". Exhausted after delivery attempt: " + data.redeliveryCounter + " caught: " + caught;
806        msg = msg + ". Handled and continue routing.";
807
808        // log that we failed but want to continue
809        logFailedDelivery(false, false, false, true, isDeadLetterChannel, exchange, msg, data, null);
810    }
811
812    protected void prepareExchangeForRedelivery(Exchange exchange, RedeliveryData data) {
813        if (!redeliveryEnabled) {
814            throw new IllegalStateException("Redelivery is not enabled on " + this + ". Make sure you have configured the error handler properly.");
815        }
816        // there must be a defensive copy of the exchange
817        ObjectHelper.notNull(data.original, "Defensive copy of Exchange is null", this);
818
819        // okay we will give it another go so clear the exception so we can try again
820        exchange.setException(null);
821
822        // clear rollback flags
823        exchange.setProperty(Exchange.ROLLBACK_ONLY, null);
824
825        // TODO: We may want to store these as state on RedeliveryData so we keep them in case end user messes with Exchange
826        // and then put these on the exchange when doing a redelivery / fault processor
827
828        // preserve these headers
829        Integer redeliveryCounter = exchange.getIn().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
830        Integer redeliveryMaxCounter = exchange.getIn().getHeader(Exchange.REDELIVERY_MAX_COUNTER, Integer.class);
831        Boolean redelivered = exchange.getIn().getHeader(Exchange.REDELIVERED, Boolean.class);
832
833        // we are redelivering so copy from original back to exchange
834        exchange.getIn().copyFrom(data.original.getIn());
835        exchange.setOut(null);
836        // reset cached streams so they can be read again
837        MessageHelper.resetStreamCache(exchange.getIn());
838
839        // put back headers
840        if (redeliveryCounter != null) {
841            exchange.getIn().setHeader(Exchange.REDELIVERY_COUNTER, redeliveryCounter);
842        }
843        if (redeliveryMaxCounter != null) {
844            exchange.getIn().setHeader(Exchange.REDELIVERY_MAX_COUNTER, redeliveryMaxCounter);
845        }
846        if (redelivered != null) {
847            exchange.getIn().setHeader(Exchange.REDELIVERED, redelivered);
848        }
849    }
850
851    protected void handleException(Exchange exchange, RedeliveryData data, boolean isDeadLetterChannel) {
852        Exception e = exchange.getException();
853        // e is never null
854
855        Throwable previous = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
856        if (previous != null && previous != e) {
857            // a 2nd exception was thrown while handling a previous exception
858            // so we need to add the previous as suppressed by the new exception
859            // see also FatalFallbackErrorHandler
860            Throwable[] suppressed = e.getSuppressed();
861            boolean found = false;
862            for (Throwable t : suppressed) {
863                if (t == previous) {
864                    found = true;
865                }
866            }
867            if (!found) {
868                e.addSuppressed(previous);
869            }
870        }
871
872        // store the original caused exception in a property, so we can restore it later
873        exchange.setProperty(Exchange.EXCEPTION_CAUGHT, e);
874
875        // find the error handler to use (if any)
876        OnExceptionDefinition exceptionPolicy = getExceptionPolicy(exchange, e);
877        if (exceptionPolicy != null) {
878            data.currentRedeliveryPolicy = exceptionPolicy.createRedeliveryPolicy(exchange.getContext(), data.currentRedeliveryPolicy);
879            data.handledPredicate = exceptionPolicy.getHandledPolicy();
880            data.continuedPredicate = exceptionPolicy.getContinuedPolicy();
881            data.retryWhilePredicate = exceptionPolicy.getRetryWhilePolicy();
882            data.useOriginalInMessage = exceptionPolicy.getUseOriginalMessagePolicy() != null && exceptionPolicy.getUseOriginalMessagePolicy();
883
884            // route specific failure handler?
885            Processor processor = null;
886            UnitOfWork uow = exchange.getUnitOfWork();
887            if (uow != null && uow.getRouteContext() != null) {
888                String routeId = uow.getRouteContext().getRoute().getId();
889                processor = exceptionPolicy.getErrorHandler(routeId);
890            } else if (!exceptionPolicy.getErrorHandlers().isEmpty()) {
891                // note this should really not happen, but we have this code as a fail safe
892                // to be backwards compatible with the old behavior
893                log.warn("Cannot determine current route from Exchange with id: {}, will fallback and use first error handler.", exchange.getExchangeId());
894                processor = exceptionPolicy.getErrorHandlers().iterator().next();
895            }
896            if (processor != null) {
897                data.failureProcessor = processor;
898            }
899
900            // route specific on redelivery?
901            processor = exceptionPolicy.getOnRedelivery();
902            if (processor != null) {
903                data.onRedeliveryProcessor = processor;
904            }
905            // route specific on exception occurred?
906            processor = exceptionPolicy.getOnExceptionOccurred();
907            if (processor != null) {
908                data.onExceptionProcessor = processor;
909            }
910        }
911
912        // only log if not failure handled or not an exhausted unit of work
913        if (!ExchangeHelper.isFailureHandled(exchange) && !ExchangeHelper.isUnitOfWorkExhausted(exchange)) {
914            String msg = "Failed delivery for " + ExchangeHelper.logIds(exchange)
915                    + ". On delivery attempt: " + data.redeliveryCounter + " caught: " + e;
916            logFailedDelivery(true, false, false, false, isDeadLetterChannel, exchange, msg, data, e);
917        }
918
919        data.redeliveryCounter = incrementRedeliveryCounter(exchange, e, data);
920    }
921
922    /**
923     * Gives an optional configured OnExceptionOccurred processor a chance to process just after an exception
924     * was thrown while processing the Exchange. This allows to execute the processor at the same time the exception was thrown.
925     */
926    protected void onExceptionOccurred(Exchange exchange, final RedeliveryData data) {
927        if (data.onExceptionProcessor == null) {
928            return;
929        }
930
931        // run this synchronously as its just a Processor
932        try {
933            if (log.isTraceEnabled()) {
934                log.trace("OnExceptionOccurred processor {} is processing Exchange: {} due exception occurred", data.onExceptionProcessor, exchange);
935            }
936            data.onExceptionProcessor.process(exchange);
937        } catch (Throwable e) {
938            // we dont not want new exception to override existing, so log it as a WARN
939            log.warn("Error during processing OnExceptionOccurred. This exception is ignored.", e);
940        }
941        log.trace("OnExceptionOccurred processor done");
942    }
943
944    /**
945     * Gives an optional configured redelivery processor a chance to process before the Exchange
946     * will be redelivered. This can be used to alter the Exchange.
947     */
948    protected void deliverToOnRedeliveryProcessor(final Exchange exchange, final RedeliveryData data) {
949        if (data.onRedeliveryProcessor == null) {
950            return;
951        }
952
953        if (log.isTraceEnabled()) {
954            log.trace("Redelivery processor {} is processing Exchange: {} before its redelivered",
955                    data.onRedeliveryProcessor, exchange);
956        }
957
958        // run this synchronously as its just a Processor
959        try {
960            data.onRedeliveryProcessor.process(exchange);
961        } catch (Throwable e) {
962            exchange.setException(e);
963        }
964        log.trace("Redelivery processor done");
965    }
966
967    /**
968     * All redelivery attempts failed so move the exchange to the dead letter queue
969     */
970    protected boolean deliverToFailureProcessor(final Processor processor, final boolean isDeadLetterChannel, final Exchange exchange,
971                                                final RedeliveryData data, final AsyncCallback callback) {
972        boolean sync = true;
973
974        Exception caught = exchange.getException();
975
976        // we did not success with the redelivery so now we let the failure processor handle it
977        // clear exception as we let the failure processor handle it
978        exchange.setException(null);
979
980        final boolean shouldHandle = shouldHandle(exchange, data);
981        final boolean shouldContinue = shouldContinue(exchange, data);
982
983        // regard both handled or continued as being handled
984        boolean handled = false;
985
986        // always handle if dead letter channel
987        boolean handleOrContinue = isDeadLetterChannel || shouldHandle || shouldContinue;
988        if (handleOrContinue) {
989            // its handled then remove traces of redelivery attempted
990            exchange.getIn().removeHeader(Exchange.REDELIVERED);
991            exchange.getIn().removeHeader(Exchange.REDELIVERY_COUNTER);
992            exchange.getIn().removeHeader(Exchange.REDELIVERY_MAX_COUNTER);
993            exchange.removeProperty(Exchange.REDELIVERY_EXHAUSTED);
994
995            // and remove traces of rollback only and uow exhausted markers
996            exchange.removeProperty(Exchange.ROLLBACK_ONLY);
997            exchange.removeProperty(Exchange.UNIT_OF_WORK_EXHAUSTED);
998
999            handled = true;
1000        } else {
1001            // must decrement the redelivery counter as we didn't process the redelivery but is
1002            // handling by the failure handler. So we must -1 to not let the counter be out-of-sync
1003            decrementRedeliveryCounter(exchange);
1004        }
1005
1006        // we should allow using the failure processor if we should not continue
1007        // or in case of continue then the failure processor is NOT a dead letter channel
1008        // because you can continue and still let the failure processor do some routing
1009        // before continue in the main route.
1010        boolean allowFailureProcessor = !shouldContinue || !isDeadLetterChannel;
1011
1012        if (allowFailureProcessor && processor != null) {
1013
1014            // prepare original IN body if it should be moved instead of current body
1015            if (data.useOriginalInMessage) {
1016                log.trace("Using the original IN message instead of current");
1017                Message original = ExchangeHelper.getOriginalInMessage(exchange);
1018                exchange.setIn(original);
1019                if (exchange.hasOut()) {
1020                    log.trace("Removing the out message to avoid some uncertain behavior");
1021                    exchange.setOut(null);
1022                }
1023            }
1024
1025            // reset cached streams so they can be read again
1026            MessageHelper.resetStreamCache(exchange.getIn());
1027
1028            // invoke custom on prepare
1029            if (onPrepareProcessor != null) {
1030                try {
1031                    log.trace("OnPrepare processor {} is processing Exchange: {}", onPrepareProcessor, exchange);
1032                    onPrepareProcessor.process(exchange);
1033                } catch (Exception e) {
1034                    // a new exception was thrown during prepare
1035                    exchange.setException(e);
1036                }
1037            }
1038
1039            log.trace("Failure processor {} is processing Exchange: {}", processor, exchange);
1040
1041            // store the last to endpoint as the failure endpoint
1042            exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
1043            // and store the route id so we know in which route we failed
1044            UnitOfWork uow = exchange.getUnitOfWork();
1045            if (uow != null && uow.getRouteContext() != null) {
1046                exchange.setProperty(Exchange.FAILURE_ROUTE_ID, uow.getRouteContext().getRoute().getId());
1047            }
1048
1049            // fire event as we had a failure processor to handle it, which there is a event for
1050            final boolean deadLetterChannel = processor == data.deadLetterProcessor;
1051
1052            EventHelper.notifyExchangeFailureHandling(exchange.getContext(), exchange, processor, deadLetterChannel, deadLetterUri);
1053
1054            // the failure processor could also be asynchronous
1055            AsyncProcessor afp = AsyncProcessorConverterHelper.convert(processor);
1056            sync = afp.process(exchange, new AsyncCallback() {
1057                public void done(boolean sync) {
1058                    log.trace("Failure processor done: {} processing Exchange: {}", processor, exchange);
1059                    try {
1060                        prepareExchangeAfterFailure(exchange, data, isDeadLetterChannel, shouldHandle, shouldContinue);
1061                        // fire event as we had a failure processor to handle it, which there is a event for
1062                        EventHelper.notifyExchangeFailureHandled(exchange.getContext(), exchange, processor, deadLetterChannel, deadLetterUri);
1063                    } finally {
1064                        // if the fault was handled asynchronously, this should be reflected in the callback as well
1065                        data.sync &= sync;
1066                        callback.done(data.sync);
1067                    }
1068                }
1069            });
1070        } else {
1071            try {
1072                // invoke custom on prepare
1073                if (onPrepareProcessor != null) {
1074                    try {
1075                        log.trace("OnPrepare processor {} is processing Exchange: {}", onPrepareProcessor, exchange);
1076                        onPrepareProcessor.process(exchange);
1077                    } catch (Exception e) {
1078                        // a new exception was thrown during prepare
1079                        exchange.setException(e);
1080                    }
1081                }
1082                // no processor but we need to prepare after failure as well
1083                prepareExchangeAfterFailure(exchange, data, isDeadLetterChannel, shouldHandle, shouldContinue);
1084            } finally {
1085                // callback we are done
1086                callback.done(data.sync);
1087            }
1088        }
1089
1090        // create log message
1091        String msg = "Failed delivery for " + ExchangeHelper.logIds(exchange);
1092        msg = msg + ". Exhausted after delivery attempt: " + data.redeliveryCounter + " caught: " + caught;
1093        if (processor != null) {
1094            if (isDeadLetterChannel && deadLetterUri != null) {
1095                msg = msg + ". Handled by DeadLetterChannel: [" + URISupport.sanitizeUri(deadLetterUri) + "]";
1096            } else {
1097                msg = msg + ". Processed by failure processor: " + processor;
1098            }
1099        }
1100
1101        // log that we failed delivery as we are exhausted
1102        logFailedDelivery(false, false, handled, false, isDeadLetterChannel, exchange, msg, data, null);
1103
1104        return sync;
1105    }
1106
1107    protected void prepareExchangeAfterFailure(final Exchange exchange, final RedeliveryData data, final boolean isDeadLetterChannel,
1108                                               final boolean shouldHandle, final boolean shouldContinue) {
1109
1110        Exception newException = exchange.getException();
1111
1112        // we could not process the exchange so we let the failure processor handled it
1113        ExchangeHelper.setFailureHandled(exchange);
1114
1115        // honor if already set a handling
1116        boolean alreadySet = exchange.getProperty(Exchange.ERRORHANDLER_HANDLED) != null;
1117        if (alreadySet) {
1118            boolean handled = exchange.getProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.class);
1119            log.trace("This exchange has already been marked for handling: {}", handled);
1120            if (!handled) {
1121                // exception not handled, put exception back in the exchange
1122                exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
1123                // and put failure endpoint back as well
1124                exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
1125            }
1126            return;
1127        }
1128
1129        // dead letter channel is special
1130        if (shouldContinue) {
1131            log.trace("This exchange is continued: {}", exchange);
1132            // okay we want to continue then prepare the exchange for that as well
1133            prepareExchangeForContinue(exchange, data, isDeadLetterChannel);
1134        } else if (shouldHandle) {
1135            log.trace("This exchange is handled so its marked as not failed: {}", exchange);
1136            exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.TRUE);
1137        } else {
1138            // okay the redelivery policy are not explicit set to true, so we should allow to check for some
1139            // special situations when using dead letter channel
1140            if (isDeadLetterChannel) {
1141
1142                // DLC is always handling the first thrown exception,
1143                // but if its a new exception then use the configured option
1144                boolean handled = newException == null || data.handleNewException;
1145
1146                // when using DLC then log new exception whether its being handled or not, as otherwise it may appear as
1147                // the DLC swallow new exceptions by default (which is by design to ensure the DLC always complete,
1148                // to avoid causing endless poison messages that fails forever)
1149                if (newException != null && data.currentRedeliveryPolicy.isLogNewException()) {
1150                    String uri = URISupport.sanitizeUri(deadLetterUri);
1151                    String msg = "New exception occurred during processing by the DeadLetterChannel[" + uri + "] due " + newException.getMessage();
1152                    if (handled) {
1153                        msg += ". The new exception is being handled as deadLetterHandleNewException=true.";
1154                    } else {
1155                        msg += ". The new exception is not handled as deadLetterHandleNewException=false.";
1156                    }
1157                    logFailedDelivery(false, true, handled, false, true, exchange, msg, data, newException);
1158                }
1159
1160                if (handled) {
1161                    log.trace("This exchange is handled so its marked as not failed: {}", exchange);
1162                    exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.TRUE);
1163                    return;
1164                }
1165            }
1166
1167            // not handled by default
1168            prepareExchangeAfterFailureNotHandled(exchange);
1169        }
1170    }
1171
1172    private void prepareExchangeAfterFailureNotHandled(Exchange exchange) {
1173        log.trace("This exchange is not handled or continued so its marked as failed: {}", exchange);
1174        // exception not handled, put exception back in the exchange
1175        exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.FALSE);
1176        exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
1177        // and put failure endpoint back as well
1178        exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
1179        // and store the route id so we know in which route we failed
1180        UnitOfWork uow = exchange.getUnitOfWork();
1181        if (uow != null && uow.getRouteContext() != null) {
1182            exchange.setProperty(Exchange.FAILURE_ROUTE_ID, uow.getRouteContext().getRoute().getId());
1183        }
1184    }
1185
1186    private void logFailedDelivery(boolean shouldRedeliver, boolean newException, boolean handled, boolean continued, boolean isDeadLetterChannel,
1187                                   Exchange exchange, String message, RedeliveryData data, Throwable e) {
1188        if (logger == null) {
1189            return;
1190        }
1191
1192        if (!exchange.isRollbackOnly()) {
1193            if (newException && !data.currentRedeliveryPolicy.isLogNewException()) {
1194                // do not log new exception
1195                return;
1196            }
1197
1198            // if we should not rollback, then check whether logging is enabled
1199
1200            if (!newException && handled && !data.currentRedeliveryPolicy.isLogHandled()) {
1201                // do not log handled
1202                return;
1203            }
1204
1205            if (!newException && continued && !data.currentRedeliveryPolicy.isLogContinued()) {
1206                // do not log handled
1207                return;
1208            }
1209
1210            if (!newException && shouldRedeliver && !data.currentRedeliveryPolicy.isLogRetryAttempted()) {
1211                // do not log retry attempts
1212                return;
1213            }
1214
1215            if (!newException && !shouldRedeliver && !data.currentRedeliveryPolicy.isLogExhausted()) {
1216                // do not log exhausted
1217                return;
1218            }
1219        }
1220
1221        LoggingLevel newLogLevel;
1222        boolean logStackTrace;
1223        if (exchange.isRollbackOnly()) {
1224            newLogLevel = data.currentRedeliveryPolicy.getRetriesExhaustedLogLevel();
1225            logStackTrace = data.currentRedeliveryPolicy.isLogStackTrace();
1226        } else if (shouldRedeliver) {
1227            newLogLevel = data.currentRedeliveryPolicy.getRetryAttemptedLogLevel();
1228            logStackTrace = data.currentRedeliveryPolicy.isLogRetryStackTrace();
1229        } else {
1230            newLogLevel = data.currentRedeliveryPolicy.getRetriesExhaustedLogLevel();
1231            logStackTrace = data.currentRedeliveryPolicy.isLogStackTrace();
1232        }
1233        if (e == null) {
1234            e = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
1235        }
1236
1237        if (newException) {
1238            // log at most WARN level
1239            if (newLogLevel == LoggingLevel.ERROR) {
1240                newLogLevel = LoggingLevel.WARN;
1241            }
1242            String msg = message;
1243            if (msg == null) {
1244                msg = "New exception " + ExchangeHelper.logIds(exchange);
1245                // special for logging the new exception
1246                Throwable cause = e;
1247                if (cause != null) {
1248                    msg = msg + " due: " + cause.getMessage();
1249                }
1250            }
1251
1252            if (e != null && logStackTrace) {
1253                logger.log(msg, e, newLogLevel);
1254            } else {
1255                logger.log(msg, newLogLevel);
1256            }
1257        } else if (exchange.isRollbackOnly()) {
1258            String msg = "Rollback " + ExchangeHelper.logIds(exchange);
1259            Throwable cause = exchange.getException() != null ? exchange.getException() : exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
1260            if (cause != null) {
1261                msg = msg + " due: " + cause.getMessage();
1262            }
1263
1264            // should we include message history
1265            if (!shouldRedeliver && data.currentRedeliveryPolicy.isLogExhaustedMessageHistory()) {
1266                // only use the exchange formatter if we should log exhausted message body (and if using a custom formatter then always use it)
1267                ExchangeFormatter formatter = customExchangeFormatter
1268                    ? exchangeFormatter : (data.currentRedeliveryPolicy.isLogExhaustedMessageBody() || camelContext.isLogExhaustedMessageBody() ? exchangeFormatter : null);
1269                String routeStackTrace = MessageHelper.dumpMessageHistoryStacktrace(exchange, formatter, false);
1270                if (routeStackTrace != null) {
1271                    msg = msg + "\n" + routeStackTrace;
1272                }
1273            }
1274
1275            if (newLogLevel == LoggingLevel.ERROR) {
1276                // log intended rollback on maximum WARN level (no ERROR)
1277                logger.log(msg, LoggingLevel.WARN);
1278            } else {
1279                // otherwise use the desired logging level
1280                logger.log(msg, newLogLevel);
1281            }
1282        } else {
1283            String msg = message;
1284            // should we include message history
1285            if (!shouldRedeliver && data.currentRedeliveryPolicy.isLogExhaustedMessageHistory()) {
1286                // only use the exchange formatter if we should log exhausted message body (and if using a custom formatter then always use it)
1287                ExchangeFormatter formatter = customExchangeFormatter
1288                    ? exchangeFormatter : (data.currentRedeliveryPolicy.isLogExhaustedMessageBody() || camelContext.isLogExhaustedMessageBody() ? exchangeFormatter : null);
1289                String routeStackTrace = MessageHelper.dumpMessageHistoryStacktrace(exchange, formatter, e != null && logStackTrace);
1290                if (routeStackTrace != null) {
1291                    msg = msg + "\n" + routeStackTrace;
1292                }
1293            }
1294
1295            if (e != null && logStackTrace) {
1296                logger.log(msg, e, newLogLevel);
1297            } else {
1298                logger.log(msg, newLogLevel);
1299            }
1300        }
1301    }
1302
1303    /**
1304     * Determines whether the exchange is exhausted (or anyway marked to not continue such as rollback).
1305     * <p/>
1306     * If the exchange is exhausted, then we will not continue processing, but let the
1307     * failure processor deal with the exchange.
1308     *
1309     * @param exchange the current exchange
1310     * @param data     the redelivery data
1311     * @return <tt>false</tt> to continue/redeliver, or <tt>true</tt> to exhaust.
1312     */
1313    private boolean isExhausted(Exchange exchange, RedeliveryData data) {
1314        // if marked as rollback only then do not continue/redeliver
1315        boolean exhausted = exchange.getProperty(Exchange.REDELIVERY_EXHAUSTED, false, Boolean.class);
1316        if (exhausted) {
1317            log.trace("This exchange is marked as redelivery exhausted: {}", exchange);
1318            return true;
1319        }
1320
1321        // if marked as rollback only then do not continue/redeliver
1322        boolean rollbackOnly = exchange.getProperty(Exchange.ROLLBACK_ONLY, false, Boolean.class);
1323        if (rollbackOnly) {
1324            log.trace("This exchange is marked as rollback only, so forcing it to be exhausted: {}", exchange);
1325            return true;
1326        }
1327        // its the first original call so continue
1328        if (data.redeliveryCounter == 0) {
1329            return false;
1330        }
1331        // its a potential redelivery so determine if we should redeliver or not
1332        boolean redeliver = data.currentRedeliveryPolicy.shouldRedeliver(exchange, data.redeliveryCounter, data.retryWhilePredicate);
1333        return !redeliver;
1334    }
1335
1336    /**
1337     * Determines whether or not to continue if we are exhausted.
1338     *
1339     * @param exchange the current exchange
1340     * @param data     the redelivery data
1341     * @return <tt>true</tt> to continue, or <tt>false</tt> to exhaust.
1342     */
1343    private boolean shouldContinue(Exchange exchange, RedeliveryData data) {
1344        if (data.continuedPredicate != null) {
1345            return data.continuedPredicate.matches(exchange);
1346        }
1347        // do not continue by default
1348        return false;
1349    }
1350
1351    /**
1352     * Determines whether or not to handle if we are exhausted.
1353     *
1354     * @param exchange the current exchange
1355     * @param data     the redelivery data
1356     * @return <tt>true</tt> to handle, or <tt>false</tt> to exhaust.
1357     */
1358    private boolean shouldHandle(Exchange exchange, RedeliveryData data) {
1359        if (data.handledPredicate != null) {
1360            return data.handledPredicate.matches(exchange);
1361        }
1362        // do not handle by default
1363        return false;
1364    }
1365
1366    /**
1367     * Increments the redelivery counter and adds the redelivered flag if the
1368     * message has been redelivered
1369     */
1370    private int incrementRedeliveryCounter(Exchange exchange, Throwable e, RedeliveryData data) {
1371        Message in = exchange.getIn();
1372        Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
1373        int next = 1;
1374        if (counter != null) {
1375            next = counter + 1;
1376        }
1377        in.setHeader(Exchange.REDELIVERY_COUNTER, next);
1378        in.setHeader(Exchange.REDELIVERED, Boolean.TRUE);
1379        // if maximum redeliveries is used, then provide that information as well
1380        if (data.currentRedeliveryPolicy.getMaximumRedeliveries() > 0) {
1381            in.setHeader(Exchange.REDELIVERY_MAX_COUNTER, data.currentRedeliveryPolicy.getMaximumRedeliveries());
1382        }
1383        return next;
1384    }
1385
1386    /**
1387     * Prepares the redelivery counter and boolean flag for the failure handle processor
1388     */
1389    private void decrementRedeliveryCounter(Exchange exchange) {
1390        Message in = exchange.getIn();
1391        Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
1392        if (counter != null) {
1393            int prev = counter - 1;
1394            in.setHeader(Exchange.REDELIVERY_COUNTER, prev);
1395            // set boolean flag according to counter
1396            in.setHeader(Exchange.REDELIVERED, prev > 0 ? Boolean.TRUE : Boolean.FALSE);
1397        } else {
1398            // not redelivered
1399            in.setHeader(Exchange.REDELIVERY_COUNTER, 0);
1400            in.setHeader(Exchange.REDELIVERED, Boolean.FALSE);
1401        }
1402    }
1403
1404    /**
1405     * Determines if redelivery is enabled by checking if any of the redelivery policy
1406     * settings may allow redeliveries.
1407     *
1408     * @return <tt>true</tt> if redelivery is possible, <tt>false</tt> otherwise
1409     * @throws Exception can be thrown
1410     */
1411    private boolean determineIfRedeliveryIsEnabled() throws Exception {
1412        // determine if redeliver is enabled either on error handler
1413        if (getRedeliveryPolicy().getMaximumRedeliveries() != 0) {
1414            // must check for != 0 as (-1 means redeliver forever)
1415            return true;
1416        }
1417        if (retryWhilePolicy != null) {
1418            return true;
1419        }
1420
1421        // or on the exception policies
1422        if (!exceptionPolicies.isEmpty()) {
1423            // walk them to see if any of them have a maximum redeliveries > 0 or retry until set
1424            for (OnExceptionDefinition def : exceptionPolicies.values()) {
1425
1426                String ref = def.getRedeliveryPolicyRef();
1427                if (ref != null) {
1428                    // lookup in registry if ref provided
1429                    RedeliveryPolicy policy = CamelContextHelper.mandatoryLookup(camelContext, ref, RedeliveryPolicy.class);
1430                    if (policy.getMaximumRedeliveries() != 0) {
1431                        // must check for != 0 as (-1 means redeliver forever)
1432                        return true;
1433                    }
1434                } else if (def.getRedeliveryPolicy() != null) {
1435                    Integer max = CamelContextHelper.parseInteger(camelContext, def.getRedeliveryPolicy().getMaximumRedeliveries());
1436                    if (max != null && max != 0) {
1437                        // must check for != 0 as (-1 means redeliver forever)
1438                        return true;
1439                    }
1440                }
1441
1442                if (def.getRetryWhilePolicy() != null || def.getRetryWhile() != null) {
1443                    return true;
1444                }
1445            }
1446        }
1447
1448        return false;
1449    }
1450
1451    /**
1452     * Gets the number of exchanges that are pending for redelivery
1453     */
1454    public int getPendingRedeliveryCount() {
1455        int answer = redeliverySleepCounter.get();
1456        if (executorService != null && executorService instanceof ThreadPoolExecutor) {
1457            answer += ((ThreadPoolExecutor) executorService).getQueue().size();
1458        }
1459
1460        return answer;
1461    }
1462
1463    @Override
1464    protected void doStart() throws Exception {
1465        ServiceHelper.startServices(output, outputAsync, deadLetter);
1466
1467        // determine if redeliver is enabled or not
1468        redeliveryEnabled = determineIfRedeliveryIsEnabled();
1469        if (log.isTraceEnabled()) {
1470            log.trace("Redelivery enabled: {} on error handler: {}", redeliveryEnabled, this);
1471        }
1472
1473        // we only need thread pool if redelivery is enabled
1474        if (redeliveryEnabled) {
1475            if (executorService == null) {
1476                // use default shared executor service
1477                executorService = camelContext.getErrorHandlerExecutorService();
1478            }
1479            if (log.isDebugEnabled()) {
1480                log.debug("Using ExecutorService: {} for redeliveries on error handler: {}", executorService, this);
1481            }
1482        }
1483
1484        // reset flag when starting
1485        preparingShutdown = false;
1486        redeliverySleepCounter.set(0);
1487    }
1488
1489    @Override
1490    protected void doStop() throws Exception {
1491        // noop, do not stop any services which we only do when shutting down
1492        // as the error handler can be context scoped, and should not stop in case
1493        // a route stops
1494    }
1495
1496    @Override
1497    protected void doShutdown() throws Exception {
1498        ServiceHelper.stopAndShutdownServices(deadLetter, output, outputAsync);
1499    }
1500}