/*
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
*
* Licensed 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.switchyard.handlers;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.jboss.logging.Logger;
import org.switchyard.Exchange;
import org.switchyard.ExchangeHandler;
import org.switchyard.HandlerException;
import org.switchyard.Property;
import org.switchyard.Scope;
import org.switchyard.label.BehaviorLabel;
import org.switchyard.policy.PolicyUtil;
import org.switchyard.policy.TransactionPolicy;
import org.switchyard.runtime.RuntimeLogger;
import org.switchyard.runtime.RuntimeMessages;
import org.switchyard.runtime.util.TransactionManagerLocator;
/**
* Interprets transactional policy specified on an exchange and handles
* transactional requirements.
*/
public class TransactionHandler implements ExchangeHandler {
private static final String SUSPENDED_TRANSACTION_PROPERTY =
"org.switchyard.exchange.transaction.suspended";
private static final String INITIATED_TRANSACTION_PROPERTY =
"org.switchyard.exchange.transaction.initiated";
private static final String BEFORE_INVOKED_PROPERTY =
"org.switchyard.exchange.transaction.beforeInvoked";
private static Logger _log = Logger.getLogger(TransactionHandler.class);
private TransactionManager _transactionManager;
/**
* Create a new TransactionHandler.
*/
public TransactionHandler() {
_transactionManager = TransactionManagerLocator.locateTransactionManager();
if (_transactionManager == null) {
_log.debug("Unable to find TransactionManager - Transaction Policy handling will not be available.");
}
}
@Override
public void handleMessage(Exchange exchange) throws HandlerException {
// if no TM is available, there's nothing to do
if (_transactionManager == null) {
return;
}
Property prop = exchange.getContext().getProperty(BEFORE_INVOKED_PROPERTY, Scope.EXCHANGE);
if (prop != null && Boolean.class.cast(prop.getValue())) {
// OUT phase in IN_OUT exchange or 2nd invocation in IN_ONLY exchange
handleAfter(exchange);
} else {
exchange.getContext().setProperty(BEFORE_INVOKED_PROPERTY, Boolean.TRUE, Scope.EXCHANGE).addLabels(BehaviorLabel.TRANSIENT.label());
handleBefore(exchange);
}
}
@Override
public void handleFault(Exchange exchange) {
// if no TM is available, there's nothing to do
if (_transactionManager == null) {
return;
}
try {
Property rollbackOnFaultProperty = exchange.getContext().getProperty(Exchange.ROLLBACK_ON_FAULT);
if (rollbackOnFaultProperty != null && rollbackOnFaultProperty.getValue() != null
&& Boolean.class.cast(rollbackOnFaultProperty.getValue())) {
Transaction transaction = getCurrentTransaction();
if (transaction != null) {
transaction.setRollbackOnly();
}
}
handleAfter(exchange);
} catch (Exception e) {
_log.error(e);
}
}
void setTransactionManager(TransactionManager transactionManager) {
_transactionManager = transactionManager;
}
TransactionManager getTransactionManager() {
return _transactionManager;
}
private void handleAfter(Exchange exchange) throws HandlerException {
Transaction transaction = null;
try {
// complete the transaction which is initiated by this handler
transaction = (Transaction) exchange.getContext().getPropertyValue(INITIATED_TRANSACTION_PROPERTY);
if (transaction != null) {
endTransaction();
}
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedToCompleteTransaction(e);
} finally {
// resume the transaction which is suspended by this handler
transaction = (Transaction) exchange.getContext().getPropertyValue(SUSPENDED_TRANSACTION_PROPERTY);
if (transaction != null) {
resumeTransaction(transaction);
}
}
}
private void handleBefore(Exchange exchange) throws HandlerException {
if (!(propagatesRequired(exchange) || suspendsRequired(exchange) || managedGlobalRequired(exchange)
|| managedLocalRequired(exchange) || noManagedRequired(exchange))) {
return;
}
evaluatePolicyCombination(exchange);
evaluateTransactionStatus(exchange);
if (isEligibleToSuspendTransaction(exchange)) {
suspendTransaction(exchange);
}
if (isEligibleToStartTransaction(exchange)) {
startTransaction(exchange);
}
provideRequiredPolicies(exchange);
}
private void evaluatePolicyCombination(Exchange exchange) throws HandlerException {
// check for incompatible policy definition
if (suspendsRequired(exchange) && propagatesRequired(exchange)) {
throw RuntimeMessages.MESSAGES.invalidTransactionPolicy(TransactionPolicy.SUSPENDS_TRANSACTION.toString(),
TransactionPolicy.PROPAGATES_TRANSACTION.toString());
}
if (managedGlobalRequired(exchange) && managedLocalRequired(exchange)
|| managedGlobalRequired(exchange) && noManagedRequired(exchange)
|| managedLocalRequired(exchange) && noManagedRequired(exchange)) {
throw RuntimeMessages.MESSAGES.invalidTransactionPolicy(TransactionPolicy.MANAGED_TRANSACTION_GLOBAL.toString(),
TransactionPolicy.NO_MANAGED_TRANSACTION.toString());
}
if (propagatesRequired(exchange) && managedLocalRequired(exchange)
|| propagatesRequired(exchange) && noManagedRequired(exchange)) {
throw RuntimeMessages.MESSAGES.invalidTransactionPolicyCombo(TransactionPolicy.PROPAGATES_TRANSACTION.toString(),
TransactionPolicy.MANAGED_TRANSACTION_LOCAL.toString(), TransactionPolicy.NO_MANAGED_TRANSACTION.toString());
}
}
private void evaluateTransactionStatus(Exchange exchange) throws HandlerException {
Transaction transaction = getCurrentTransaction();
if (transaction == null && propagatesRequired(exchange) && !managedGlobalRequired(exchange)) {
throw RuntimeMessages.MESSAGES.invalidTransactionStatus(TransactionPolicy.PROPAGATES_TRANSACTION.toString());
}
}
private boolean isEligibleToSuspendTransaction(Exchange exchange) throws HandlerException {
Transaction transaction = getCurrentTransaction();
if (transaction == null) {
return false;
}
if (managedLocalRequired(exchange) || noManagedRequired(exchange) || suspendsRequired(exchange)) {
return true;
}
return false;
}
private boolean isEligibleToStartTransaction(Exchange exchange) throws HandlerException {
Transaction transaction = getCurrentTransaction();
if (managedLocalRequired(exchange)) {
return true;
} else if (managedGlobalRequired(exchange)) {
if (transaction == null) {
return true;
}
}
return false;
}
private void provideRequiredPolicies(Exchange exchange) {
if (suspendsRequired(exchange)) {
provideSuspends(exchange);
} else if (propagatesRequired(exchange)) {
providePropagates(exchange);
}
if (managedGlobalRequired(exchange)) {
provideManagedGlobal(exchange);
} else if (managedLocalRequired(exchange)) {
provideManagedLocal(exchange);
} else if (noManagedRequired(exchange)) {
provideNoManaged(exchange);
}
}
private boolean managedGlobalRequired(Exchange exchange) {
return PolicyUtil.isRequired(exchange, TransactionPolicy.MANAGED_TRANSACTION_GLOBAL);
}
private boolean managedLocalRequired(Exchange exchange) {
return PolicyUtil.isRequired(exchange, TransactionPolicy.MANAGED_TRANSACTION_LOCAL);
}
private boolean noManagedRequired(Exchange exchange) {
return PolicyUtil.isRequired(exchange, TransactionPolicy.NO_MANAGED_TRANSACTION);
}
private boolean suspendsRequired(Exchange exchange) {
return PolicyUtil.isRequired(exchange, TransactionPolicy.SUSPENDS_TRANSACTION);
}
private boolean propagatesRequired(Exchange exchange) {
return PolicyUtil.isRequired(exchange, TransactionPolicy.PROPAGATES_TRANSACTION);
}
private void providePropagates(Exchange exchange) {
PolicyUtil.provide(exchange, TransactionPolicy.PROPAGATES_TRANSACTION);
}
private void provideSuspends(Exchange exchange) {
PolicyUtil.provide(exchange, TransactionPolicy.SUSPENDS_TRANSACTION);
}
private void provideManagedGlobal(Exchange exchange) {
PolicyUtil.provide(exchange, TransactionPolicy.MANAGED_TRANSACTION_GLOBAL);
}
private void provideManagedLocal(Exchange exchange) {
PolicyUtil.provide(exchange, TransactionPolicy.MANAGED_TRANSACTION_LOCAL);
}
private void provideNoManaged(Exchange exchange) {
PolicyUtil.provide(exchange, TransactionPolicy.NO_MANAGED_TRANSACTION);
}
private void startTransaction(Exchange exchange) throws HandlerException {
if (_log.isDebugEnabled()) {
printDebugInfo("Creating new transaction");
}
int txStatus = getCurrentTransactionStatus();
if (txStatus == Status.STATUS_NO_TRANSACTION) {
Transaction transaction = null;
try {
_transactionManager.begin();
transaction = _transactionManager.getTransaction();
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedCreateNewTransaction(e);
}
if (transaction != null) {
if (_log.isDebugEnabled()) {
printDebugInfo("Created new transaction");
}
exchange.getContext().setProperty(INITIATED_TRANSACTION_PROPERTY, transaction, Scope.EXCHANGE).addLabels(BehaviorLabel.TRANSIENT.label());
}
} else {
throw RuntimeMessages.MESSAGES.transactionAlreadyExists();
}
}
private void endTransaction() throws HandlerException {
if (_log.isDebugEnabled()) {
printDebugInfo("Completing transaction");
}
int txStatus = getCurrentTransactionStatus();
if (txStatus == Status.STATUS_MARKED_ROLLBACK) {
try {
_transactionManager.rollback();
if (_log.isDebugEnabled()) {
printDebugInfo("Transaction rolled back as it has been marked as RollbackOnly");
}
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedToRollbackTransaction(e);
}
} else if (txStatus == Status.STATUS_ACTIVE) {
try {
_transactionManager.commit();
if (_log.isDebugEnabled()) {
printDebugInfo("Transaction has been committed");
}
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedToCommitTransaction(e);
}
} else {
throw RuntimeMessages.MESSAGES.failedToCompleteWithStatus(txStatus);
}
}
private void suspendTransaction(Exchange exchange) {
if (_log.isDebugEnabled()) {
printDebugInfo("Suspending active transaction");
}
Transaction transaction = null;
try {
transaction = _transactionManager.suspend();
} catch (SystemException sysEx) {
RuntimeLogger.ROOT_LOGGER.failedToSuspendTransactionOnExchange(sysEx);
}
if (transaction != null) {
if (_log.isDebugEnabled()) {
printDebugInfo("Suspended active transaction");
}
exchange.getContext().setProperty(SUSPENDED_TRANSACTION_PROPERTY, transaction, Scope.EXCHANGE).addLabels(BehaviorLabel.TRANSIENT.label());
}
}
private void resumeTransaction(Transaction transaction) {
try {
if (_log.isDebugEnabled()) {
printDebugInfo("Resuming suspended transaction");
}
_transactionManager.resume(transaction);
if (_log.isDebugEnabled()) {
printDebugInfo("Resumed suspended transaction");
}
} catch (Exception ex) {
RuntimeLogger.ROOT_LOGGER.failedToResumeTransaction(ex);
}
}
private Transaction getCurrentTransaction() throws HandlerException {
try {
return _transactionManager.getTransaction();
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedToRetrieveStatus(e);
}
}
private int getCurrentTransactionStatus() throws HandlerException {
try {
return _transactionManager.getStatus();
} catch (Exception e) {
throw RuntimeMessages.MESSAGES.failedToRetrieveStatus(e);
}
}
private void printDebugInfo(String message) {
StringBuilder buf = new StringBuilder(message);
buf.append(" - [Thread: ");
buf.append(Thread.currentThread().toString());
buf.append(", Transaction: ");
Transaction currentTx = null;
try {
currentTx = getCurrentTransaction();
} catch (Exception e) { e.getMessage(); } // ignore
if (currentTx == null) {
buf.append("N/A");
} else {
buf.append(currentTx.toString());
}
buf.append(']');
_log.debug(buf.toString());
}
}