/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.capedwarf.server.jee.tx; import org.jboss.capedwarf.server.api.tx.TransactionPropagationType; import org.jboss.capedwarf.server.api.tx.Transactional; import org.jboss.capedwarf.server.api.tx.TxInterceptorDelegate; import org.jboss.logging.Logger; import javax.inject.Inject; import javax.interceptor.InvocationContext; import javax.transaction.*; import java.io.Serializable; import java.lang.reflect.AnnotatedElement; import java.util.HashMap; import java.util.Map; /** * UserTransaction interceptor. * Note: no nested tx support! * * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a> */ public class UserTransactionInterceptor implements TxInterceptorDelegate, Serializable { private static final long serialVersionUID = 1L; private static final Logger log = Logger.getLogger(UserTransactionInterceptor.class); private transient UserTransaction tx; private transient Map<AnnotatedElement, TransactionMetadata> transactionMetadata = new HashMap<AnnotatedElement, TransactionMetadata>(); public Object invoke(final InvocationContext invocation) throws Exception { TransactionPropagationType propType = getPropType(invocation); switch (propType) { case MANDATORY: return mandatory(invocation); case NEVER: return never(invocation); case NOT_SUPPORTED: return notSupported(invocation); case REQUIRED: return required(invocation); case REQUIRES_NEW: return requiresNew(invocation); case SUPPORTS: return supports(invocation); default: throw new IllegalStateException("Unexpected tx propagation type " + propType + " on " + invocation); } } /** * The <code>endTransaction</code> method ends a transaction and * translates any exceptions into * TransactionRolledBack[Local]Exception or SystemException. */ protected void endTransaction() { try { if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) { tx.rollback(); } else { // Commit tx // This will happen if // a) everything goes well // b) app. exception was thrown tx.commit(); } } catch (RollbackException e) { handleEndTransactionException(e); } catch (HeuristicMixedException e) { handleEndTransactionException(e); } catch (HeuristicRollbackException e) { handleEndTransactionException(e); } catch (SystemException e) { handleEndTransactionException(e); } } private TransactionPropagationType getPropType(final InvocationContext invocation) { TransactionMetadata transMeta = lookupTransactionMetadata(invocation.getMethod()); /** is method annotated */ if (transMeta.isAnnotationPresent()) { return transMeta.getPropType(); } else { transMeta = lookupTransactionMetadata(invocation.getTarget().getClass()); /** if method is not annotated, then class must be */ if (transMeta.isAnnotationPresent()) { return transMeta.getPropType(); } else { throw new RuntimeException("No Transacional annotation found. " + invocation); } } } protected Object invokeInCallerTx(InvocationContext invocation) throws Exception { try { return invocation.proceed(); } catch (Throwable t) { handleExceptionInCallerTx(invocation, t); } throw new RuntimeException("UNREACHABLE"); } protected Object invokeInNoTx(InvocationContext invocation) throws Exception { return invocation.proceed(); } protected Object invokeInOurTx(InvocationContext invocation) throws Exception { tx.begin(); try { return invocation.proceed(); } catch (Throwable t) { handleExceptionInOurTx(invocation, t); } finally { endTransaction(); } throw new RuntimeException("UNREACHABLE"); } protected Object mandatory(InvocationContext invocation) throws Exception { if (tx.getStatus() != Status.STATUS_ACTIVE) { //TODO throw typed exception // throw new EJBTransactionRequiredException("Transaction is required for invocation: " + invocation); throw new RuntimeException("Transaction is required for invocation: " + invocation); } return invokeInCallerTx(invocation); } protected Object never(InvocationContext invocation) throws Exception { if (tx.getStatus() != Status.STATUS_NO_TRANSACTION) { //TODO throw typed exception throw new RuntimeException("Transaction present on server in Never call (EJB3 13.6.2.6)"); } return invokeInNoTx(invocation); } protected Object notSupported(InvocationContext invocation) throws Exception { // TODO return invokeInNoTx(invocation); } protected Object required(InvocationContext invocation) throws Exception { if (tx.getStatus() == Status.STATUS_NO_TRANSACTION) { return invokeInOurTx(invocation); } else { return invokeInCallerTx(invocation); } } protected Object requiresNew(InvocationContext invocation) throws Exception { if (tx.getStatus() == Status.STATUS_ACTIVE) { throw new NotSupportedException("Requires-New is not supported."); } else { return invokeInOurTx(invocation); } } protected Object supports(InvocationContext invocation) throws Exception { if (tx.getStatus() == Status.STATUS_NO_TRANSACTION) { return invokeInNoTx(invocation); } else { return invokeInCallerTx(invocation); } } /** * The <code>setRollbackOnly</code> method calls setRollbackOnly() * on the invocation's transaction and logs any exceptions than may * occur. */ protected void setRollbackOnly() { try { tx.setRollbackOnly(); } catch (SystemException ex) { log.error("SystemException while setting transaction for rollback only", ex); } catch (IllegalStateException ex) { log.error("IllegalStateException while setting transaction for rollback only", ex); } } protected void handleEndTransactionException(Exception e) { //TODO throw typed exception throw new RuntimeException("Transaction rolled back", e); } protected void handleExceptionInCallerTx(InvocationContext invocation, Throwable t) throws Exception { setRollbackOnly(); log.error(t); throw (Exception) t; } public void handleExceptionInOurTx(InvocationContext invocation, Throwable t) throws Exception { setRollbackOnly(); throw (Exception) t; } private TransactionMetadata lookupTransactionMetadata(AnnotatedElement element) { TransactionMetadata metadata = transactionMetadata.get(element); if (metadata == null) metadata = loadMetadata(element); return metadata; } private synchronized TransactionMetadata loadMetadata(AnnotatedElement element) { if (transactionMetadata.containsKey(element) == false) { TransactionMetadata metadata = new TransactionMetadata(element); transactionMetadata.put(element, metadata); return metadata; } return transactionMetadata.get(element); } private class TransactionMetadata { private boolean annotationPresent; private TransactionPropagationType propType; public TransactionMetadata(AnnotatedElement element) { annotationPresent = element.isAnnotationPresent(Transactional.class); if (annotationPresent) { propType = element.getAnnotation(Transactional.class).value(); } } public boolean isAnnotationPresent() { return annotationPresent; } /** * @return the propType */ public TransactionPropagationType getPropType() { return propType; } } @Inject public void setUserTransaction(UserTransaction tx) { this.tx = tx; } }