/* * JBoss, Home of Professional Open Source. * Copyright 2006, 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.util.Hashtable; import java.rmi.RemoteException; import javax.transaction.Transaction; import javax.transaction.Status; import javax.transaction.SystemException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.Reference; import javax.naming.RefAddr; import javax.naming.spi.ObjectFactory; import org.jboss.ejb.EnterpriseContext; import org.jboss.ejb.AllowedOperationsAssociation; import org.jboss.invocation.Invocation; import org.jboss.tm.TxUtils; /** * A common superclass for the BMT transaction interceptors. * * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a> * @version $Revision: 73911 $ */ abstract class AbstractTxInterceptorBMT extends AbstractTxInterceptor { // Attributes ---------------------------------------------------- /** * This associates the thread to the UserTransaction. * * It is used to redirect lookups on java:comp/UserTransaction to * the <code>getUserTransaction()</code> method of the context. */ private ThreadLocal userTransaction = new ThreadLocal(); /** * If <code>false</code>, transactions may live across bean instance * invocations, otherwise the bean instance should terminate any * transaction before returning from the invocation. * This attribute defaults to <code>true</code>. */ protected boolean stateless = true; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- // Public -------------------------------------------------------- // Interceptor implementation -------------------------------------- public void create() throws Exception { // Do initialization in superclass. super.create(); // bind java:comp/UserTransaction RefAddr refAddr = new RefAddr("userTransaction") { /** This is never really serialized */ private static final long serialVersionUID = -8228448967597474960L; public Object getContent() { return userTransaction; } }; Reference ref = new Reference("javax.transaction.UserTransaction", refAddr, new UserTxFactory().getClass() .getName(), null); ((Context) new InitialContext().lookup("java:comp/")).bind("UserTransaction", ref); } public void stop() { // bind java:comp/UserTransaction try { ((Context) new InitialContext().lookup("java:comp/")).unbind("UserTransaction"); } catch (Exception e) { //ignore } } // Protected ---------------------------------------------------- /* * This method calls the next interceptor in the chain. * * It handles the suspension of any client transaction, and the * association of the calling thread with the instance transaction. * And it takes care that any lookup of * <code>java:comp/UserTransaction</code> will return the right * UserTransaction for the bean instance. * * @param remoteInvocation If <code>true</code> this is an invocation * of a method in the remote interface, otherwise * it is an invocation of a method in the home * interface. * @param mi The <code>Invocation</code> of this call. */ protected Object invokeNext(Invocation mi) throws Exception { // Save the transaction that comes with the MI Transaction oldTransaction = mi.getTransaction(); // Get old threadlocal: It may be non-null if one BMT bean does a local // call to another. Object oldUserTx = userTransaction.get(); // Suspend any transaction associated with the thread: It may be // non-null on optimized local calls. Transaction threadTx = tm.suspend(); try { EnterpriseContext ctx = ((EnterpriseContext) mi.getEnterpriseContext()); // Set the threadlocal to the userTransaction of the instance try { AllowedOperationsAssociation.pushInMethodFlag(IN_INTERCEPTOR_METHOD); userTransaction.set(ctx.getEJBContext().getUserTransaction()); } finally { AllowedOperationsAssociation.popInMethodFlag(); } // Get the bean instance transaction Transaction beanTx = ctx.getTransaction(); // Resume the bean instance transaction // only if it not null, some TMs can't resume(null), e.g. Tyrex if (beanTx != null) tm.resume(beanTx); // Let the MI know about our new transaction mi.setTransaction(beanTx); try { // Let the superclass call next interceptor and do the exception // handling return super.invokeNext(mi, false); } finally { try { if (stateless) checkStatelessDone(); else checkBadStateful(); } finally { tm.suspend(); } } } finally { // Reset threadlocal to its old value userTransaction.set(oldUserTx); // Restore old MI transaction // OSH: Why ??? mi.setTransaction(oldTransaction); // If we had a Tx associated with the thread reassociate if (threadTx != null) tm.resume(threadTx); } } private void checkStatelessDone() throws RemoteException { int status = Status.STATUS_NO_TRANSACTION; try { status = tm.getStatus(); } catch (SystemException ex) { log.error("Failed to get status", ex); } try { switch (status) { case Status.STATUS_ACTIVE : case Status.STATUS_COMMITTING : case Status.STATUS_MARKED_ROLLBACK : case Status.STATUS_PREPARING : case Status.STATUS_ROLLING_BACK : try { tm.rollback(); } catch (Exception ex) { log.error("Failed to rollback", ex); } // fall through... case Status.STATUS_PREPARED : String msg = "Application error: BMT stateless bean " + container.getBeanMetaData().getEjbName() + " should complete transactions before" + " returning (ejb1.1 spec, 11.6.1)"; log.error(msg); // the instance interceptor will discard the instance throw new RemoteException(msg); } } finally { Transaction tx = null; try { tx = tm.suspend(); } catch (SystemException ex) { log.error("Failed to suspend transaction", ex); } if (tx != null) { String msg = "Application error: BMT stateless bean " + container.getBeanMetaData().getEjbName() + " should complete transactions before " + " returning (ejb1.1 spec, 11.6.1), suspended tx=" + tx ; log.error(msg); throw new RemoteException(msg); } } } private void checkBadStateful() throws RemoteException { int status = Status.STATUS_NO_TRANSACTION; try { status = tm.getStatus(); } catch (SystemException ex) { log.error("Failed to get status", ex); } switch (status) { case Status.STATUS_COMMITTING : case Status.STATUS_MARKED_ROLLBACK : case Status.STATUS_PREPARING : case Status.STATUS_ROLLING_BACK : try { tm.rollback(); } catch (Exception ex) { log.error("Failed to rollback", ex); } String msg = "BMT stateful bean '" + container.getBeanMetaData().getEjbName() + "' did not complete user transaction properly status=" + TxUtils.getStatusAsString(status); log.error(msg); } } // Inner classes ------------------------------------------------- public static class UserTxFactory implements ObjectFactory { public Object getObjectInstance(Object ref, Name name, Context nameCtx, Hashtable environment) throws Exception { // The ref is a list with only one RefAddr ... RefAddr refAddr = ((Reference) ref).get(0); // ... whose content is the threadlocal ThreadLocal threadLocal = (ThreadLocal) refAddr.getContent(); // The threadlocal holds the right UserTransaction return threadLocal.get(); } } }