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