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