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