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