package org.ovirt.engine.core.utils.transaction;
import org.ovirt.engine.core.utils.ejb.EjbUtils;
import org.ovirt.engine.core.utils.ejb.ContainerManagedResourceType;
import org.ovirt.engine.core.compat.LogCompat;
import org.ovirt.engine.core.compat.LogFactoryCompat;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import javax.transaction.*;
import javax.ejb.TransactionRolledbackLocalException;
public class TransactionSupport {
private static LogCompat log = LogFactoryCompat.getLog(TransactionSupport.class);
/**
* JBoss specific location of TransactionManager
*/
private static TransactionManager findTransactionManager() {
TransactionManager tm = EjbUtils.findResource(ContainerManagedResourceType.TRANSACTION_MANAGER);
return tm;
}
/**
* Suspends and returns current transaction
*/
public static Transaction suspend() {
try {
TransactionManager tm = findTransactionManager();
return tm.suspend();
} catch (Exception e) {
throw new RuntimeException("Unable to suspend transaction", e);
}
}
/**
* Resumes given transaction
*/
public static void resume(Transaction transaction) {
try {
TransactionManager tm = findTransactionManager();
tm.resume(transaction);
} catch (Exception e) {
throw new RuntimeException("Unable to resume transaction", e);
}
}
/**
* Returns current transaction
*/
public static Transaction current() {
try {
TransactionManager tm = findTransactionManager();
return tm.getTransaction();
} catch (Exception e) {
throw new RuntimeException("Unable to get handle to current transaction", e);
}
}
/**
* Attaches rollback handler to current transaction
*/
public static void registerRollbackHandler(final RollbackHandler rollbackHandler) {
try {
current().registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
if (!needToRollback(status))
return;
rollbackHandler.Rollback();
}
});
} catch (Exception e) {
throw new RuntimeException("Unable to register synchronization to current transaction", e);
}
}
private static boolean needToRollback(int status) {
return status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLEDBACK
|| status == Status.STATUS_ROLLING_BACK;
}
/**
* Runs given code in a given transaction scope
*/
public static <T> T executeInScope(TransactionScopeOption scope, TransactionMethod<T> code) {
// check if we are already in rollback
try {
TransactionManager tm = findTransactionManager();
if (needToRollback(tm.getStatus())) {
throw new TransactionRolledbackLocalException(
"Current transaction is marked for rollback, no further operations are possible or desired");
}
} catch (SystemException e) {
throw new RuntimeException("Failed to check transaction status - this shouldn't ever happen");
}
switch (scope) {
case RequiresNew:
return executeInNewTransaction(code);
case Suppress:
return executeInSuppressed(code);
case Required:
return executeInRequired(code);
default:
throw new RuntimeException("Undefined Scope: " + scope);
}
}
private static boolean statusOneOf(int status, int... options) {
for (int option : options) {
if (status == option)
return true;
}
return false;
}
/**
* Simply executes given code in current scope It won't actually open transaction since we are assuming we always
* start with transaction from JBoss That assumption should always hold - if not we need to look at specific case
* where it fails
*/
private static <T> T executeInRequired(TransactionMethod<T> code) {
try {
TransactionManager tm = findTransactionManager();
// verify we are not in a bad state
int status = tm.getStatus();
if (statusOneOf(status, Status.STATUS_COMMITTED, Status.STATUS_COMMITTING, Status.STATUS_MARKED_ROLLBACK,
Status.STATUS_PREPARED, Status.STATUS_PREPARING, Status.STATUS_ROLLEDBACK,
Status.STATUS_ROLLING_BACK, Status.STATUS_UNKNOWN)) {
throw new RuntimeException(
"Transaction is required to proceed but current transaction status is wrong: " + status);
}
if (status == Status.STATUS_NO_TRANSACTION)
return executeInNewTransaction(code);
else
return code.runInTransaction();
} catch (RuntimeException rte) {
throw rte;
} catch (Exception e) {
log.error("executeInRequired - Wrapping Exception: " + e.getClass().getName() + " with RunTimeException");
throw new RuntimeException("Failed running code", e);
}
}
/**
* Forces "SUPRESS" and executes the code in that scope
*/
private static <T> T executeInSuppressed(TransactionMethod<T> code) {
T result = null;
try {
TransactionManager tm = findTransactionManager();
Transaction transaction = tm.getTransaction();
if (transaction != null)
transaction = tm.suspend();
try {
result = code.runInTransaction();
} finally {
if (transaction != null)
tm.resume(transaction);
}
} catch (RuntimeException rte) {
throw rte;
} catch (Exception e) {
log.error("executeInSuppressed - Wrapping Exception: " + e.getClass().getName() + " with RunTimeException");
throw new RuntimeException("Failed executing code", e);
}
return result;
}
/**
* Forces "REQUIRES_NEW" and executes given code in that scope
*/
public static <T> T executeInNewTransaction(TransactionMethod<T> code) {
T result = null;
Transaction transaction = null;
try {
TransactionManager tm = findTransactionManager();
// suspend existing if exists
transaction = tm.getTransaction();
if (transaction != null)
transaction = tm.suspend();
// start new transaction
tm.begin();
Transaction newTransaction = tm.getTransaction();
// run the code
try {
result = code.runInTransaction();
} catch (RuntimeException rte) {
tm.rollback();
log.info("transaction rolled back");
throw rte;
} catch (Exception e) {
// code failed need to rollback
tm.rollback();
log.info("transaction rolled back");
log.error("executeInNewTransaction - Wrapping Exception: " + e.getClass().getName()
+ " with RunTimeException");
throw new RuntimeException("Failed executing code", e);
}
// commit or rollback according to state
if (needToRollback(newTransaction.getStatus())) {
tm.rollback();
} else {
tm.commit();
}
} catch (SystemException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (SecurityException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (IllegalStateException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (RollbackException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (HeuristicMixedException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (HeuristicRollbackException e) {
throw new RuntimeException("Failed managing transaction", e);
} catch (NotSupportedException e) {
throw new RuntimeException("Failed managing transaction", e);
} finally {
// check if we need to resume previous transaction
if (transaction != null) {
resume(transaction);
}
}
// and we are done...
return result;
}
/**
* Marks the transaction for forced rollback. The actual rollback will happen when the code reaches the transaction
* boundary
*/
public static void setRollbackOnly() {
try {
TransactionManager tm = findTransactionManager();
Transaction transaction = tm.getTransaction();
if (transaction != null) {
transaction.setRollbackOnly();
}
} catch (SystemException e) {
throw new RuntimeException("Failed to mark transaction for rollback", e);
}
}
}