/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.transaction;
import org.aopalliance.intercept.MethodInvocation;
import org.seedstack.seed.Configuration;
import org.seedstack.seed.transaction.Propagation;
import org.seedstack.seed.transaction.TransactionConfig;
import org.seedstack.seed.transaction.spi.TransactionHandler;
import org.seedstack.seed.transaction.spi.TransactionMetadata;
import org.seedstack.seed.SeedException;
import javax.inject.Inject;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
/**
* This transaction manager delegates to JTA the transactional behavior.
*/
public class JtaTransactionManager extends AbstractTransactionManager {
private static final String[] AUTODETECT_TRANSACTION_MANAGER_NAMES = new String[]{"java:comp/TransactionManager", "java:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager"};
@Inject
private Context jndiContext;
@Configuration
private TransactionConfig.JtaConfig jtaConfig;
protected UserTransaction userTransaction;
protected TransactionManager transactionManager;
@Override
protected Object doMethodInterception(TransactionLogger transactionLogger, MethodInvocation invocation, TransactionMetadata transactionMetadata, TransactionHandler<Object> transactionHandler) throws Throwable {
initJTAObjects(transactionLogger);
PropagationResult propagationResult;
try {
propagationResult = handlePropagation(transactionMetadata.getPropagation());
} catch (Exception e) {
throw SeedException.wrap(e, TransactionErrorCode.TRANSACTION_PROPAGATION_ERROR);
}
Transaction suspendedTransaction = null;
try {
if (propagationResult.isSuspendCurrentTransaction() && userTransaction.getStatus() == Status.STATUS_ACTIVE) {
if (transactionManager != null) {
transactionLogger.log("suspending current JTA transaction");
suspendedTransaction = transactionManager.suspend();
} else {
throw SeedException.createNew(TransactionErrorCode.TRANSACTION_SUSPENSION_IS_NOT_SUPPORTED);
}
}
if (propagationResult.isNewTransactionNeeded()) {
transactionLogger.log("initializing transaction handler");
transactionHandler.doInitialize(transactionMetadata);
}
Object result = null;
try {
if (propagationResult.isNewTransactionNeeded()) {
transactionLogger.log("beginning the JTA transaction");
userTransaction.begin();
} else {
transactionLogger.log("participating in an existing JTA transaction");
}
try {
if (userTransaction.getStatus() == Status.STATUS_ACTIVE) {
transactionHandler.doJoinGlobalTransaction();
}
try {
result = doInvocation(transactionLogger, invocation, transactionMetadata, userTransaction);
} catch (Throwable throwable) {
if (propagationResult.isNewTransactionNeeded()) {
transactionLogger.log("rolling back JTA transaction after invocation exception");
userTransaction.rollback();
} else if (transactionMetadata.isRollbackOnParticipationFailure()) {
transactionLogger.log("marking JTA transaction as rollback-only after invocation exception");
userTransaction.setRollbackOnly();
}
throw throwable;
}
if (propagationResult.isNewTransactionNeeded()) {
transactionLogger.log("committing JTA transaction");
userTransaction.commit();
}
} finally {
if (propagationResult.isNewTransactionNeeded() && userTransaction.getStatus() == Status.STATUS_ACTIVE) {
transactionLogger.log("rolling back JTA transaction (no commit occurred)");
userTransaction.rollback();
}
}
} finally {
if (propagationResult.isNewTransactionNeeded()) {
transactionLogger.log("cleaning up transaction handler");
transactionHandler.doCleanup();
}
}
return result;
} finally {
if (suspendedTransaction != null) {
transactionLogger.log("resuming suspended transaction");
transactionManager.resume(suspendedTransaction);
}
}
}
protected TransactionManager getTransactionManager(TransactionLogger transactionLogger, UserTransaction ut) {
if (ut instanceof TransactionManager) {
transactionLogger.log("JTA UserTransaction object [{}] implements TransactionManager", ut);
return (TransactionManager) ut;
}
if (jtaConfig.getTxManagerName() != null) {
try {
return (TransactionManager) jndiContext.lookup(jtaConfig.getTxManagerName());
} catch (NamingException e) {
throw SeedException.wrap(e, TransactionErrorCode.UNABLE_TO_FIND_JTA_TRANSACTION_MANAGER);
}
}
for (String jndiName : AUTODETECT_TRANSACTION_MANAGER_NAMES) {
try {
TransactionManager tm = (TransactionManager) jndiContext.lookup(jndiName);
transactionLogger.log("JTA TransactionManager found at JNDI location [{}]", jndiName);
return tm;
} catch (NamingException ex) {
transactionLogger.log("No JTA TransactionManager found at JNDI location [{}]", jndiName, ex);
}
}
return null;
}
protected UserTransaction getUserTransaction(TransactionLogger transactionLogger) throws NamingException {
String jndiName = jtaConfig.getUserTxName();
UserTransaction ut = (UserTransaction) jndiContext.lookup(jndiName);
transactionLogger.log("JTA UserTransaction found at default JNDI location [{}]", jndiName);
return ut;
}
private void initJTAObjects(TransactionLogger transactionLogger) {
if (userTransaction == null) {
try {
userTransaction = getUserTransaction(transactionLogger);
} catch (Exception e) {
throw SeedException.wrap(e, TransactionErrorCode.UNABLE_TO_FIND_JTA_TRANSACTION);
}
}
if (transactionManager == null) {
transactionManager = getTransactionManager(transactionLogger, userTransaction);
}
}
private PropagationResult handlePropagation(Propagation propagation) throws SystemException {
switch (propagation) {
case MANDATORY:
if (userTransaction.getStatus() != Status.STATUS_ACTIVE) {
throw SeedException.createNew(TransactionErrorCode.TRANSACTION_NEEDED_WHEN_USING_PROPAGATION_MANDATORY);
}
return new PropagationResult(false, false);
case NEVER:
if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION) {
throw SeedException.createNew(TransactionErrorCode.NO_TRANSACTION_ALLOWED_WHEN_USING_PROPAGATION_NEVER);
}
return new PropagationResult(false, false);
case NOT_SUPPORTED:
return new PropagationResult(false, true);
case REQUIRED:
return new PropagationResult(userTransaction.getStatus() != Status.STATUS_ACTIVE, false);
case REQUIRES_NEW:
return new PropagationResult(true, true);
case SUPPORTS:
return new PropagationResult(false, false);
default:
throw SeedException.createNew(TransactionErrorCode.PROPAGATION_NOT_SUPPORTED).put("propagation", propagation);
}
}
}