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.spring.spi; 018 019 import org.apache.camel.Exchange; 020 import org.apache.camel.Predicate; 021 import org.apache.camel.Processor; 022 import org.apache.camel.model.OnExceptionDefinition; 023 import org.apache.camel.processor.ErrorHandlerSupport; 024 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy; 025 import org.apache.camel.util.MessageHelper; 026 import org.apache.camel.util.ServiceHelper; 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 import org.springframework.transaction.TransactionDefinition; 030 import org.springframework.transaction.TransactionStatus; 031 import org.springframework.transaction.support.DefaultTransactionStatus; 032 import org.springframework.transaction.support.TransactionCallbackWithoutResult; 033 import org.springframework.transaction.support.TransactionSynchronizationManager; 034 import org.springframework.transaction.support.TransactionTemplate; 035 036 /** 037 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a> 038 * EIP pattern. 039 * 040 * @version $Revision: 765920 $ 041 */ 042 public class TransactionErrorHandler extends ErrorHandlerSupport { 043 044 private static final transient Log LOG = LogFactory.getLog(TransactionErrorHandler.class); 045 private final TransactionTemplate transactionTemplate; 046 private Processor output; 047 048 public TransactionErrorHandler(TransactionTemplate transactionTemplate) { 049 this.transactionTemplate = transactionTemplate; 050 } 051 052 public TransactionErrorHandler(TransactionTemplate transactionTemplate, Processor output, 053 ExceptionPolicyStrategy exceptionPolicy) { 054 this.transactionTemplate = transactionTemplate; 055 setOutput(output); 056 setExceptionPolicy(exceptionPolicy); 057 } 058 059 public boolean supportTransacted() { 060 return true; 061 } 062 063 @Override 064 public String toString() { 065 if (output == null) { 066 // if no output then dont do any description 067 return ""; 068 } 069 return "TransactionErrorHandler:" 070 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior()) 071 + "[" + getOutput() + "]"; 072 } 073 074 public void process(final Exchange exchange) { 075 if (output == null) { 076 // no output then just return as nothing to wrap in a transaction 077 return; 078 } 079 080 transactionTemplate.execute(new TransactionCallbackWithoutResult() { 081 protected void doInTransactionWithoutResult(TransactionStatus status) { 082 083 // wrapper exception to throw if the exchange failed 084 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback" 085 TransactedRuntimeCamelException rce; 086 087 // find out if there is an actual transaction alive, and thus we are in transacted mode 088 boolean activeTx = TransactionSynchronizationManager.isActualTransactionActive(); 089 if (!activeTx) { 090 activeTx = status.isNewTransaction() && !status.isCompleted(); 091 if (!activeTx) { 092 if (DefaultTransactionStatus.class.isAssignableFrom(status.getClass())) { 093 DefaultTransactionStatus defStatus = DefaultTransactionStatus.class.cast(status); 094 activeTx = defStatus.hasTransaction() && !status.isCompleted(); 095 } 096 } 097 } 098 if (LOG.isTraceEnabled()) { 099 LOG.trace("Is actual transaction active: " + activeTx); 100 } 101 102 // okay mark the exchange as transacted, then the DeadLetterChannel or others know 103 // its a transacted exchange 104 if (activeTx) { 105 exchange.setProperty(Exchange.TRANSACTED, Boolean.TRUE); 106 } 107 108 try { 109 // process the exchange 110 output.process(exchange); 111 } catch (Exception e) { 112 exchange.setException(e); 113 } 114 115 // an exception occured maybe an onException can handle it 116 if (exchange.getException() != null) { 117 // handle onException 118 // but test beforehand if we have already handled it, if so we should not do it again 119 boolean handled = false; 120 if (exchange.getException() instanceof TransactedRuntimeCamelException) { 121 TransactedRuntimeCamelException trce = exchange.getException(TransactedRuntimeCamelException.class); 122 handled = trce.isHandled(); 123 } 124 if (!handled) { 125 // not handled before so handle it once 126 handleException(exchange); 127 } 128 } 129 130 // after handling and still an exception or marked as rollback only then rollback 131 if (exchange.getException() != null || exchange.isRollbackOnly()) { 132 rce = wrapTransactedRuntimeException(exchange.getException()); 133 134 if (activeTx) { 135 status.setRollbackOnly(); 136 if (LOG.isDebugEnabled()) { 137 if (rce != null) { 138 LOG.debug("Setting transaction to rollbackOnly due to exception being thrown: " + rce.getMessage()); 139 } else { 140 LOG.debug("Setting transaction to rollbackOnly as Exchange was marked as rollback only"); 141 } 142 } 143 } 144 145 // rethrow if an exception occured 146 if (rce != null) { 147 throw rce; 148 } 149 } 150 } 151 }); 152 } 153 154 protected TransactedRuntimeCamelException wrapTransactedRuntimeException(Exception exception) { 155 if (exception instanceof TransactedRuntimeCamelException) { 156 return (TransactedRuntimeCamelException) exception; 157 } else { 158 // Mark as handled so we dont want to handle the same exception twice or more in other 159 // wrapped transaction error handlers in this route. 160 // We need to mark this information in the exception as we need to propagage 161 // the exception back by rehtrowing it. We cannot mark it on the exchange as Camel 162 // uses copies of exchanges in its pipeline and the data isnt copied back in case 163 // when an exception occured 164 return new TransactedRuntimeCamelException(exception, true); 165 } 166 } 167 168 /** 169 * Handles when an exception occured during processing. Is used to let the exception policy 170 * deal with it, eg letting an onException handle it. 171 * 172 * @param exchange the current exchange 173 */ 174 protected void handleException(Exchange exchange) { 175 Exception e = exchange.getException(); 176 // store the original caused exception in a property, so we can restore it later 177 exchange.setProperty(Exchange.EXCEPTION_CAUGHT, e); 178 179 // find the error handler to use (if any) 180 OnExceptionDefinition exceptionPolicy = getExceptionPolicy(exchange, e); 181 if (exceptionPolicy != null) { 182 Predicate handledPredicate = exceptionPolicy.getHandledPolicy(); 183 184 Processor processor = exceptionPolicy.getErrorHandler(); 185 prepareExchangeBeforeOnException(exchange); 186 if (processor != null) { 187 deliverToFaultProcessor(exchange, processor); 188 } 189 prepareExchangeAfterOnException(exchange, handledPredicate); 190 } 191 } 192 193 private void deliverToFaultProcessor(Exchange exchange, Processor faultProcessor) { 194 try { 195 faultProcessor.process(exchange); 196 } catch (Exception e) { 197 // fault processor also failed so set the exception 198 exchange.setException(e); 199 } 200 } 201 202 private void prepareExchangeBeforeOnException(Exchange exchange) { 203 // okay lower the exception as we are handling it by onException 204 if (exchange.getException() != null) { 205 exchange.setException(null); 206 } 207 208 // clear rollback flags 209 exchange.setProperty(Exchange.ROLLBACK_ONLY, null); 210 211 // reset cached streams so they can be read again 212 MessageHelper.resetStreamCache(exchange.getIn()); 213 } 214 215 private void prepareExchangeAfterOnException(Exchange exchange, Predicate handledPredicate) { 216 if (handledPredicate == null || !handledPredicate.matches(exchange)) { 217 if (LOG.isDebugEnabled()) { 218 LOG.debug("This exchange is not handled so its marked as rollback only: " + exchange); 219 } 220 // exception not handled, put exception back in the exchange 221 exchange.setException(exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class)); 222 // mark as rollback so we dont do multiple onException for this one 223 exchange.setProperty(Exchange.ROLLBACK_ONLY, Boolean.TRUE); 224 } else { 225 if (LOG.isDebugEnabled()) { 226 LOG.debug("This exchange is handled so its marked as not failed: " + exchange); 227 } 228 exchange.setProperty(Exchange.EXCEPTION_HANDLED, Boolean.TRUE); 229 } 230 } 231 232 protected String propagationBehaviorToString(int propagationBehavior) { 233 String rc; 234 switch (propagationBehavior) { 235 case TransactionDefinition.PROPAGATION_MANDATORY: 236 rc = "PROPAGATION_MANDATORY"; 237 break; 238 case TransactionDefinition.PROPAGATION_NESTED: 239 rc = "PROPAGATION_NESTED"; 240 break; 241 case TransactionDefinition.PROPAGATION_NEVER: 242 rc = "PROPAGATION_NEVER"; 243 break; 244 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 245 rc = "PROPAGATION_NOT_SUPPORTED"; 246 break; 247 case TransactionDefinition.PROPAGATION_REQUIRED: 248 rc = "PROPAGATION_REQUIRED"; 249 break; 250 case TransactionDefinition.PROPAGATION_REQUIRES_NEW: 251 rc = "PROPAGATION_REQUIRES_NEW"; 252 break; 253 case TransactionDefinition.PROPAGATION_SUPPORTS: 254 rc = "PROPAGATION_SUPPORTS"; 255 break; 256 default: 257 rc = "UNKNOWN"; 258 } 259 return rc; 260 } 261 262 protected void doStart() throws Exception { 263 ServiceHelper.startServices(output); 264 } 265 266 protected void doStop() throws Exception { 267 ServiceHelper.stopServices(output); 268 } 269 270 /** 271 * Returns the output processor 272 */ 273 public Processor getOutput() { 274 return output; 275 } 276 277 public void setOutput(Processor output) { 278 this.output = output; 279 } 280 281 }