/* * JBoss, Home of Professional Open Source. * Copyright 2008, 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.ejb.plugins; import java.io.PrintWriter; import java.io.StringWriter; import java.rmi.RemoteException; import java.rmi.ServerException; import java.rmi.NoSuchObjectException; import java.lang.reflect.Method; import javax.transaction.TransactionManager; import javax.transaction.TransactionRolledbackException; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.Synchronization; import javax.transaction.RollbackException; import javax.ejb.EJBException; import javax.ejb.NoSuchEntityException; import javax.ejb.NoSuchObjectLocalException; import javax.ejb.TransactionRolledbackLocalException; import javax.ejb.TimedObject; import javax.ejb.Timer; import org.jboss.invocation.Invocation; import org.jboss.invocation.InvocationType; import org.jboss.tm.TxUtils; /** * A common superclass for the transaction interceptors. * <p/> * The only important method in this class is invokeNext which is incharge * of invoking the next interceptor and if an exception is thrown, it must * follow the rules in the EJB 2.0 specification section 18.3. These * rules specify if the transaction is rolled back and what exception * should be thrown. * * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a> * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a> * @version $Revision: 81030 $ */ abstract class AbstractTxInterceptor extends AbstractInterceptor { /** A reference to {@link javax.ejb.TimedObject#ejbTimeout}. */ protected static final Method ejbTimeout; static { try { ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class}); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * Local reference to the container's TransactionManager. */ protected TransactionManager tm; public void create() throws Exception { super.create(); tm = getContainer().getTransactionManager(); } /** * This method calls the next interceptor in the chain. * <p/> * All Throwables are caught and divided into two groups: application * exceptions and system exceptions. Application exception are simply * rethrown. System exceptions result in the transaction being marked * for rollback only. If the transaction was not started by the container * (i.e., it was inherited from the client) the system exception is wrapped * in a TransactionRolledBack[Local]Exception. * * @param invocation The <code>Invocation</code> of this call. * @param inheritedTx If <code>true</code> the transaction has just been started * in this interceptor. * @throws Exception if an exception occures in the interceptor chain. The * actual exception throw is governed by the rules in the EJB 2.0 * specification section 18.3 */ protected Object invokeNext(Invocation invocation, boolean inheritedTx) throws Exception { InvocationType type = invocation.getType(); try { if (type == InvocationType.REMOTE || type == InvocationType.LOCAL || type == InvocationType.SERVICE_ENDPOINT) { // register the Timer with the transaction if (ejbTimeout.equals(invocation.getMethod())) registerTimer(invocation); return getNext().invoke(invocation); } else { return getNext().invokeHome(invocation); } } catch (Throwable e) { // if this is an ApplicationException, just rethrow it if (e instanceof Exception && !(e instanceof RuntimeException || e instanceof RemoteException)) { throw (Exception) e; } // attempt to rollback the transaction Transaction tx = invocation.getTransaction(); if (tx == null) { // Look for a hanging active user transaction that we should mark rollback try { tx = tm.getTransaction(); if (TxUtils.isActive(tx) == false) tx = null; } catch (Exception ex) { log.warn("Unable to determine transaction context", ex); } } if (tx != null) { 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); } } // is this a local invocation boolean isLocal = type == InvocationType.LOCAL || type == InvocationType.LOCALHOME; // if this transaction was NOT inherited from the caller we simply // rethrow the exception, and LogInterceptor will handle // all exception conversions. if (!inheritedTx) { if (e instanceof Exception) { throw (Exception) e; } if (e instanceof Error) { throw (Error) e; } // we have some funky throwable, wrap it if (isLocal) { String msg = formatException("Unexpected Throwable", e); throw new EJBException(msg); } else { ServerException ex = new ServerException("Unexpected Throwable"); ex.detail = e; throw ex; } } // to be nice we coerce the execption to an interface friendly type // before wrapping it with a transaction rolled back exception Throwable cause; if (e instanceof NoSuchEntityException) { NoSuchEntityException nsee = (NoSuchEntityException) e; if (isLocal) { cause = new NoSuchObjectLocalException(nsee.getMessage(), nsee.getCausedByException()); } else { cause = new NoSuchObjectException(nsee.getMessage()); // set the detil of the exception ((NoSuchObjectException) cause).detail = nsee.getCausedByException(); } } else { if (isLocal) { // local transaction rolled back exception can only wrap // an exception so we create an EJBException for the cause if (e instanceof Exception) { cause = e; } else if (e instanceof Error) { String msg = formatException("Unexpected Error", e); cause = new EJBException(msg); } else { String msg = formatException("Unexpected Throwable", e); cause = new EJBException(msg); } } else { // remote transaction rolled back exception can wrap // any throwable so we are ok cause = e; } } // We inherited tx: Tell caller we marked for rollback only. if (isLocal) { if (cause instanceof TransactionRolledbackLocalException) { throw (TransactionRolledbackLocalException) cause; } else { throw new TransactionRolledbackLocalException(cause.getMessage(), (Exception) cause); } } else { if (cause instanceof TransactionRolledbackException) { throw (TransactionRolledbackException) cause; } else { TransactionRolledbackException ex = new TransactionRolledbackException(cause.getMessage()); ex.detail = cause; throw ex; } } } } private void registerTimer(Invocation invocation) throws RollbackException, SystemException { Timer timer = (Timer) invocation.getArguments()[0]; Transaction transaction = invocation.getTransaction(); if (transaction != null && timer instanceof Synchronization) transaction.registerSynchronization((Synchronization) timer); } private String formatException(String msg, Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); if (msg != null) { pw.println(msg); } t.printStackTrace(pw); return sw.toString(); } }