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.concurrent.RejectedExecutionException; 020 021 import org.apache.camel.AsyncCallback; 022 import org.apache.camel.AsyncProcessor; 023 import org.apache.camel.Exchange; 024 import org.apache.camel.Message; 025 import org.apache.camel.Processor; 026 import org.apache.camel.impl.converter.AsyncProcessorTypeConverter; 027 import org.apache.camel.model.ExceptionType; 028 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy; 029 import org.apache.camel.util.AsyncProcessorHelper; 030 import org.apache.camel.util.ServiceHelper; 031 import org.apache.commons.logging.Log; 032 import org.apache.commons.logging.LogFactory; 033 034 /** 035 * Implements a <a 036 * href="http://activemq.apache.org/camel/dead-letter-channel.html">Dead Letter 037 * Channel</a> after attempting to redeliver the message using the 038 * {@link RedeliveryPolicy} 039 * 040 * @version $Revision: 37863 $ 041 */ 042 public class DeadLetterChannel extends ErrorHandlerSupport implements AsyncProcessor { 043 public static final String REDELIVERY_COUNTER = "org.apache.camel.RedeliveryCounter"; 044 public static final String REDELIVERED = "org.apache.camel.Redelivered"; 045 public static final String EXCEPTION_CAUSE_PROPERTY = "CamelCauseException"; 046 047 private class RedeliveryData { 048 int redeliveryCounter; 049 long redeliveryDelay; 050 boolean sync = true; 051 052 // default behaviour which can be overloaded on a per exception basis 053 RedeliveryPolicy currentRedeliveryPolicy = redeliveryPolicy; 054 Processor failureProcessor = deadLetter; 055 } 056 057 private static final transient Log LOG = LogFactory.getLog(DeadLetterChannel.class); 058 private static final String FAILURE_HANDLED_PROPERTY = DeadLetterChannel.class.getName() + ".FAILURE_HANDLED"; 059 private Processor output; 060 private Processor deadLetter; 061 private AsyncProcessor outputAsync; 062 private RedeliveryPolicy redeliveryPolicy; 063 private Logger logger; 064 065 public DeadLetterChannel(Processor output, Processor deadLetter) { 066 this(output, deadLetter, new RedeliveryPolicy(), DeadLetterChannel.createDefaultLogger(), 067 ErrorHandlerSupport.createDefaultExceptionPolicyStrategy()); 068 } 069 070 public DeadLetterChannel(Processor output, Processor deadLetter, RedeliveryPolicy redeliveryPolicy, Logger logger, ExceptionPolicyStrategy exceptionPolicyStrategy) { 071 this.deadLetter = deadLetter; 072 this.output = output; 073 this.outputAsync = AsyncProcessorTypeConverter.convert(output); 074 075 this.redeliveryPolicy = redeliveryPolicy; 076 this.logger = logger; 077 setExceptionPolicy(exceptionPolicyStrategy); 078 } 079 080 public static <E extends Exchange> Logger createDefaultLogger() { 081 return new Logger(LOG, LoggingLevel.ERROR); 082 } 083 084 @Override 085 public String toString() { 086 return "DeadLetterChannel[" + output + ", " + deadLetter + ", " + redeliveryPolicy + "]"; 087 } 088 089 public boolean process(Exchange exchange, final AsyncCallback callback) { 090 return process(exchange, callback, new RedeliveryData()); 091 } 092 093 public boolean process(final Exchange exchange, final AsyncCallback callback, final RedeliveryData data) { 094 095 while (true) { 096 097 // We can't keep retrying if the route is being shutdown. 098 if (!isRunAllowed()) { 099 if (exchange.getException() == null) { 100 exchange.setException(new RejectedExecutionException()); 101 } 102 callback.done(data.sync); 103 return data.sync; 104 } 105 106 if (exchange.getException() != null) { 107 Throwable e = exchange.getException(); 108 exchange.setException(null); // Reset it since we are handling it. 109 110 logger.log("Failed delivery for exchangeId: " + exchange.getExchangeId() + ". On delivery attempt: " + data.redeliveryCounter + " caught: " + e, e); 111 data.redeliveryCounter = incrementRedeliveryCounter(exchange, e); 112 113 ExceptionType exceptionPolicy = getExceptionPolicy(exchange, e); 114 if (exceptionPolicy != null) { 115 data.currentRedeliveryPolicy = exceptionPolicy.createRedeliveryPolicy(data.currentRedeliveryPolicy); 116 Processor processor = exceptionPolicy.getErrorHandler(); 117 if (processor != null) { 118 data.failureProcessor = processor; 119 } 120 } 121 } 122 123 if (!data.currentRedeliveryPolicy.shouldRedeliver(data.redeliveryCounter)) { 124 setFailureHandled(exchange, true); 125 AsyncProcessor afp = AsyncProcessorTypeConverter.convert(data.failureProcessor); 126 boolean sync = afp.process(exchange, new AsyncCallback() { 127 public void done(boolean sync) { 128 restoreExceptionOnExchange(exchange); 129 callback.done(data.sync); 130 } 131 }); 132 133 restoreExceptionOnExchange(exchange); 134 logger.log("Failed delivery for exchangeId: " + exchange.getExchangeId() + ". Handled by the failure processor: " + data.failureProcessor); 135 return sync; 136 } 137 138 if (data.redeliveryCounter > 0) { 139 // Figure out how long we should wait to resend this message. 140 data.redeliveryDelay = data.currentRedeliveryPolicy.getRedeliveryDelay(data.redeliveryDelay); 141 sleep(data.redeliveryDelay); 142 } 143 144 exchange.setProperty(EXCEPTION_CAUSE_PROPERTY, exchange.getException()); 145 exchange.setException(null); 146 boolean sync = outputAsync.process(exchange, new AsyncCallback() { 147 public void done(boolean sync) { 148 // Only handle the async case... 149 if (sync) { 150 return; 151 } 152 data.sync = false; 153 if (exchange.getException() != null) { 154 process(exchange, callback, data); 155 } else { 156 callback.done(sync); 157 } 158 } 159 }); 160 if (!sync) { 161 // It is going to be processed async.. 162 return false; 163 } 164 if (exchange.getException() == null || isFailureHandled(exchange)) { 165 // If everything went well.. then we exit here.. 166 callback.done(true); 167 return true; 168 } 169 // error occurred so loop back around..... 170 } 171 172 } 173 174 public static boolean isFailureHandled(Exchange exchange) { 175 return exchange.getProperty(FAILURE_HANDLED_PROPERTY) != null; 176 } 177 178 public static void setFailureHandled(Exchange exchange, boolean isHandled) { 179 if (isHandled) { 180 exchange.setProperty(FAILURE_HANDLED_PROPERTY, exchange.getException()); 181 exchange.setException(null); 182 } else { 183 exchange.setException(exchange.getProperty(FAILURE_HANDLED_PROPERTY, Throwable.class)); 184 exchange.removeProperty(FAILURE_HANDLED_PROPERTY); 185 } 186 187 } 188 189 public static void restoreExceptionOnExchange(Exchange exchange) { 190 exchange.setException(exchange.getProperty(FAILURE_HANDLED_PROPERTY, Throwable.class)); 191 } 192 193 public void process(Exchange exchange) throws Exception { 194 AsyncProcessorHelper.process(this, exchange); 195 } 196 197 // Properties 198 // ------------------------------------------------------------------------- 199 200 /** 201 * Returns the output processor 202 */ 203 public Processor getOutput() { 204 return output; 205 } 206 207 /** 208 * Returns the dead letter that message exchanges will be sent to if the 209 * redelivery attempts fail 210 */ 211 public Processor getDeadLetter() { 212 return deadLetter; 213 } 214 215 public RedeliveryPolicy getRedeliveryPolicy() { 216 return redeliveryPolicy; 217 } 218 219 /** 220 * Sets the redelivery policy 221 */ 222 public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) { 223 this.redeliveryPolicy = redeliveryPolicy; 224 } 225 226 public Logger getLogger() { 227 return logger; 228 } 229 230 /** 231 * Sets the logger strategy; which {@link Log} to use and which 232 * {@link LoggingLevel} to use 233 */ 234 public void setLogger(Logger logger) { 235 this.logger = logger; 236 } 237 238 // Implementation methods 239 // ------------------------------------------------------------------------- 240 241 /** 242 * Increments the redelivery counter and adds the redelivered flag if the 243 * message has been redelivered 244 */ 245 protected int incrementRedeliveryCounter(Exchange exchange, Throwable e) { 246 Message in = exchange.getIn(); 247 Integer counter = in.getHeader(REDELIVERY_COUNTER, Integer.class); 248 int next = 1; 249 if (counter != null) { 250 next = counter + 1; 251 } 252 in.setHeader(REDELIVERY_COUNTER, next); 253 in.setHeader(REDELIVERED, true); 254 exchange.setException(e); 255 return next; 256 } 257 258 protected void sleep(long redeliveryDelay) { 259 if (redeliveryDelay > 0) { 260 if (LOG.isDebugEnabled()) { 261 LOG.debug("Sleeping for: " + redeliveryDelay + " millis until attempting redelivery"); 262 } 263 try { 264 Thread.sleep(redeliveryDelay); 265 } catch (InterruptedException e) { 266 if (LOG.isDebugEnabled()) { 267 LOG.debug("Thread interrupted: " + e, e); 268 } 269 } 270 } 271 } 272 273 protected void doStart() throws Exception { 274 ServiceHelper.startServices(output, deadLetter); 275 } 276 277 protected void doStop() throws Exception { 278 ServiceHelper.stopServices(deadLetter, output); 279 } 280 281 }