/* * GNU Lesser General Public License * * Beanlet - JSE Application Container. * Copyright (C) 2006 Leon van Zantvoort * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Leon van Zantvoort * 243 Acalanes Drive #11 * Sunnyvale, CA 94086 * USA * * zantvoort@users.sourceforge.net * http://beanlet.org */ package org.beanlet.transaction.impl; import java.util.concurrent.atomic.AtomicReference; import org.beanlet.BeanletStateException; import static org.beanlet.WiringMode.*; import static org.beanlet.transaction.impl.TransactionStatus.*; import static java.util.logging.Level.*; import java.util.IdentityHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.jargo.InvocationContext; import org.jargo.InvocationInterceptor; import org.beanlet.transaction.TransactionAttributeType; import org.beanlet.transaction.TransactionSynchronization; import org.jargo.ComponentConfiguration; /** * <p>Intercepts business methods that are marked with the * {@code TransactionAttribute} annotation. This interceptor interacts with the * transaction manager according to the transaction attribute type specified * by this annotation.</p> * * @author Leon van Zantvoort */ public final class TransactionInvocationInterceptor implements InvocationInterceptor { private static final Logger logger = Logger.getLogger( TransactionInvocationInterceptor.class.getName()); private final IdentityHashMap<Transaction, Object> registrations = new IdentityHashMap<Transaction, Object>(); private static final Lock lock = new ReentrantLock(true); private static final Object DUMMY = new Object(); private final ComponentConfiguration configuration; private final TransactionManager transactionManager; private final TransactionAttributeType type; private final int timeout; private final TransactionSynchronization synchronization; public TransactionInvocationInterceptor( ComponentConfiguration configuration, TransactionManager transactionManager, TransactionAttributeType type, int timeout, TransactionSynchronization synchronization) { this.configuration = configuration; this.transactionManager = transactionManager; this.type = type; this.timeout = timeout; this.synchronization = synchronization; } private void cleanRegistration(Transaction transaction) { lock.lock(); try { registrations.remove(transaction); } finally { lock.unlock(); } } private Transaction preTransaction(InvocationContext ctx) throws BeanletStateException { try { final Transaction transaction; transactionManager.setTransactionTimeout(timeout); TransactionStatus status = TransactionStatus.toEnum( transactionManager.getStatus()); switch (type) { case NEVER: if (transactionManager.getTransaction() != null || status != NO_TRANSACTION) { throw new BeanletStateException("Active transaction not " + "allowed for attribute NEVER."); } if (synchronization != null) { throw new BeanletStateException("TransactionSynchronization " + "callback prohibited for transaction attribute NEVER."); } transaction = null; break; case NOT_SUPPORTED: if (transactionManager.getTransaction() != null || status != NO_TRANSACTION) { transaction = transactionManager.suspend(); log("Suspended transaction " + transaction + ".", FINEST); } else { transaction = null; } if (synchronization != null) { throw new BeanletStateException("TransactionSynchronization " + "callback prohibited for transaction attribute NOT_SUPPORTED."); } break; case MANDATORY: if (transactionManager.getTransaction() == null || status == NO_TRANSACTION) { throw new BeanletStateException("Active transaction " + "required for attribute MANDATORY."); } else { if (status == MARKED_ROLLBACK) { log("Transaction is marked for rollback only.", INFO); } else { // Don't register if registration is fruitless b/c of status. registerSynchronization(ctx); } transaction = null; } break; case SUPPORTS: if (status == MARKED_ROLLBACK) { log("Transaction is marked for rollback only.", INFO); } // Note that EJB does not allow synchronization for attribute SUPPORTS. if (status == ACTIVE) { // Don't register if registration is fruitless b/c of status. registerSynchronization(ctx); } else { log("Skipping synchronization registration. " + "Transaction is not active: " + status + ".", FINEST); } transaction = null; break; case REQUIRED: if (transactionManager.getTransaction() == null || status == NO_TRANSACTION) { transactionManager.begin(); transaction = transactionManager.getTransaction(); log("Started transaction " + transaction + ".", FINEST); registerSynchronization(ctx); } else { if (status == MARKED_ROLLBACK) { log("Transaction is marked for rollback only.", INFO); } else { // Don't register if registration is fruitless b/c of status. registerSynchronization(ctx); } transaction = null; } break; case REQUIRES_NEW: if (transactionManager.getTransaction() != null || status != NO_TRANSACTION) { transaction = transactionManager.suspend(); log("Suspended transaction " + transaction + ".", FINEST); } else { transaction = null; } transactionManager.begin(); log("Started transaction " + transactionManager.getTransaction() + ".", FINEST); registerSynchronization(ctx); break; default: transaction = null; assert false; } return transaction; } catch (BeanletStateException e) { throw e; } catch (Exception e) { throw new BeanletStateException(configuration. getComponentName(), e); } } private void postTransaction(Transaction transaction, boolean commit) throws BeanletStateException{ if (transactionManager != null) { try { TransactionStatus status = TransactionStatus.toEnum( transactionManager.getStatus()); log("Transaction status: " + status + ".", FINEST); if (status == MARKED_ROLLBACK || status == ROLLEDBACK) { commit = false; } switch (type) { case NEVER: break; case NOT_SUPPORTED: if (transaction != null) { log("Resuming transaction " + transaction + ".", FINEST); transactionManager.resume(transaction); } break; case MANDATORY: break; case SUPPORTS: break; case REQUIRED: if (transaction != null) { try { if (commit) { log("Committing transaction " + transaction + ".", FINEST); transactionManager.commit(); } else { log("Rolling back transaction " + transaction + ".", FINEST); transactionManager.rollback(); } } catch (IllegalStateException e) { // Ignore. } finally { // Disabled as this assertion might fail on application shutdown. // assert transactionManager.getTransaction() == null || // TransactionStatus.toEnum(transactionManager.getStatus()) == NO_TRANSACTION; } } break; case REQUIRES_NEW: try { if (commit) { log("Committing transaction " + transactionManager.getTransaction() + ".", FINEST); transactionManager.commit(); } else { log("Rolling back transaction " + transactionManager.getTransaction() + ".", FINEST); transactionManager.rollback(); } } catch (IllegalStateException e) { // Ignore. } finally { // Disabled as this assertion might fail on application shutdown. // assert transactionManager.getTransaction() == null || // TransactionStatus.toEnum(transactionManager.getStatus()) == NO_TRANSACTION; } if (transaction != null) { log("Resuming transaction " + transaction + ".", FINEST); transactionManager.resume(transaction); } break; default: assert false; } } catch (BeanletStateException e) { throw e; } catch (Exception e) { throw new BeanletStateException(configuration. getComponentName(), e); } } } public boolean isLifecycleInterceptor() { return false; } public Object intercept(InvocationContext ctx) throws Exception { Transaction transaction = null; boolean commit = true; try { if (transactionManager != null) { log("Intercepting " + ctx.getInvocation().getMethod().getName() + " with attribute " + type + ".", FINEST); transaction = preTransaction(ctx); } else { transaction = null; } Object result = ctx.proceed(); return result; } catch (RuntimeException e) { commit = false; throw e; } finally { if (transactionManager != null) { postTransaction(transaction, commit); } } } private void registerSynchronization(final InvocationContext ctx) throws IllegalStateException, RollbackException, SystemException { assert transactionManager != null; final Transaction transaction = transactionManager.getTransaction(); assert transaction != null; lock.lock(); try { if (registrations.put(transaction, DUMMY) == null) { log("Registering transaction: " + transaction + ".", FINEST); boolean registered = false; TransactionLocalDelegateImpl.begin(transaction); try { try { if (synchronization != null) { synchronization.afterBegin(); } } finally { transaction.registerSynchronization(new Synchronization() { // Hold a strong reference to the component to prevent it from being destroyed. AtomicReference<?> reference = new AtomicReference<Object>( ctx.getComponentContext().reference()); public void beforeCompletion() { if (synchronization != null) { synchronization.beforeCompletion(); } } public void afterCompletion(int i) { try { if (synchronization != null) { synchronization.afterCompletion(i == Status.STATUS_COMMITTED); } } finally { reference.set(null); try { log("Deregistering transaction on" + " completion: " + transaction + ".", FINEST); cleanRegistration(transaction); } finally { TransactionLocalDelegateImpl.commit(transaction); } } } }); registered = true; } } catch (Throwable t) { if (logger != null) { logger.log(WARNING, "Failed to register " + "synchronization for transaction: " + transaction + ".", t); } try { throw t; } catch (IllegalStateException e) { throw e; } catch (RollbackException e) { // Note that in case of Jotm, the // registerSynchronization method does not fail // atomically. If this exception is thrown, // the synchronization methods stil seem to be executed. throw e; } catch (SystemException e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { assert false; throw new RuntimeException(e); } catch (Error e) { throw e; } catch (Throwable e) { assert false : e; throw new RuntimeException(e); } } finally { if (!registered) { if (logger != null) { logger.finest("Deregistering transaction because " + "synchronization registration failed: " + transaction + "."); } cleanRegistration(transaction); } } } } finally { lock.unlock(); } } private void log(String msg, Level level) { if (logger != null && logger.isLoggable(level)) { logger.log(level, msg); } } }