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 }