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