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