/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., 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.as.txn.deployment; import java.util.Collections; import java.util.Map; import java.util.Set; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import org.jboss.as.server.deployment.SetupAction; import org.jboss.as.txn.logging.TransactionLogger; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; /** * Setup action that makes sure that no transactions leak from EE requests * * @author Stuart Douglas */ public class TransactionRollbackSetupAction implements SetupAction, Service<TransactionRollbackSetupAction> { private static final ThreadLocal<Holder> depth = new ThreadLocal<Holder>(); private final InjectedValue<TransactionManager> transactionManager = new InjectedValue<TransactionManager>(); private final ServiceName serviceName; public TransactionRollbackSetupAction(final ServiceName serviceName) { this.serviceName = serviceName; } @Override public void setup(final Map<String, Object> properties) { changeDepth(1); } @Override public void teardown(final Map<String, Object> properties) { if (changeDepth(-1)) { checkTransactionStatus(); } } @Override public int priority() { return 0; } @Override public Set<ServiceName> dependencies() { return Collections.singleton(serviceName); } @Override public void start(final StartContext context) throws StartException { } @Override public void stop(final StopContext context) { } @Override public TransactionRollbackSetupAction getValue() throws IllegalStateException, IllegalArgumentException { return this; } public InjectedValue<TransactionManager> getTransactionManager() { return transactionManager; } private boolean changeDepth(int increment) { Holder holder = depth.get(); if (holder == null) { //if there is a transaction active initially we just track the depth //and don't actually close it, because we don't 'own' the transaction //this can happen when running async listeners outside the context of a request holder = new Holder(); try { final TransactionManager tm = transactionManager.getOptionalValue(); if (tm != null) { holder.actuallyCleanUp = !isTransactionActive(tm, tm.getStatus()); } depth.set(holder); } catch (Exception e) { TransactionLogger.ROOT_LOGGER.unableToGetTransactionStatus(e); } } holder.depth += increment; if (holder.depth == 0) { depth.set(null); return holder.actuallyCleanUp; } return false; } private void checkTransactionStatus() { try { final TransactionManager tm = transactionManager.getOptionalValue(); if (tm == null) { return; } final int status = tm.getStatus(); final boolean active = isTransactionActive(tm, status); if (active) { try { TransactionLogger.ROOT_LOGGER.transactionStillOpen(status); tm.rollback(); } catch (Exception ex) { TransactionLogger.ROOT_LOGGER.unableToRollBack(ex); } } } catch (Exception e) { TransactionLogger.ROOT_LOGGER.unableToGetTransactionStatus(e); } } private boolean isTransactionActive(TransactionManager tm, int status) throws SystemException { switch (status) { case Status.STATUS_ACTIVE: case Status.STATUS_COMMITTING: case Status.STATUS_MARKED_ROLLBACK: case Status.STATUS_PREPARING: case Status.STATUS_ROLLING_BACK: case Status.STATUS_ROLLEDBACK: case Status.STATUS_PREPARED: return true; } return false; } private static class Holder { int depth; boolean actuallyCleanUp = true; } }