package org.jboss.seam.util; import static org.jboss.seam.util.EJB.APPLICATION_EXCEPTION; import static org.jboss.seam.util.EJB.rollback; import javax.transaction.Status; import javax.transaction.UserTransaction; import org.jboss.seam.annotations.ApplicationException; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.transaction.Transaction; /** * Performs work in a JTA transaction. * * @author Gavin King */ public abstract class Work<T> { private static final LogProvider log = Logging.getLogProvider(Work.class); protected abstract T work() throws Exception; protected boolean isNewTransactionRequired(boolean transactionActive) { return !transactionActive; } public final T workInTransaction() throws Exception { org.jboss.seam.transaction.UserTransaction transaction = null; boolean transactionActive = false; boolean newTransactionRequired = false; UserTransaction userTransaction = null; try { transaction = Transaction.instance(); transactionActive = transaction.isActiveOrMarkedRollback() || transaction.isRolledBack(); //TODO: temp workaround, what should we really do in this case?? newTransactionRequired = isNewTransactionRequired(transactionActive); userTransaction = newTransactionRequired ? transaction : null; } catch (IllegalStateException e) { // for shutdown case, when we can't get the tx object because the event context is gone // but we should still check if a tx is required and fail accordingly if it is newTransactionRequired = isNewTransactionRequired(false); if (newTransactionRequired) { throw e; } } try { if (newTransactionRequired) { log.debug("beginning transaction"); userTransaction.begin(); } T result = work(); if (newTransactionRequired) { if (transaction.isMarkedRollback()) { log.debug("rolling back transaction"); userTransaction.rollback(); } else { log.debug("committing transaction"); userTransaction.commit(); } } return result; } catch (Exception e) { if (newTransactionRequired && userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION ) { if(isRollbackRequired(e, true)) { log.debug("rolling back transaction"); userTransaction.rollback(); } else { log.debug("committing transaction after ApplicationException(rollback=false):" + e.getMessage()); userTransaction.commit(); } } throw e; } } public static boolean isRollbackRequired(Exception e, boolean isJavaBean) { Class<? extends Exception> clazz = e.getClass(); return ( isSystemException(e, isJavaBean, clazz) ) || ( isJavaBean && clazz.isAnnotationPresent(APPLICATION_EXCEPTION) && rollback( clazz.getAnnotation(APPLICATION_EXCEPTION) ) ) || ( clazz.isAnnotationPresent(ApplicationException.class) && clazz.getAnnotation(ApplicationException.class).rollback() ); } private static boolean isSystemException(Exception e, boolean isJavaBean, Class<? extends Exception> clazz) { return isJavaBean && (e instanceof RuntimeException) && !clazz.isAnnotationPresent(APPLICATION_EXCEPTION) && !clazz.isAnnotationPresent(ApplicationException.class) && //TODO: this is hackish, maybe just turn off RollackInterceptor for @Converter/@Validator components !JSF.VALIDATOR_EXCEPTION.isInstance(e) && !JSF.CONVERTER_EXCEPTION.isInstance(e); } }