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.RuntimeCamelException; 023 import org.apache.camel.processor.Logger; 024 import org.apache.camel.processor.RedeliveryErrorHandler; 025 import org.apache.camel.processor.RedeliveryPolicy; 026 import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy; 027 import org.apache.camel.util.ObjectHelper; 028 import org.springframework.transaction.TransactionDefinition; 029 import org.springframework.transaction.TransactionStatus; 030 import org.springframework.transaction.support.TransactionCallbackWithoutResult; 031 import org.springframework.transaction.support.TransactionTemplate; 032 033 /** 034 * The <a href="http://camel.apache.org/transactional-client.html">Transactional Client</a> 035 * EIP pattern. 036 * 037 * @version $Revision: 886781 $ 038 */ 039 public class TransactionErrorHandler extends RedeliveryErrorHandler { 040 041 private final TransactionTemplate transactionTemplate; 042 043 /** 044 * Creates the transaction error handler. 045 * 046 * @param output outer processor that should use this default error handler 047 * @param logger logger to use for logging failures and redelivery attempts 048 * @param redeliveryProcessor an optional processor to run before redelivery attempt 049 * @param redeliveryPolicy policy for redelivery 050 * @param handledPolicy policy for handling failed exception that are moved to the dead letter queue 051 * @param exceptionPolicyStrategy strategy for onException handling 052 * @param transactionTemplate the transaction template 053 */ 054 public TransactionErrorHandler(Processor output, Logger logger, Processor redeliveryProcessor, 055 RedeliveryPolicy redeliveryPolicy, Predicate handledPolicy, 056 ExceptionPolicyStrategy exceptionPolicyStrategy, TransactionTemplate transactionTemplate) { 057 super(output, logger, redeliveryProcessor, redeliveryPolicy, handledPolicy, null, null, false); 058 setExceptionPolicy(exceptionPolicyStrategy); 059 this.transactionTemplate = transactionTemplate; 060 } 061 062 public boolean supportTransacted() { 063 return true; 064 } 065 066 @Override 067 public String toString() { 068 if (output == null) { 069 // if no output then don't do any description 070 return ""; 071 } 072 return "TransactionErrorHandler:" 073 + propagationBehaviorToString(transactionTemplate.getPropagationBehavior()) 074 + "[" + getOutput() + "]"; 075 } 076 077 public void process(final Exchange exchange) throws Exception { 078 if (exchange.getUnitOfWork().isTransactedBy(transactionTemplate)) { 079 // already transacted by this transaction template 080 // so lets just let the regular default error handler process it 081 processByRegularErrorHandler(exchange); 082 } else { 083 // not yet wrapped in transaction so lets do that 084 processInTransaction(exchange); 085 } 086 } 087 088 protected void processByRegularErrorHandler(Exchange exchange) { 089 try { 090 super.process(exchange); 091 } catch (Exception e) { 092 exchange.setException(e); 093 } 094 } 095 096 protected void processInTransaction(final Exchange exchange) throws Exception { 097 String id = ObjectHelper.getIdentityHashCode(transactionTemplate); 098 try { 099 // mark the beginning of this transaction boundary 100 exchange.getUnitOfWork().beginTransactedBy(transactionTemplate); 101 102 if (log.isDebugEnabled()) { 103 log.debug("Transaction begin (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 104 } 105 106 doInTransactionTemplate(exchange); 107 108 if (log.isDebugEnabled()) { 109 log.debug("Transaction commit (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 110 } 111 } catch (TransactionRollbackException e) { 112 // ignore as its just a dummy exception to force spring TX to rollback 113 if (log.isDebugEnabled()) { 114 log.debug("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId()); 115 } 116 } catch (Exception e) { 117 log.warn("Transaction rollback (" + id + ") for ExchangeId: " + exchange.getExchangeId() + " due exception: " + e.getMessage()); 118 exchange.setException(e); 119 } finally { 120 // mark the end of this transaction boundary 121 exchange.getUnitOfWork().endTransactedBy(transactionTemplate); 122 } 123 } 124 125 protected void doInTransactionTemplate(final Exchange exchange) { 126 127 // spring transaction template is working best with rollback if you throw it a runtime exception 128 // otherwise it may not rollback messages send to JMS queues etc. 129 130 transactionTemplate.execute(new TransactionCallbackWithoutResult() { 131 protected void doInTransactionWithoutResult(TransactionStatus status) { 132 // wrapper exception to throw if the exchange failed 133 // IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback" 134 RuntimeCamelException rce = null; 135 136 exchange.setProperty(Exchange.TRANSACTED, Boolean.TRUE); 137 138 // and now let process the exchange 139 try { 140 TransactionErrorHandler.super.process(exchange); 141 } catch (Exception e) { 142 exchange.setException(e); 143 } 144 145 // after handling and still an exception or marked as rollback only then rollback 146 if (exchange.getException() != null || exchange.isRollbackOnly()) { 147 148 // if it was a local rollback only then remove its marker so outer transaction 149 // wont rollback as well (Note: isRollbackOnly() also returns true for ROLLBACK_ONLY_LAST) 150 exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST); 151 152 // wrap exception in transacted exception 153 if (exchange.getException() != null) { 154 rce = ObjectHelper.wrapRuntimeCamelException(exchange.getException()); 155 } 156 157 if (!status.isRollbackOnly()) { 158 status.setRollbackOnly(); 159 } 160 161 // rethrow if an exception occurred 162 if (rce != null) { 163 throw rce; 164 } else { 165 // create dummy exception to force spring transaction manager to rollback 166 throw new TransactionRollbackException(); 167 } 168 } 169 } 170 }); 171 } 172 173 protected String propagationBehaviorToString(int propagationBehavior) { 174 String rc; 175 switch (propagationBehavior) { 176 case TransactionDefinition.PROPAGATION_MANDATORY: 177 rc = "PROPAGATION_MANDATORY"; 178 break; 179 case TransactionDefinition.PROPAGATION_NESTED: 180 rc = "PROPAGATION_NESTED"; 181 break; 182 case TransactionDefinition.PROPAGATION_NEVER: 183 rc = "PROPAGATION_NEVER"; 184 break; 185 case TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 186 rc = "PROPAGATION_NOT_SUPPORTED"; 187 break; 188 case TransactionDefinition.PROPAGATION_REQUIRED: 189 rc = "PROPAGATION_REQUIRED"; 190 break; 191 case TransactionDefinition.PROPAGATION_REQUIRES_NEW: 192 rc = "PROPAGATION_REQUIRES_NEW"; 193 break; 194 case TransactionDefinition.PROPAGATION_SUPPORTS: 195 rc = "PROPAGATION_SUPPORTS"; 196 break; 197 default: 198 rc = "UNKNOWN"; 199 } 200 return rc; 201 } 202 203 }