package org.jboss.seam.ioc.spring; import static org.jboss.seam.annotations.Install.FRAMEWORK; import javax.persistence.EntityManager; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Destroy; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.ServletLifecycle; import org.jboss.seam.core.Expressions.ValueExpression; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.transaction.AbstractUserTransaction; import org.jboss.seam.transaction.Synchronizations; import org.springframework.beans.factory.BeanFactory; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.web.context.support.WebApplicationContextUtils; @Name("org.jboss.seam.transaction.transaction") @Scope(ScopeType.EVENT) @Install(value = false, precedence = FRAMEWORK) @BypassInterceptors public class SpringTransaction extends AbstractUserTransaction { private static final LogProvider log = Logging.getLogProvider(SpringTransaction.class); private ValueExpression<PlatformTransactionManager> platformTransactionManager; private String platformTransactionManagerName; private DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); private boolean conversationContextRequired = true; private TransactionStatus currentTransaction; private Boolean joinTransaction; @Override public void registerSynchronization(Synchronization sync) { getSynchronizations().registerSynchronization(sync); } public void begin() throws NotSupportedException, SystemException { log.debug("beginning Spring transaction"); if (TransactionSynchronizationManager.isActualTransactionActive()) { throw new NotSupportedException("A Spring transaction is already active."); } currentTransaction = getPlatformTransactionManagerRequired().getTransaction(definition); getSynchronizations().afterTransactionBegin(); } /** * Obtains a PlatformTransactionManager from either the name or expression * specified. */ protected PlatformTransactionManager getPlatformTransactionManager() { if (((platformTransactionManagerName == null || "".equals(platformTransactionManagerName)) && platformTransactionManager == null) || (platformTransactionManagerName != null && !"".equals(platformTransactionManagerName)) && platformTransactionManager != null) { throw new IllegalArgumentException("When configuring spring:spring-transaction you must specify either platformTransactionManager or platformTransactionManagerName."); } if ((platformTransactionManagerName == null || "".equals(platformTransactionManagerName))) { return platformTransactionManager.getValue(); } BeanFactory beanFactory = findBeanFactory(); if (beanFactory == null) { log.debug("BeanFactory either not found or not yet available."); return null; } PlatformTransactionManager ptm = (PlatformTransactionManager) beanFactory.getBean(platformTransactionManagerName); return ptm; } private PlatformTransactionManager getPlatformTransactionManagerRequired() { PlatformTransactionManager ptm = getPlatformTransactionManager(); if (ptm == null) { throw new IllegalStateException("Unable to find PlatformTransactionManager"); } return ptm; } /** * Attempts to find a BeanFactory and return the instance found. * * @return BeanFactory or null if non found. */ protected BeanFactory findBeanFactory() { return WebApplicationContextUtils.getWebApplicationContext(ServletLifecycle.getServletContext()); } public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { log.debug("committing Spring transaction"); assertActive(); boolean success = false; Synchronizations synchronizations = getSynchronizations(); synchronizations.beforeTransactionCommit(); try { getPlatformTransactionManagerRequired().commit(currentTransaction); success = true; } catch(Exception e) { e.printStackTrace(); throw new SystemException(e.toString()); } finally { currentTransaction = null; synchronizations.afterTransactionCommit(success); } } public int getStatus() throws SystemException { PlatformTransactionManager ptm = getPlatformTransactionManager(); if (ptm == null) { return Status.STATUS_NO_TRANSACTION; } if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionStatus transaction = null; try { if (currentTransaction == null) { transaction = ptm.getTransaction(definition); if (transaction.isNewTransaction()) { return Status.STATUS_COMMITTED; } } else { transaction = currentTransaction; } // If SynchronizationManager thinks it has an active transaction but // our transaction is a new one // then we must be in the middle of committing if (transaction.isCompleted()) { if (transaction.isRollbackOnly()) { return Status.STATUS_ROLLEDBACK; } return Status.STATUS_COMMITTED; } else { if (transaction.isRollbackOnly()) { return Status.STATUS_MARKED_ROLLBACK; } return Status.STATUS_ACTIVE; } } finally { if (currentTransaction == null) { ptm.commit(transaction); } } } return Status.STATUS_NO_TRANSACTION; } public void rollback() throws IllegalStateException, SecurityException, SystemException { log.debug("rolling back Spring transaction"); assertActive(); try { getPlatformTransactionManagerRequired().rollback(currentTransaction); } finally { currentTransaction = null; getSynchronizations().afterTransactionRollback(); } } /** * */ private void assertActive() { if (!TransactionSynchronizationManager.isActualTransactionActive() || currentTransaction == null) { throw new IllegalStateException("No transaction currently active that Seam started." + "Seam should only be able to committ or rollback transactions it started."); } } public void setRollbackOnly() throws IllegalStateException, SystemException { if (!TransactionSynchronizationManager.isActualTransactionActive()) { throw new IllegalStateException("No Spring Transaction is currently available."); } TransactionStatus transaction = null; PlatformTransactionManager ptm = getPlatformTransactionManagerRequired(); try { if (currentTransaction == null) { transaction = ptm.getTransaction(definition); } else { transaction = currentTransaction; } transaction.setRollbackOnly(); } finally { if (currentTransaction == null) { ptm.commit(transaction); } } } public void setTransactionTimeout(int timeout) throws SystemException { if (TransactionSynchronizationManager.isActualTransactionActive()) { // cannot set timeout on already running transaction return; } definition.setTimeout(timeout); } @Override public void enlist(EntityManager entityManager) throws SystemException { if (joinTransaction == null) { // If not set attempt to detect if we should join or not if (!(getPlatformTransactionManagerRequired() instanceof JpaTransactionManager)) { super.enlist(entityManager); } } else if (joinTransaction) { super.enlist(entityManager); } } @Destroy public void cleanupCurrentTransaction() { if (currentTransaction != null) { try { log.debug("Attempting to rollback left over transaction. Should never be called."); getPlatformTransactionManagerRequired().rollback(currentTransaction); } catch (Throwable e) { // ignore } } } public void setPlatformTransactionManager(ValueExpression<PlatformTransactionManager> platformTransactionManager) { this.platformTransactionManager = platformTransactionManager; } public void setPlatformTransactionManagerName(String platformTransactionManagerName) { this.platformTransactionManagerName = platformTransactionManagerName; } @Override public boolean isConversationContextRequired() { return conversationContextRequired; } public void setConversationContextRequired(boolean conversationContextRequired) { this.conversationContextRequired = conversationContextRequired; } public void setJoinTransaction(Boolean joinTransaction) { this.joinTransaction = joinTransaction; } }