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 */ 017package org.apache.camel.spring.spi; 018 019import java.util.concurrent.ScheduledExecutorService; 020 021import org.apache.camel.AsyncCallback; 022import org.apache.camel.CamelContext; 023import org.apache.camel.Exchange; 024import org.apache.camel.LoggingLevel; 025import org.apache.camel.Predicate; 026import org.apache.camel.Processor; 027import org.apache.camel.RuntimeCamelException; 028import org.apache.camel.processor.errorhandler.ExceptionPolicyStrategy; 029import org.apache.camel.processor.errorhandler.RedeliveryErrorHandler; 030import org.apache.camel.processor.errorhandler.RedeliveryPolicy; 031import org.apache.camel.spi.CamelLogger; 032import org.apache.camel.support.AsyncProcessorSupport; 033import org.apache.camel.support.ExchangeHelper; 034import org.apache.camel.util.ObjectHelper; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037import org.springframework.transaction.TransactionDefinition; 038import org.springframework.transaction.TransactionStatus; 039import org.springframework.transaction.support.TransactionCallbackWithoutResult; 040import org.springframework.transaction.support.TransactionTemplate; 041 042/** 043 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a> 044 * EIP pattern. 045 */ 046public class TransactionErrorHandler extends RedeliveryErrorHandler { 047 048 private static final Logger LOG = LoggerFactory.getLogger(TransactionErrorHandler.class); 049 050 private final TransactionTemplate transactionTemplate; 051 private final String transactionKey; 052 private final LoggingLevel rollbackLoggingLevel; 053 054 /** 055 * Creates the transaction error handler. 056 * 057 * @param camelContext the camel context 058 * @param output outer processor that should use this default error handler 059 * @param logger logger to use for logging failures and redelivery attempts 060 * @param redeliveryProcessor an optional processor to run before redelivery attempt 061 * @param redeliveryPolicy policy for redelivery 062 * @param exceptionPolicyStrategy strategy for onException handling 063 * @param transactionTemplate the transaction template 064 * @param retryWhile retry while 065 * @param executorService the {@link java.util.concurrent.ScheduledExecutorService} to be used for redelivery thread pool. Can be <tt>null</tt>. 066 * @param rollbackLoggingLevel logging level to use for logging transaction rollback occurred 067 * @param onExceptionOccurredProcessor a custom {@link org.apache.camel.Processor} to process the {@link org.apache.camel.Exchange} just after an exception was thrown. 068 */ 069 public TransactionErrorHandler(CamelContext camelContext, Processor output, CamelLogger logger, 070 Processor redeliveryProcessor, RedeliveryPolicy redeliveryPolicy, ExceptionPolicyStrategy exceptionPolicyStrategy, 071 TransactionTemplate transactionTemplate, Predicate retryWhile, ScheduledExecutorService executorService, 072 LoggingLevel rollbackLoggingLevel, Processor onExceptionOccurredProcessor) { 073 074 super(camelContext, output, logger, redeliveryProcessor, redeliveryPolicy, null, null, false, false, false, retryWhile, 075 executorService, null, onExceptionOccurredProcessor); 076 setExceptionPolicy(exceptionPolicyStrategy); 077 this.transactionTemplate = transactionTemplate; 078 this.rollbackLoggingLevel = rollbackLoggingLevel; 079 this.transactionKey = ObjectHelper.getIdentityHashCode(transactionTemplate); 080 } 081 082 @Override 083 public boolean supportTransacted() { 084 return true; 085 } 086 087 @Override 088 public String toString() { 089 if (output == null) { 090 // if no output then don't do any description 091 return ""; 092 } 093 return "TransactionErrorHandler:" 094 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior()) 095 + "[" + getOutput() + "]"; 096 } 097 098 @Override 099 public void process(Exchange exchange) { 100 // we have to run this synchronously as Spring Transaction does *not* support 101 // using multiple threads to span a transaction 102 if (transactionTemplate.getPropagationBehavior() != TransactionDefinition.PROPAGATION_REQUIRES_NEW 103 && exchange.getUnitOfWork() != null 104 && exchange.getUnitOfWork().isTransactedBy(transactionKey)) { 105 // already transacted by this transaction template 106 // so lets just let the error handler process it 107 processByErrorHandler(exchange); 108 } else { 109 // not yet wrapped in transaction so lets do that 110 // and then have it invoke the error handler from within that transaction 111 processInTransaction(exchange); 112 } 113 } 114 115 @Override 116 public boolean process(Exchange exchange, AsyncCallback callback) { 117 // invoke ths synchronous method as Spring Transaction does *not* support 118 // using multiple threads to span a transaction 119 try { 120 process(exchange); 121 } catch (Throwable e) { 122 exchange.setException(e); 123 } 124 125 // notify callback we are done synchronously 126 callback.done(true); 127 return true; 128 } 129 130 protected void processInTransaction(final Exchange exchange) { 131 // is the exchange redelivered, for example JMS brokers support such details 132 final String redelivered = Boolean.toString(exchange.isExternalRedelivered()); 133 final String ids = ExchangeHelper.logIds(exchange); 134 135 try { 136 // mark the beginning of this transaction boundary 137 if (exchange.getUnitOfWork() != null) { 138 exchange.getUnitOfWork().beginTransactedBy(transactionKey); 139 } 140 141 // do in transaction 142 logTransactionBegin(redelivered, ids); 143 doInTransactionTemplate(exchange); 144 logTransactionCommit(redelivered, ids); 145 146 } catch (TransactionRollbackException e) { 147 // do not set as exception, as its just a dummy exception to force spring TX to rollback 148 logTransactionRollback(redelivered, ids, null, true); 149 } catch (Throwable e) { 150 exchange.setException(e); 151 logTransactionRollback(redelivered, ids, e, false); 152 } finally { 153 // mark the end of this transaction boundary 154 if (exchange.getUnitOfWork() != null) { 155 exchange.getUnitOfWork().endTransactedBy(transactionKey); 156 } 157 } 158 159 // if it was a local rollback only then remove its marker so outer transaction wont see the marker 160 boolean onlyLast = exchange.isRollbackOnlyLast(); 161 exchange.setRollbackOnlyLast(false); 162 if (onlyLast) { 163 // we only want this logged at debug level 164 if (LOG.isDebugEnabled()) { 165 // log exception if there was a cause exception so we have the stack trace 166 Exception cause = exchange.getException(); 167 if (cause != null) { 168 LOG.debug("Transaction rollback (" + transactionKey + ") redelivered(" + redelivered + ") for " 169 + ids + " due exchange was marked for rollbackOnlyLast and caught: ", cause); 170 } else { 171 LOG.debug("Transaction rollback ({}) redelivered({}) for {} " 172 + "due exchange was marked for rollbackOnlyLast", new Object[]{transactionKey, redelivered, ids}); 173 } 174 } 175 // remove caused exception due we was marked as rollback only last 176 // so by removing the exception, any outer transaction will not be affected 177 exchange.setException(null); 178 } 179 } 180 181 protected void doInTransactionTemplate(final Exchange exchange) { 182 183 // spring transaction template is working best with rollback if you throw it a runtime exception 184 // otherwise it may not rollback messages send to JMS queues etc. 185 186 transactionTemplate.execute(new TransactionCallbackWithoutResult() { 187 protected void doInTransactionWithoutResult(TransactionStatus status) { 188 // wrapper exception to throw if the exchange failed 189 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback" 190 RuntimeException rce; 191 192 // and now let process the exchange by the error handler 193 processByErrorHandler(exchange); 194 195 // after handling and still an exception or marked as rollback only then rollback 196 if (exchange.getException() != null || exchange.isRollbackOnly()) { 197 198 // wrap exception in transacted exception 199 if (exchange.getException() != null) { 200 rce = RuntimeCamelException.wrapRuntimeCamelException(exchange.getException()); 201 } else { 202 // create dummy exception to force spring transaction manager to rollback 203 rce = new TransactionRollbackException(); 204 } 205 206 if (!status.isRollbackOnly()) { 207 status.setRollbackOnly(); 208 } 209 210 // throw runtime exception to force rollback (which works best to rollback with Spring transaction manager) 211 if (LOG.isTraceEnabled()) { 212 LOG.trace("Throwing runtime exception to force transaction to rollback on {}", transactionTemplate.getName()); 213 } 214 throw rce; 215 } 216 } 217 }); 218 } 219 220 /** 221 * Processes the {@link Exchange} using the error handler. 222 * <p/> 223 * This implementation will invoke ensure this occurs synchronously, that means if the async routing engine 224 * did kick in, then this implementation will wait for the task to complete before it continues. 225 * 226 * @param exchange the exchange 227 */ 228 protected void processByErrorHandler(final Exchange exchange) { 229 awaitManager.process(new AsyncProcessorSupport() { 230 @Override 231 public boolean process(Exchange exchange, AsyncCallback callback) { 232 return TransactionErrorHandler.super.process(exchange, callback); 233 } 234 }, exchange); 235 } 236 237 /** 238 * Logs the transaction begin 239 */ 240 private void logTransactionBegin(String redelivered, String ids) { 241 if (LOG.isDebugEnabled()) { 242 LOG.debug("Transaction begin ({}) redelivered({}) for {})", transactionKey, redelivered, ids); 243 } 244 } 245 246 /** 247 * Logs the transaction commit 248 */ 249 private void logTransactionCommit(String redelivered, String ids) { 250 if ("true".equals(redelivered)) { 251 // okay its a redelivered message so log at INFO level if rollbackLoggingLevel is INFO or higher 252 // this allows people to know that the redelivered message was committed this time 253 if (rollbackLoggingLevel == LoggingLevel.INFO || rollbackLoggingLevel == LoggingLevel.WARN || rollbackLoggingLevel == LoggingLevel.ERROR) { 254 LOG.info("Transaction commit ({}) redelivered({}) for {})", transactionKey, redelivered, ids); 255 // return after we have logged 256 return; 257 } 258 } 259 260 // log non redelivered by default at DEBUG level 261 LOG.debug("Transaction commit ({}) redelivered({}) for {})", transactionKey, redelivered, ids); 262 } 263 264 /** 265 * Logs the transaction rollback. 266 */ 267 private void logTransactionRollback(String redelivered, String ids, Throwable e, boolean rollbackOnly) { 268 if (rollbackLoggingLevel == LoggingLevel.OFF) { 269 return; 270 } else if (rollbackLoggingLevel == LoggingLevel.ERROR && LOG.isErrorEnabled()) { 271 if (rollbackOnly) { 272 LOG.error("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", transactionKey, redelivered, ids); 273 } else { 274 LOG.error("Transaction rollback ({}) redelivered({}) for {} caught: {}", transactionKey, redelivered, ids, e.getMessage()); 275 } 276 } else if (rollbackLoggingLevel == LoggingLevel.WARN && LOG.isWarnEnabled()) { 277 if (rollbackOnly) { 278 LOG.warn("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", transactionKey, redelivered, ids); 279 } else { 280 LOG.warn("Transaction rollback ({}) redelivered({}) for {} caught: {}", transactionKey, redelivered, ids, e.getMessage()); 281 } 282 } else if (rollbackLoggingLevel == LoggingLevel.INFO && LOG.isInfoEnabled()) { 283 if (rollbackOnly) { 284 LOG.info("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", transactionKey, redelivered, ids); 285 } else { 286 LOG.info("Transaction rollback ({}) redelivered({}) for {} caught: {}", transactionKey, redelivered, ids, e.getMessage()); 287 } 288 } else if (rollbackLoggingLevel == LoggingLevel.DEBUG && LOG.isDebugEnabled()) { 289 if (rollbackOnly) { 290 LOG.debug("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", transactionKey, redelivered, ids); 291 } else { 292 LOG.debug("Transaction rollback ({}) redelivered({}) for {} caught: {}", transactionKey, redelivered, ids, e.getMessage()); 293 } 294 } else if (rollbackLoggingLevel == LoggingLevel.TRACE && LOG.isTraceEnabled()) { 295 if (rollbackOnly) { 296 LOG.trace("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", transactionKey, redelivered, ids); 297 } else { 298 LOG.trace("Transaction rollback ({}) redelivered({}) for {} caught: {}", transactionKey, redelivered, ids, e.getMessage()); 299 } 300 } 301 } 302 303 private static String propagationBehaviorToString(int propagationBehavior) { 304 String rc; 305 switch (propagationBehavior) { 306 case TransactionDefinition.PROPAGATION_MANDATORY: 307 rc = "PROPAGATION_MANDATORY"; 308 break; 309 case TransactionDefinition.PROPAGATION_NESTED: 310 rc = "PROPAGATION_NESTED"; 311 break; 312 case TransactionDefinition.PROPAGATION_NEVER: 313 rc = "PROPAGATION_NEVER"; 314 break; 315 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 316 rc = "PROPAGATION_NOT_SUPPORTED"; 317 break; 318 case TransactionDefinition.PROPAGATION_REQUIRED: 319 rc = "PROPAGATION_REQUIRED"; 320 break; 321 case TransactionDefinition.PROPAGATION_REQUIRES_NEW: 322 rc = "PROPAGATION_REQUIRES_NEW"; 323 break; 324 case TransactionDefinition.PROPAGATION_SUPPORTS: 325 rc = "PROPAGATION_SUPPORTS"; 326 break; 327 default: 328 rc = "UNKNOWN"; 329 } 330 return rc; 331 } 332 333}