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.AsyncCallback; 020 import org.apache.camel.CamelContext; 021 import org.apache.camel.Exchange; 022 import org.apache.camel.Predicate; 023 import org.apache.camel.Processor; 024 import org.apache.camel.processor.Logger; 025 import org.apache.camel.processor.RedeliveryErrorHandler; 026 import org.apache.camel.processor.RedeliveryPolicy; 027 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy; 028 import org.apache.camel.util.ObjectHelper; 029 import org.springframework.transaction.TransactionDefinition; 030 import org.springframework.transaction.TransactionStatus; 031 import org.springframework.transaction.support.TransactionCallbackWithoutResult; 032 import org.springframework.transaction.support.TransactionTemplate; 033 034 /** 035 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a> 036 * EIP pattern. 037 * 038 * @version $Revision: 1024138 $ 039 */ 040 public class TransactionErrorHandler extends RedeliveryErrorHandler { 041 042 private final TransactionTemplate transactionTemplate; 043 044 /** 045 * Creates the transaction error handler. 046 * 047 * @param camelContext the camel context 048 * @param output outer processor that should use this default error handler 049 * @param logger logger to use for logging failures and redelivery attempts 050 * @param redeliveryProcessor an optional processor to run before redelivery attempt 051 * @param redeliveryPolicy policy for redelivery 052 * @param handledPolicy policy for handling failed exception that are moved to the dead letter queue 053 * @param exceptionPolicyStrategy strategy for onException handling 054 * @param transactionTemplate the transaction template 055 * @param retryWhile retry while 056 */ 057 public TransactionErrorHandler(CamelContext camelContext, Processor output, Logger logger, Processor redeliveryProcessor, 058 RedeliveryPolicy redeliveryPolicy, Predicate handledPolicy, ExceptionPolicyStrategy exceptionPolicyStrategy, 059 TransactionTemplate transactionTemplate, Predicate retryWhile) { 060 super(camelContext, output, logger, redeliveryProcessor, redeliveryPolicy, handledPolicy, null, null, false, retryWhile); 061 setExceptionPolicy(exceptionPolicyStrategy); 062 this.transactionTemplate = transactionTemplate; 063 } 064 065 public boolean supportTransacted() { 066 return true; 067 } 068 069 @Override 070 public String toString() { 071 if (output == null) { 072 // if no output then don't do any description 073 return ""; 074 } 075 return "TransactionErrorHandler:" 076 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior()) 077 + "[" + getOutput() + "]"; 078 } 079 080 @Override 081 public void process(Exchange exchange) throws Exception { 082 // we have to run this synchronously as Spring Transaction does *not* support 083 // using multiple threads to span a transaction 084 if (exchange.getUnitOfWork().isTransactedBy(transactionTemplate)) { 085 // already transacted by this transaction template 086 // so lets just let the error handler process it 087 processByErrorHandler(exchange); 088 } else { 089 // not yet wrapped in transaction so lets do that 090 // and then have it invoke the error handler from within that transaction 091 processInTransaction(exchange); 092 } 093 } 094 095 @Override 096 public boolean process(Exchange exchange, AsyncCallback callback) { 097 // invoke ths synchronous method as Spring Transaction does *not* support 098 // using multiple threads to span a transaction 099 try { 100 process(exchange); 101 } catch (Throwable e) { 102 exchange.setException(e); 103 } 104 105 // notify callback we are done synchronously 106 callback.done(true); 107 return true; 108 } 109 110 protected void processInTransaction(final Exchange exchange) throws Exception { 111 String id = ObjectHelper.getIdentityHashCode(transactionTemplate); 112 try { 113 // mark the beginning of this transaction boundary 114 exchange.getUnitOfWork().beginTransactedBy(transactionTemplate); 115 116 if (log.isDebugEnabled()) { 117 log.debug("Transaction begin (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 118 } 119 120 doInTransactionTemplate(exchange); 121 122 if (log.isDebugEnabled()) { 123 log.debug("Transaction commit (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 124 } 125 } catch (TransactionRollbackException e) { 126 // ignore as its just a dummy exception to force spring TX to rollback 127 if (log.isDebugEnabled()) { 128 log.debug("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 129 } 130 } catch (Exception e) { 131 log.warn("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId() + " due exception: " + e.getMessage()); 132 exchange.setException(e); 133 } finally { 134 // mark the end of this transaction boundary 135 exchange.getUnitOfWork().endTransactedBy(transactionTemplate); 136 } 137 } 138 139 140 protected void doInTransactionTemplate(final Exchange exchange) { 141 142 // spring transaction template is working best with rollback if you throw it a runtime exception 143 // otherwise it may not rollback messages send to JMS queues etc. 144 145 transactionTemplate.execute(new TransactionCallbackWithoutResult() { 146 protected void doInTransactionWithoutResult(TransactionStatus status) { 147 // wrapper exception to throw if the exchange failed 148 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback" 149 RuntimeException rce = null; 150 151 // and now let process the exchange by the error handler 152 processByErrorHandler(exchange); 153 154 // after handling and still an exception or marked as rollback only then rollback 155 if (exchange.getException() != null || exchange.isRollbackOnly()) { 156 157 // if it was a local rollback only then remove its marker so outer transaction 158 // wont rollback as well (Note: isRollbackOnly() also returns true for ROLLBACK_ONLY_LAST) 159 exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST); 160 161 // wrap exception in transacted exception 162 if (exchange.getException() != null) { 163 rce = ObjectHelper.wrapRuntimeCamelException(exchange.getException()); 164 } else if (exchange.isRollbackOnly()) { 165 // create dummy exception to force spring transaction manager to rollback 166 rce = new TransactionRollbackException(); 167 } 168 169 if (!status.isRollbackOnly()) { 170 status.setRollbackOnly(); 171 } 172 173 // rethrow if an exception occurred 174 if (rce != null) { 175 throw rce; 176 } 177 } 178 } 179 }); 180 } 181 182 /** 183 * Processes the {@link Exchange} using the error handler. 184 * <p/> 185 * This implementation will invoke ensure this occurs synchronously, that means if the async routing engine 186 * did kick in, then this implementation will wait for the task to complete before it continues. 187 * 188 * @param exchange the exchange 189 */ 190 protected void processByErrorHandler(final Exchange exchange) { 191 // must invoke the async method with empty callback to have it invoke the 192 // super.processErrorHandler 193 // we are transacted so we have to route synchronously so don't worry about returned 194 // value from the process method 195 // and the camel routing engine will detect this is an transacted Exchange and route 196 // it fully synchronously so we don't have to wait here if we hit an async endpoint 197 // all that is taken care of in the camel-core 198 super.process(exchange, new AsyncCallback() { 199 public void done(boolean doneSync) { 200 // noop 201 } 202 }); 203 } 204 205 private static String propagationBehaviorToString(int propagationBehavior) { 206 String rc; 207 switch (propagationBehavior) { 208 case TransactionDefinition.PROPAGATION_MANDATORY: 209 rc = "PROPAGATION_MANDATORY"; 210 break; 211 case TransactionDefinition.PROPAGATION_NESTED: 212 rc = "PROPAGATION_NESTED"; 213 break; 214 case TransactionDefinition.PROPAGATION_NEVER: 215 rc = "PROPAGATION_NEVER"; 216 break; 217 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 218 rc = "PROPAGATION_NOT_SUPPORTED"; 219 break; 220 case TransactionDefinition.PROPAGATION_REQUIRED: 221 rc = "PROPAGATION_REQUIRED"; 222 break; 223 case TransactionDefinition.PROPAGATION_REQUIRES_NEW: 224 rc = "PROPAGATION_REQUIRES_NEW"; 225 break; 226 case TransactionDefinition.PROPAGATION_SUPPORTS: 227 rc = "PROPAGATION_SUPPORTS"; 228 break; 229 default: 230 rc = "UNKNOWN"; 231 } 232 return rc; 233 } 234 235 }