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 java.util.Timer;
020 import java.util.TimerTask;
021 import java.util.concurrent.RejectedExecutionException;
022
023 import org.apache.camel.AsyncCallback;
024 import org.apache.camel.AsyncProcessor;
025 import org.apache.camel.Exchange;
026 import org.apache.camel.LoggingLevel;
027 import org.apache.camel.Message;
028 import org.apache.camel.Predicate;
029 import org.apache.camel.Processor;
030 import org.apache.camel.impl.converter.AsyncProcessorTypeConverter;
031 import org.apache.camel.model.OnExceptionDefinition;
032 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy;
033 import org.apache.camel.util.AsyncProcessorHelper;
034 import org.apache.camel.util.ExchangeHelper;
035 import org.apache.camel.util.MessageHelper;
036 import org.apache.camel.util.ServiceHelper;
037 import org.apache.commons.logging.Log;
038 import org.apache.commons.logging.LogFactory;
039
040 /**
041 * Implements a <a
042 * href="http://camel.apache.org/dead-letter-channel.html">Dead Letter
043 * Channel</a> after attempting to redeliver the message using the
044 * {@link RedeliveryPolicy}
045 *
046 * @version $Revision: 752532 $
047 */
048 public class DeadLetterChannel extends ErrorHandlerSupport implements AsyncProcessor {
049 private static final transient Log LOG = LogFactory.getLog(DeadLetterChannel.class);
050
051 private static Timer timer = new Timer();
052 private Processor output;
053 private Processor deadLetter;
054 private AsyncProcessor outputAsync;
055 private RedeliveryPolicy redeliveryPolicy;
056 private Logger logger;
057 private Processor redeliveryProcessor;
058
059 private class RedeliveryData {
060 int redeliveryCounter;
061 long redeliveryDelay;
062 boolean sync = true;
063 Predicate handledPredicate;
064 Predicate retryUntilPredicate;
065
066 // default behavior which can be overloaded on a per exception basis
067 RedeliveryPolicy currentRedeliveryPolicy = redeliveryPolicy;
068 Processor failureProcessor = deadLetter;
069 Processor onRedeliveryProcessor = redeliveryProcessor;
070 }
071
072 private class RedeliverTimerTask extends TimerTask {
073 private final Exchange exchange;
074 private final AsyncCallback callback;
075 private final RedeliveryData data;
076
077 public RedeliverTimerTask(Exchange exchange, AsyncCallback callback, RedeliveryData data) {
078 this.exchange = exchange;
079 this.callback = callback;
080 this.data = data;
081 }
082
083 @Override
084 public void run() {
085 //only handle the real AsyncProcess the exchange
086 outputAsync.process(exchange, new AsyncCallback() {
087 public void done(boolean sync) {
088 // Only handle the async case...
089 if (sync) {
090 return;
091 }
092 data.sync = false;
093 // only process if the exchange hasn't failed
094 // and it has not been handled by the error processor
095 if (exchange.getException() != null && !ExchangeHelper.isFailureHandled(exchange)) {
096 // if we are redelivering then sleep before trying again
097 asyncProcess(exchange, callback, data);
098 } else {
099 callback.done(sync);
100 }
101 }
102 });
103 }
104 }
105
106 public DeadLetterChannel(Processor output, Processor deadLetter, Processor redeliveryProcessor, RedeliveryPolicy redeliveryPolicy, Logger logger, ExceptionPolicyStrategy exceptionPolicyStrategy) {
107 this.output = output;
108 this.deadLetter = deadLetter;
109 this.redeliveryProcessor = redeliveryProcessor;
110 this.outputAsync = AsyncProcessorTypeConverter.convert(output);
111 this.redeliveryPolicy = redeliveryPolicy;
112 this.logger = logger;
113 setExceptionPolicy(exceptionPolicyStrategy);
114 }
115
116 public static Logger createDefaultLogger() {
117 return new Logger(LOG, LoggingLevel.ERROR);
118 }
119
120 @Override
121 public String toString() {
122 return "DeadLetterChannel[" + output + ", " + deadLetter + "]";
123 }
124
125 public void process(Exchange exchange) throws Exception {
126 AsyncProcessorHelper.process(this, exchange);
127 }
128
129 public boolean process(Exchange exchange, final AsyncCallback callback) {
130 return process(exchange, callback, new RedeliveryData());
131 }
132
133 /**
134 * Processes the exchange using decorated with this dead letter channel.
135 */
136 protected boolean process(final Exchange exchange, final AsyncCallback callback, final RedeliveryData data) {
137
138 while (true) {
139 // we can't keep retrying if the route is being shutdown.
140 if (!isRunAllowed()) {
141 if (LOG.isDebugEnabled()) {
142 LOG.debug("Rejected execution as we are not started for exchange: " + exchange);
143 }
144 if (exchange.getException() == null) {
145 exchange.setException(new RejectedExecutionException());
146 }
147 callback.done(data.sync);
148 return data.sync;
149 }
150
151 // if the exchange is transacted then let the underlying system handle the redelivery etc.
152 // this DeadLetterChannel is only for non transacted exchanges
153 if (exchange.isTransacted() && exchange.getException() != null) {
154 if (LOG.isDebugEnabled()) {
155 LOG.debug("This is a transacted exchange, bypassing this DeadLetterChannel: " + this + " for exchange: " + exchange);
156 }
157 return data.sync;
158 }
159
160 // did previous processing caused an exception?
161 if (exchange.getException() != null) {
162 handleException(exchange, data);
163 }
164
165 // compute if we should redeliver or not
166 boolean shouldRedeliver = shouldRedeliver(exchange, data);
167 if (!shouldRedeliver) {
168 return deliverToFaultProcessor(exchange, callback, data);
169 }
170
171 // if we are redelivering then sleep before trying again
172 if (data.redeliveryCounter > 0) {
173 // okay we will give it another go so clear the exception so we can try again
174 if (exchange.getException() != null) {
175 exchange.setException(null);
176 }
177
178 // reset cached streams so they can be read again
179 MessageHelper.resetStreamCache(exchange.getIn());
180
181 // wait until we should redeliver
182 try {
183 data.redeliveryDelay = data.currentRedeliveryPolicy.sleep(data.redeliveryDelay, data.redeliveryCounter);
184 } catch (InterruptedException e) {
185 LOG.debug("Sleep interrupted, are we stopping? " + (isStopping() || isStopped()));
186 // continue from top
187 continue;
188 }
189
190 // letting onRedeliver be executed
191 deliverToRedeliveryProcessor(exchange, callback, data);
192 }
193
194 // process the exchange
195 boolean sync = outputAsync.process(exchange, new AsyncCallback() {
196 public void done(boolean sync) {
197 // Only handle the async case...
198 if (sync) {
199 return;
200 }
201 data.sync = false;
202 // only process if the exchange hasn't failed
203 // and it has not been handled by the error processor
204 if (exchange.getException() != null && !ExchangeHelper.isFailureHandled(exchange)) {
205 //TODO Call the Timer for the asyncProcessor
206 asyncProcess(exchange, callback, data);
207 } else {
208 callback.done(sync);
209 }
210 }
211 });
212 if (!sync) {
213 // It is going to be processed async..
214 return false;
215 }
216 if (exchange.getException() == null || ExchangeHelper.isFailureHandled(exchange)) {
217 // If everything went well.. then we exit here..
218 callback.done(true);
219 return true;
220 }
221 // error occurred so loop back around.....
222 }
223
224 }
225
226 protected void asyncProcess(final Exchange exchange, final AsyncCallback callback, final RedeliveryData data) {
227 // set the timer here
228 if (!isRunAllowed()) {
229 if (exchange.getException() == null) {
230 exchange.setException(new RejectedExecutionException());
231 }
232 callback.done(data.sync);
233 return;
234 }
235
236 // if the exchange is transacted then let the underlying system handle the redelivery etc.
237 // this DeadLetterChannel is only for non transacted exchanges
238 if (exchange.isTransacted() && exchange.getException() != null) {
239 if (LOG.isDebugEnabled()) {
240 LOG.debug("This is a transacted exchange, bypassing this DeadLetterChannel: " + this + " for exchange: " + exchange);
241 }
242 return;
243 }
244
245 // did previous processing caused an exception?
246 if (exchange.getException() != null) {
247 handleException(exchange, data);
248 }
249
250 // compute if we should redeliver or not
251 boolean shouldRedeliver = shouldRedeliver(exchange, data);
252 if (!shouldRedeliver) {
253 deliverToFaultProcessor(exchange, callback, data);
254 return;
255 }
256
257 // process the next try
258 // if we are redelivering then sleep before trying again
259 if (data.redeliveryCounter > 0) {
260 // okay we will give it another go so clear the exception so we can try again
261 if (exchange.getException() != null) {
262 exchange.setException(null);
263 }
264 // wait until we should redeliver
265 data.redeliveryDelay = data.currentRedeliveryPolicy.calculateRedeliveryDelay(data.redeliveryDelay, data.redeliveryCounter);
266 timer.schedule(new RedeliverTimerTask(exchange, callback, data), data.redeliveryDelay);
267
268 // letting onRedeliver be executed
269 deliverToRedeliveryProcessor(exchange, callback, data);
270 }
271 }
272
273 private void handleException(Exchange exchange, RedeliveryData data) {
274 Throwable e = exchange.getException();
275
276 // store the original caused exception in a property, so we can restore it later
277 exchange.setProperty(Exchange.EXCEPTION_CAUGHT, e);
278
279 // find the error handler to use (if any)
280 OnExceptionDefinition exceptionPolicy = getExceptionPolicy(exchange, e);
281 if (exceptionPolicy != null) {
282 data.currentRedeliveryPolicy = exceptionPolicy.createRedeliveryPolicy(exchange.getContext(), data.currentRedeliveryPolicy);
283 data.handledPredicate = exceptionPolicy.getHandledPolicy();
284 data.retryUntilPredicate = exceptionPolicy.getRetryUntilPolicy();
285
286 // route specific failure handler?
287 Processor processor = exceptionPolicy.getErrorHandler();
288 if (processor != null) {
289 data.failureProcessor = processor;
290 }
291 // route specific on redelivey?
292 processor = exceptionPolicy.getOnRedelivery();
293 if (processor != null) {
294 data.onRedeliveryProcessor = processor;
295 }
296 }
297
298 String msg = "Failed delivery for exchangeId: " + exchange.getExchangeId()
299 + ". On delivery attempt: " + data.redeliveryCounter + " caught: " + e;
300 logFailedDelivery(true, exchange, msg, data, e);
301
302 data.redeliveryCounter = incrementRedeliveryCounter(exchange, e);
303 }
304
305 /**
306 * Gives an optional configure redelivery processor a chance to process before the Exchange
307 * will be redelivered. This can be used to alter the Exchange.
308 */
309 private void deliverToRedeliveryProcessor(final Exchange exchange, final AsyncCallback callback,
310 final RedeliveryData data) {
311 if (data.onRedeliveryProcessor == null) {
312 return;
313 }
314
315 if (LOG.isTraceEnabled()) {
316 LOG.trace("RedeliveryProcessor " + data.onRedeliveryProcessor + " is processing Exchange: " + exchange + " before its redelivered");
317 }
318
319 AsyncProcessor afp = AsyncProcessorTypeConverter.convert(data.onRedeliveryProcessor);
320 afp.process(exchange, new AsyncCallback() {
321 public void done(boolean sync) {
322 LOG.trace("Redelivery processor done");
323 // do NOT call done on callback as this is the redelivery processor that
324 // is done. we should not mark the entire exchange as done.
325 }
326 });
327 }
328
329 private boolean deliverToFaultProcessor(final Exchange exchange, final AsyncCallback callback,
330 final RedeliveryData data) {
331 // we did not success with the redelivery so now we let the failure processor handle it
332 ExchangeHelper.setFailureHandled(exchange);
333 // must decrement the redelivery counter as we didn't process the redelivery but is
334 // handling by the failure handler. So we must -1 to not let the counter be out-of-sync
335 decrementRedeliveryCounter(exchange);
336
337 AsyncProcessor afp = AsyncProcessorTypeConverter.convert(data.failureProcessor);
338 boolean sync = afp.process(exchange, new AsyncCallback() {
339 public void done(boolean sync) {
340 LOG.trace("Fault processor done");
341 restoreExceptionOnExchange(exchange, data.handledPredicate);
342 callback.done(data.sync);
343 }
344 });
345
346 String msg = "Failed delivery for exchangeId: " + exchange.getExchangeId()
347 + ". Handled by the failure processor: " + data.failureProcessor;
348 logFailedDelivery(false, exchange, msg, data, null);
349
350 return sync;
351 }
352
353 // Properties
354 // -------------------------------------------------------------------------
355
356 /**
357 * Returns the output processor
358 */
359 public Processor getOutput() {
360 return output;
361 }
362
363 /**
364 * Returns the dead letter that message exchanges will be sent to if the
365 * redelivery attempts fail
366 */
367 public Processor getDeadLetter() {
368 return deadLetter;
369 }
370
371 public RedeliveryPolicy getRedeliveryPolicy() {
372 return redeliveryPolicy;
373 }
374
375 /**
376 * Sets the redelivery policy
377 */
378 public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
379 this.redeliveryPolicy = redeliveryPolicy;
380 }
381
382 public Logger getLogger() {
383 return logger;
384 }
385
386 /**
387 * Sets the logger strategy; which {@link Log} to use and which
388 * {@link LoggingLevel} to use
389 */
390 public void setLogger(Logger logger) {
391 this.logger = logger;
392 }
393
394 // Implementation methods
395
396 // -------------------------------------------------------------------------
397
398 protected static void restoreExceptionOnExchange(Exchange exchange, Predicate handledPredicate) {
399 if (handledPredicate == null || !handledPredicate.matches(exchange)) {
400 if (LOG.isDebugEnabled()) {
401 LOG.debug("This exchange is not handled so its marked as failed: " + exchange);
402 }
403 // exception not handled, put exception back in the exchange
404 exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class));
405 } else {
406 if (LOG.isDebugEnabled()) {
407 LOG.debug("This exchange is handled so its marked as not failed: " + exchange);
408 }
409 exchange.setProperty(Exchange.EXCEPTION_HANDLED, Boolean.TRUE);
410 }
411 }
412
413 private void logFailedDelivery(boolean shouldRedeliver, Exchange exchange, String message, RedeliveryData data, Throwable e) {
414 LoggingLevel newLogLevel;
415 if (shouldRedeliver) {
416 newLogLevel = data.currentRedeliveryPolicy.getRetryAttemptedLogLevel();
417 } else {
418 newLogLevel = data.currentRedeliveryPolicy.getRetriesExhaustedLogLevel();
419 }
420 if (data.currentRedeliveryPolicy.isLogStackTrace() && e != null) {
421 logger.log(message, e, newLogLevel);
422 } else {
423 logger.log(message, newLogLevel);
424 }
425 }
426
427 private boolean shouldRedeliver(Exchange exchange, RedeliveryData data) {
428 return data.currentRedeliveryPolicy.shouldRedeliver(exchange, data.redeliveryCounter, data.retryUntilPredicate);
429 }
430
431 /**
432 * Increments the redelivery counter and adds the redelivered flag if the
433 * message has been redelivered
434 */
435 protected int incrementRedeliveryCounter(Exchange exchange, Throwable e) {
436 Message in = exchange.getIn();
437 Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
438 int next = 1;
439 if (counter != null) {
440 next = counter + 1;
441 }
442 in.setHeader(Exchange.REDELIVERY_COUNTER, next);
443 in.setHeader(Exchange.REDELIVERED, Boolean.TRUE);
444 return next;
445 }
446
447 /**
448 * Prepares the redelivery counter and boolean flag for the failure handle processor
449 */
450 private void decrementRedeliveryCounter(Exchange exchange) {
451 Message in = exchange.getIn();
452 Integer counter = in.getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);
453 if (counter != null) {
454 int prev = counter - 1;
455 in.setHeader(Exchange.REDELIVERY_COUNTER, prev);
456 // set boolean flag according to counter
457 in.setHeader(Exchange.REDELIVERED, prev > 0 ? Boolean.TRUE : Boolean.FALSE);
458 } else {
459 // not redelivered
460 in.setHeader(Exchange.REDELIVERY_COUNTER, 0);
461 in.setHeader(Exchange.REDELIVERED, Boolean.FALSE);
462 }
463 }
464
465 @Override
466 protected void doStart() throws Exception {
467 ServiceHelper.startServices(output, deadLetter);
468 }
469
470 @Override
471 protected void doStop() throws Exception {
472 ServiceHelper.stopServices(deadLetter, output);
473 }
474
475 }