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     */
017    package org.apache.camel.processor;
018    
019    import org.apache.camel.CamelExchangeException;
020    import org.apache.camel.Exchange;
021    import org.apache.camel.LoggingLevel;
022    import org.apache.camel.Message;
023    import org.apache.camel.Predicate;
024    import org.apache.camel.Processor;
025    import org.apache.camel.model.OnExceptionDefinition;
026    import org.apache.camel.util.EventHelper;
027    import org.apache.camel.util.ExchangeHelper;
028    import org.apache.camel.util.MessageHelper;
029    import org.apache.camel.util.ServiceHelper;
030    
031    /**
032     * Base redeliverable error handler that also supports a final dead letter queue in case
033     * all redelivery attempts fail.
034     * <p/>
035     * This implementation should contain all the error handling logic and the sub classes
036     * should only configure it according to what they support.
037     *
038     * @version $Revision: 906417 $
039     */
040    public abstract class RedeliveryErrorHandler extends ErrorHandlerSupport implements Processor {
041    
042        protected final Processor deadLetter;
043        protected final String deadLetterUri;
044        protected final Processor output;
045        protected final Processor redeliveryProcessor;
046        protected final RedeliveryPolicy redeliveryPolicy;
047        protected final Predicate handledPolicy;
048        protected final Logger logger;
049        protected final boolean useOriginalMessagePolicy;
050    
051        protected class RedeliveryData {
052            int redeliveryCounter;
053            long redeliveryDelay;
054            Predicate retryUntilPredicate;
055    
056            // default behavior which can be overloaded on a per exception basis
057            RedeliveryPolicy currentRedeliveryPolicy = redeliveryPolicy;
058            Processor deadLetterProcessor = deadLetter;
059            Processor failureProcessor;
060            Processor onRedeliveryProcessor = redeliveryProcessor;
061            Predicate handledPredicate = handledPolicy;
062            boolean useOriginalInMessage = useOriginalMessagePolicy;
063        }
064    
065        public RedeliveryErrorHandler(Processor output, Logger logger, Processor redeliveryProcessor,
066                                      RedeliveryPolicy redeliveryPolicy, Predicate handledPolicy, Processor deadLetter,
067                                      String deadLetterUri, boolean useOriginalMessagePolicy) {
068            this.redeliveryProcessor = redeliveryProcessor;
069            this.deadLetter = deadLetter;
070            this.output = output;
071            this.redeliveryPolicy = redeliveryPolicy;
072            this.logger = logger;
073            this.deadLetterUri = deadLetterUri;
074            this.handledPolicy = handledPolicy;
075            this.useOriginalMessagePolicy = useOriginalMessagePolicy;
076        }
077    
078        public boolean supportTransacted() {
079            return false;
080        }
081    
082        public void process(Exchange exchange) throws Exception {
083            if (output == null) {
084                // no output then just return
085                return;
086            }
087    
088            processErrorHandler(exchange, new RedeliveryData());
089        }
090    
091        /**
092         * Processes the exchange decorated with this dead letter channel.
093         */
094        protected void processErrorHandler(final Exchange exchange, final RedeliveryData data) throws Exception {
095            while (true) {
096    
097                // did previous processing cause an exception?
098                boolean handle = shouldHandleException(exchange);
099                if (handle) {
100                    handleException(exchange, data);
101                }
102    
103                // compute if we should redeliver or not
104                boolean shouldRedeliver = shouldRedeliver(exchange, data);
105                if (!shouldRedeliver) {
106                    // no we should not redeliver to the same output so either try an onException (if any given)
107                    // or the dead letter queue
108                    Processor target = data.failureProcessor != null ? data.failureProcessor : data.deadLetterProcessor;
109                    // deliver to the failure processor (either an on exception or dead letter queue
110                    deliverToFailureProcessor(target, exchange, data);
111                    // prepare the exchange for failure before returning
112                    prepareExchangeAfterFailure(exchange, data);
113                    // fire event if we had a failure processor to handle it
114                    if (target != null) {
115                        boolean deadLetterChannel = target == data.deadLetterProcessor && data.deadLetterProcessor != null;
116                        EventHelper.notifyExchangeFailureHandled(exchange.getContext(), exchange, target, deadLetterChannel);
117                    }
118                    // and then return
119                    return;
120                }
121    
122                // if we are redelivering then sleep before trying again
123                if (shouldRedeliver && data.redeliveryCounter > 0) {
124                    prepareExchangeForRedelivery(exchange);
125    
126                    // wait until we should redeliver
127                    try {
128                        data.redeliveryDelay = data.currentRedeliveryPolicy.sleep(data.redeliveryDelay, data.redeliveryCounter);
129                    } catch (InterruptedException e) {
130                        if (log.isDebugEnabled()) {
131                            log.debug("Sleep interrupted, are we stopping? " + (isStopping() || isStopped()));
132                        }
133                        // continue from top
134                        continue;
135                    }
136    
137                    // letting onRedeliver be executed
138                    deliverToRedeliveryProcessor(exchange, data);
139                }
140    
141                // process the exchange (also redelivery)
142                try {
143                    processExchange(exchange);
144                } catch (Exception e) {
145                    exchange.setException(e);
146                } catch (Throwable t) {
147                    // let Camel error handle take care of all kind of exceptions now
148                    exchange.setException(new CamelExchangeException("Error processing Exchange", exchange, t));
149                }
150    
151                boolean done = isDone(exchange);
152                if (done) {
153                    return;
154                }
155                // error occurred so loop back around.....
156            }
157    
158        }
159    
160        /**
161         * Strategy whether the exchange has an exception that we should try to handle.
162         * <p/>
163         * Standard implementations should just look for an exception.
164         */
165        protected boolean shouldHandleException(Exchange exchange) {
166            return exchange.getException() != null;
167        }
168    
169        /**
170         * Strategy to process the given exchange to the designated output.
171         * <p/>
172         * This happens when the exchange is processed the first time and also for redeliveries
173         * to the same destination.
174         */
175        protected void processExchange(Exchange exchange) throws Exception {
176            // process the exchange (also redelivery)
177            output.process(exchange);
178        }
179    
180        /**
181         * Strategy to determine if the exchange is done so we can continue
182         */
183        protected boolean isDone(Exchange exchange) throws Exception {
184            // only done if the exchange hasn't failed
185            // and it has not been handled by the failure processor
186            // or we are exhausted
187            return exchange.getException() == null
188                || ExchangeHelper.isFailureHandled(exchange)
189                || ExchangeHelper.isRedelieryExhausted(exchange);
190        }
191    
192        /**
193         * Returns the output processor
194         */
195        public Processor getOutput() {
196            return output;
197        }
198    
199        /**
200         * Returns the dead letter that message exchanges will be sent to if the
201         * redelivery attempts fail
202         */
203        public Processor getDeadLetter() {
204            return deadLetter;
205        }
206    
207        public String getDeadLetterUri() {
208            return deadLetterUri;
209        }
210    
211        public boolean isUseOriginalMessagePolicy() {
212            return useOriginalMessagePolicy;
213        }
214    
215        public RedeliveryPolicy getRedeliveryPolicy() {
216            return redeliveryPolicy;
217        }
218    
219        public Logger getLogger() {
220            return logger;
221        }
222    
223        protected void prepareExchangeForRedelivery(Exchange exchange) {
224            // okay we will give it another go so clear the exception so we can try again
225            if (exchange.getException() != null) {
226                exchange.setException(null);
227            }
228    
229            // clear rollback flags
230            exchange.setProperty(Exchange.ROLLBACK_ONLY, null);
231    
232            // reset cached streams so they can be read again
233            MessageHelper.resetStreamCache(exchange.getIn());
234        }
235    
236        protected void handleException(Exchange exchange, RedeliveryData data) {
237            Exception e = exchange.getException();
238    
239            // store the original caused exception in a property, so we can restore it later
240            exchange.setProperty(Exchange.EXCEPTION_CAUGHT, e);
241    
242            // find the error handler to use (if any)
243            OnExceptionDefinition exceptionPolicy = getExceptionPolicy(exchange, e);
244            if (exceptionPolicy != null) {
245                data.currentRedeliveryPolicy = exceptionPolicy.createRedeliveryPolicy(exchange.getContext(), data.currentRedeliveryPolicy);
246                data.handledPredicate = exceptionPolicy.getHandledPolicy();
247                data.retryUntilPredicate = exceptionPolicy.getRetryUntilPolicy();
248                data.useOriginalInMessage = exceptionPolicy.getUseOriginalMessagePolicy();
249    
250                // route specific failure handler?
251                Processor processor = exceptionPolicy.getErrorHandler();
252                if (processor != null) {
253                    data.failureProcessor = processor;
254                }
255                // route specific on redelivery?
256                processor = exceptionPolicy.getOnRedelivery();
257                if (processor != null) {
258                    data.onRedeliveryProcessor = processor;
259                }
260            }
261    
262            String msg = "Failed delivery for exchangeId: " + exchange.getExchangeId()
263                    + ". On delivery attempt: " + data.redeliveryCounter + " caught: " + e;
264            logFailedDelivery(true, exchange, msg, data, e);
265    
266            data.redeliveryCounter = incrementRedeliveryCounter(exchange, e);
267        }
268    
269        /**
270         * Gives an optional configure redelivery processor a chance to process before the Exchange
271         * will be redelivered. This can be used to alter the Exchange.
272         */
273        protected void deliverToRedeliveryProcessor(final Exchange exchange, final RedeliveryData data) {
274            if (data.onRedeliveryProcessor == null) {
275                return;
276            }
277    
278            if (log.isTraceEnabled()) {
279                log.trace("Redelivery processor " + data.onRedeliveryProcessor + " is processing Exchange: " + exchange
280                        + " before its redelivered");
281            }
282    
283            try {
284                data.onRedeliveryProcessor.process(exchange);
285            } catch (Exception e) {
286                exchange.setException(e);
287            }
288            log.trace("Redelivery processor done");
289        }
290    
291        /**
292         * All redelivery attempts failed so move the exchange to the dead letter queue
293         */
294        protected void deliverToFailureProcessor(final Processor processor, final Exchange exchange,
295                                                 final RedeliveryData data) {
296    
297            Exception caught = exchange.getException();
298    
299            // we did not success with the redelivery so now we let the failure processor handle it
300            // clear exception as we let the failure processor handle it
301            exchange.setException(null);
302    
303            if (data.handledPredicate != null && data.handledPredicate.matches(exchange)) {
304                // its handled then remove traces of redelivery attempted
305                exchange.getIn().removeHeader(Exchange.REDELIVERED);
306                exchange.getIn().removeHeader(Exchange.REDELIVERY_COUNTER);
307            } else {
308                // must decrement the redelivery counter as we didn't process the redelivery but is
309                // handling by the failure handler. So we must -1 to not let the counter be out-of-sync
310                decrementRedeliveryCounter(exchange);
311            }
312    
313            // reset cached streams so they can be read again
314            MessageHelper.resetStreamCache(exchange.getIn());
315    
316            if (processor != null) {
317                // prepare original IN body if it should be moved instead of current body
318                if (data.useOriginalInMessage) {
319                    if (log.isTraceEnabled()) {
320                        log.trace("Using the original IN message instead of current");
321                    }
322    
323                    Message original = exchange.getUnitOfWork().getOriginalInMessage();
324                    exchange.setIn(original);
325                }
326    
327                if (log.isTraceEnabled()) {
328                    log.trace("Failure processor " + processor + " is processing Exchange: " + exchange);
329                }
330                try {
331                    // store the last to endpoint as the failure endpoint
332                    exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
333                    processor.process(exchange);
334                } catch (Exception e) {
335                    exchange.setException(e);
336                }
337                log.trace("Failure processor done");
338            }
339    
340            // create log message
341            String msg = "Failed delivery for exchangeId: " + exchange.getExchangeId();
342            msg = msg + ". Exhausted after delivery attempt: " + data.redeliveryCounter + " caught: " + caught;
343            if (processor != null) {
344                msg = msg + ". Processed by failure processor: " + processor;
345            }
346    
347            // log that we failed delivery as we are exhausted
348            logFailedDelivery(false, exchange, msg, data, null);
349        }
350    
351        protected void prepareExchangeAfterFailure(final Exchange exchange, final RedeliveryData data) {
352            // TODO: setting failure handled should only be if we used a failure processor
353            // we could not process the exchange so we let the failure processor handled it
354            ExchangeHelper.setFailureHandled(exchange);
355    
356            // honor if already set a handling
357            boolean alreadySet = exchange.getProperty(Exchange.ERRORHANDLER_HANDLED) != null;
358            if (alreadySet) {
359                boolean handled = exchange.getProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.class);
360                if (log.isDebugEnabled()) {
361                    log.debug("This exchange has already been marked for handling: " + handled);
362                }
363                if (handled) {
364                    exchange.setException(null);
365                } else {
366                    // exception not handled, put exception back in the exchange
367                    exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
368                    // and put failure endpoint back as well
369                    exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
370                }
371                return;
372            }
373    
374            Predicate handledPredicate = data.handledPredicate;
375            if (handledPredicate == null || !handledPredicate.matches(exchange)) {
376                if (log.isDebugEnabled()) {
377                    log.debug("This exchange is not handled so its marked as failed: " + exchange);
378                }
379                // exception not handled, put exception back in the exchange
380                exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.FALSE);
381                exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
382                // and put failure endpoint back as well
383                exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
384            } else {
385                if (log.isDebugEnabled()) {
386                    log.debug("This exchange is handled so its marked as not failed: " + exchange);
387                }
388                exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, Boolean.TRUE);
389            }
390        }
391    
392        private void logFailedDelivery(boolean shouldRedeliver, Exchange exchange, String message, RedeliveryData data, Throwable e) {
393            if (logger == null) {
394                return;
395            }
396    
397            LoggingLevel newLogLevel;
398            boolean logStrackTrace;
399            if (shouldRedeliver) {
400                newLogLevel = data.currentRedeliveryPolicy.getRetryAttemptedLogLevel();
401                logStrackTrace = data.currentRedeliveryPolicy.isLogRetryStackTrace();
402            } else {
403                newLogLevel = data.currentRedeliveryPolicy.getRetriesExhaustedLogLevel();
404                logStrackTrace = data.currentRedeliveryPolicy.isLogStackTrace();
405            }
406            if (e == null) {
407                e = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
408            }
409    
410            if (exchange.isRollbackOnly()) {
411                String msg = "Rollback exchange";
412                if (exchange.getException() != null) {
413                    msg = msg + " due: " + exchange.getException().getMessage();
414                }
415                if (newLogLevel == LoggingLevel.ERROR || newLogLevel == LoggingLevel.FATAL) {
416                    // log intended rollback on maximum WARN level (no ERROR or FATAL)
417                    logger.log(msg, LoggingLevel.WARN);
418                } else {
419                    // otherwise use the desired logging level
420                    logger.log(msg, newLogLevel);
421                }
422            } else if (e != null && logStrackTrace) {
423                logger.log(message, e, newLogLevel);
424            } else {
425                logger.log(message, newLogLevel);
426            }
427        }
428    
429        private boolean shouldRedeliver(Exchange exchange, RedeliveryData data) {
430            // if marked as rollback only then do not redeliver
431            boolean rollbackOnly = exchange.getProperty(Exchange.ROLLBACK_ONLY, false, Boolean.class);
432            if (rollbackOnly) {
433                if (log.isTraceEnabled()) {
434                    log.trace("This exchange is marked as rollback only, should not be redelivered: " + exchange);
435                }
436                return false;
437            }
438            return data.currentRedeliveryPolicy.shouldRedeliver(exchange, data.redeliveryCounter, data.retryUntilPredicate);
439        }
440    
441        /**
442         * Increments the redelivery counter and adds the redelivered flag if the
443         * message has been redelivered
444         */
445        private int incrementRedeliveryCounter(Exchange exchange, Throwable e) {
446            Message in = exchange.getIn();
447            Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
448            int next = 1;
449            if (counter != null) {
450                next = counter + 1;
451            }
452            in.setHeader(Exchange.REDELIVERY_COUNTER, next);
453            in.setHeader(Exchange.REDELIVERED, Boolean.TRUE);
454            return next;
455        }
456    
457        /**
458         * Prepares the redelivery counter and boolean flag for the failure handle processor
459         */
460        private void decrementRedeliveryCounter(Exchange exchange) {
461            Message in = exchange.getIn();
462            Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
463            if (counter != null) {
464                int prev = counter - 1;
465                in.setHeader(Exchange.REDELIVERY_COUNTER, prev);
466                // set boolean flag according to counter
467                in.setHeader(Exchange.REDELIVERED, prev > 0 ? Boolean.TRUE : Boolean.FALSE);
468            } else {
469                // not redelivered
470                in.setHeader(Exchange.REDELIVERY_COUNTER, 0);
471                in.setHeader(Exchange.REDELIVERED, Boolean.FALSE);
472            }
473        }
474    
475        @Override
476        protected void doStart() throws Exception {
477            ServiceHelper.startServices(output, deadLetter);
478        }
479    
480        @Override
481        protected void doStop() throws Exception {
482            ServiceHelper.stopServices(deadLetter, output);
483        }
484    
485    }