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 }