org.apache.camel.spring.spi.TransactionErrorHandler Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.spring.spi;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.processor.RedeliveryErrorHandler;
import org.apache.camel.processor.RedeliveryPolicy;
import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy;
import org.apache.camel.util.CamelLogger;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.ObjectHelper;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* The Transactional Client
* EIP pattern.
*
* @version
*/
public class TransactionErrorHandler extends RedeliveryErrorHandler {
private final TransactionTemplate transactionTemplate;
private final String transactionKey;
private final LoggingLevel rollbackLoggingLevel;
/**
* Creates the transaction error handler.
*
* @param camelContext the camel context
* @param output outer processor that should use this default error handler
* @param logger logger to use for logging failures and redelivery attempts
* @param redeliveryProcessor an optional processor to run before redelivery attempt
* @param redeliveryPolicy policy for redelivery
* @param exceptionPolicyStrategy strategy for onException handling
* @param transactionTemplate the transaction template
* @param retryWhile retry while
* @param executorService the {@link java.util.concurrent.ScheduledExecutorService} to be used for redelivery thread pool. Can be null.
* @param rollbackLoggingLevel logging level to use for logging transaction rollback occurred
*/
public TransactionErrorHandler(CamelContext camelContext, Processor output, CamelLogger logger,
Processor redeliveryProcessor, RedeliveryPolicy redeliveryPolicy, ExceptionPolicyStrategy exceptionPolicyStrategy,
TransactionTemplate transactionTemplate, Predicate retryWhile, ScheduledExecutorService executorService,
LoggingLevel rollbackLoggingLevel) {
super(camelContext, output, logger, redeliveryProcessor, redeliveryPolicy, null, null, false, false, retryWhile, executorService);
setExceptionPolicy(exceptionPolicyStrategy);
this.transactionTemplate = transactionTemplate;
this.rollbackLoggingLevel = rollbackLoggingLevel;
this.transactionKey = ObjectHelper.getIdentityHashCode(transactionTemplate);
}
public boolean supportTransacted() {
return true;
}
@Override
public String toString() {
if (output == null) {
// if no output then don't do any description
return "";
}
return "TransactionErrorHandler:"
+ propagationBehaviorToString(transactionTemplate.getPropagationBehavior())
+ "[" + getOutput() + "]";
}
@Override
public void process(Exchange exchange) throws Exception {
// we have to run this synchronously as Spring Transaction does *not* support
// using multiple threads to span a transaction
if (transactionTemplate.getPropagationBehavior() != TransactionDefinition.PROPAGATION_REQUIRES_NEW && exchange.getUnitOfWork().isTransactedBy(transactionKey)) {
// already transacted by this transaction template
// so lets just let the error handler process it
processByErrorHandler(exchange);
} else {
// not yet wrapped in transaction so lets do that
// and then have it invoke the error handler from within that transaction
processInTransaction(exchange);
}
}
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
// invoke ths synchronous method as Spring Transaction does *not* support
// using multiple threads to span a transaction
try {
process(exchange);
} catch (Throwable e) {
exchange.setException(e);
}
// notify callback we are done synchronously
callback.done(true);
return true;
}
protected void processInTransaction(final Exchange exchange) throws Exception {
// is the exchange redelivered, for example JMS brokers support such details
Boolean externalRedelivered = exchange.isExternalRedelivered();
final String redelivered = externalRedelivered != null ? externalRedelivered.toString() : "unknown";
final String ids = ExchangeHelper.logIds(exchange);
try {
// mark the beginning of this transaction boundary
exchange.getUnitOfWork().beginTransactedBy(transactionKey);
// do in transaction
logTransactionBegin(redelivered, ids);
doInTransactionTemplate(exchange);
logTransactionCommit(redelivered, ids);
} catch (TransactionRollbackException e) {
// do not set as exception, as its just a dummy exception to force spring TX to rollback
logTransactionRollback(redelivered, ids, null, true);
} catch (Throwable e) {
exchange.setException(e);
logTransactionRollback(redelivered, ids, e, false);
} finally {
// mark the end of this transaction boundary
exchange.getUnitOfWork().endTransactedBy(transactionKey);
}
// if it was a local rollback only then remove its marker so outer transaction wont see the marker
Boolean onlyLast = (Boolean) exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST);
if (onlyLast != null && onlyLast) {
// we only want this logged at debug level
if (log.isDebugEnabled()) {
// log exception if there was a cause exception so we have the stack trace
Exception cause = exchange.getException();
if (cause != null) {
log.debug("Transaction rollback (" + transactionKey + ") redelivered(" + redelivered + ") for "
+ ids + " due exchange was marked for rollbackOnlyLast and caught: ", cause);
} else {
log.debug("Transaction rollback ({}) redelivered({}) for {} "
+ "due exchange was marked for rollbackOnlyLast", new Object[]{transactionKey, redelivered, ids});
}
}
// remove caused exception due we was marked as rollback only last
// so by removing the exception, any outer transaction will not be affected
exchange.setException(null);
}
}
protected void doInTransactionTemplate(final Exchange exchange) {
// spring transaction template is working best with rollback if you throw it a runtime exception
// otherwise it may not rollback messages send to JMS queues etc.
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
// wrapper exception to throw if the exchange failed
// IMPORTANT: Must be a runtime exception to let Spring regard it as to do "rollback"
RuntimeException rce;
// and now let process the exchange by the error handler
processByErrorHandler(exchange);
// after handling and still an exception or marked as rollback only then rollback
if (exchange.getException() != null || exchange.isRollbackOnly()) {
// wrap exception in transacted exception
if (exchange.getException() != null) {
rce = ObjectHelper.wrapRuntimeCamelException(exchange.getException());
} else {
// create dummy exception to force spring transaction manager to rollback
rce = new TransactionRollbackException();
}
if (!status.isRollbackOnly()) {
status.setRollbackOnly();
}
// throw runtime exception to force rollback (which works best to rollback with Spring transaction manager)
if (log.isTraceEnabled()) {
log.trace("Throwing runtime exception to force transaction to rollback on {}", transactionTemplate.getName());
}
throw rce;
}
}
});
}
/**
* Processes the {@link Exchange} using the error handler.
*
* This implementation will invoke ensure this occurs synchronously, that means if the async routing engine
* did kick in, then this implementation will wait for the task to complete before it continues.
*
* @param exchange the exchange
*/
protected void processByErrorHandler(final Exchange exchange) {
final CountDownLatch latch = new CountDownLatch(1);
boolean sync = super.process(exchange, new AsyncCallback() {
public void done(boolean doneSync) {
if (!doneSync) {
log.trace("Asynchronous callback received for exchangeId: {}", exchange.getExchangeId());
latch.countDown();
}
}
@Override
public String toString() {
return "Done " + TransactionErrorHandler.this.toString();
}
});
if (!sync) {
log.trace("Waiting for asynchronous callback before continuing for exchangeId: {} -> {}",
exchange.getExchangeId(), exchange);
try {
latch.await();
} catch (InterruptedException e) {
exchange.setException(e);
}
log.trace("Asynchronous callback received, will continue routing exchangeId: {} -> {}",
exchange.getExchangeId(), exchange);
}
}
/**
* Logs the transaction begin
*/
private void logTransactionBegin(String redelivered, String ids) {
if (log.isDebugEnabled()) {
log.debug("Transaction begin ({}) redelivered({}) for {})", new Object[]{transactionKey, redelivered, ids});
}
}
/**
* Logs the transaction commit
*/
private void logTransactionCommit(String redelivered, String ids) {
if ("true".equals(redelivered)) {
// okay its a redelivered message so log at INFO level if rollbackLoggingLevel is INFO or higher
// this allows people to know that the redelivered message was committed this time
if (rollbackLoggingLevel == LoggingLevel.INFO || rollbackLoggingLevel == LoggingLevel.WARN || rollbackLoggingLevel == LoggingLevel.ERROR) {
log.info("Transaction commit ({}) redelivered({}) for {})", new Object[]{transactionKey, redelivered, ids});
// return after we have logged
return;
}
}
// log non redelivered by default at DEBUG level
log.debug("Transaction commit ({}) redelivered({}) for {})", new Object[]{transactionKey, redelivered, ids});
}
/**
* Logs the transaction rollback.
*/
private void logTransactionRollback(String redelivered, String ids, Throwable e, boolean rollbackOnly) {
if (rollbackLoggingLevel == LoggingLevel.OFF) {
return;
} else if (rollbackLoggingLevel == LoggingLevel.ERROR && log.isErrorEnabled()) {
if (rollbackOnly) {
log.error("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", new Object[]{transactionKey, redelivered, ids});
} else {
log.error("Transaction rollback ({}) redelivered({}) for {} caught: {}", new Object[]{transactionKey, redelivered, ids, e.getMessage()});
}
} else if (rollbackLoggingLevel == LoggingLevel.WARN && log.isWarnEnabled()) {
if (rollbackOnly) {
log.warn("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", new Object[]{transactionKey, redelivered, ids});
} else {
log.warn("Transaction rollback ({}) redelivered({}) for {} caught: {}", new Object[]{transactionKey, redelivered, ids, e.getMessage()});
}
} else if (rollbackLoggingLevel == LoggingLevel.INFO && log.isInfoEnabled()) {
if (rollbackOnly) {
log.info("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", new Object[]{transactionKey, redelivered, ids});
} else {
log.info("Transaction rollback ({}) redelivered({}) for {} caught: {}", new Object[]{transactionKey, redelivered, ids, e.getMessage()});
}
} else if (rollbackLoggingLevel == LoggingLevel.DEBUG && log.isDebugEnabled()) {
if (rollbackOnly) {
log.debug("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", new Object[]{transactionKey, redelivered, ids});
} else {
log.debug("Transaction rollback ({}) redelivered({}) for {} caught: {}", new Object[]{transactionKey, redelivered, ids, e.getMessage()});
}
} else if (rollbackLoggingLevel == LoggingLevel.TRACE && log.isTraceEnabled()) {
if (rollbackOnly) {
log.trace("Transaction rollback ({}) redelivered({}) for {} due exchange was marked for rollbackOnly", new Object[]{transactionKey, redelivered, ids});
} else {
log.trace("Transaction rollback ({}) redelivered({}) for {} caught: {}", new Object[]{transactionKey, redelivered, ids, e.getMessage()});
}
}
}
private static String propagationBehaviorToString(int propagationBehavior) {
String rc;
switch (propagationBehavior) {
case TransactionDefinition.PROPAGATION_MANDATORY:
rc = "PROPAGATION_MANDATORY";
break;
case TransactionDefinition.PROPAGATION_NESTED:
rc = "PROPAGATION_NESTED";
break;
case TransactionDefinition.PROPAGATION_NEVER:
rc = "PROPAGATION_NEVER";
break;
case TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
rc = "PROPAGATION_NOT_SUPPORTED";
break;
case TransactionDefinition.PROPAGATION_REQUIRED:
rc = "PROPAGATION_REQUIRED";
break;
case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
rc = "PROPAGATION_REQUIRES_NEW";
break;
case TransactionDefinition.PROPAGATION_SUPPORTS:
rc = "PROPAGATION_SUPPORTS";
break;
default:
rc = "UNKNOWN";
}
return rc;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy