/* * The contents of this file are subject to the Open Software License * Version 3.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.rosenlaw.com/OSL3.0.htm * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * This file is an original work developed by Netymon Pty Ltd * (http://www.netymon.com, mailto:mail@netymon.com). Portions created * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd. * All Rights Reserved. * * Work deriving from MulgaraTransactionManager Copyright (c) 2007 Topaz * Foundation under contract by Andrae Muys (mailto:andrae@netymon.com). */ package org.mulgara.resolver; // Java2 packages import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; // Third party packages import org.apache.log4j.Logger; // Local packages import org.mulgara.query.MulgaraTransactionException; import org.mulgara.transaction.TransactionManagerFactory; import org.mulgara.util.StackTrace; /** * Implements the internal transaction controls offered by Session. * * @created 2006-10-06 * * @author <a href="mailto:andrae@netymon.com">Andrae Muys</a> * * @company <A href="mailto:mail@netymon.com">Netymon Pty Ltd</A> * * @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a> * * @licence Open Software License v3.0</a> */ public class MulgaraInternalTransactionFactory extends MulgaraTransactionFactory { /** Logger. */ private static final Logger logger = Logger.getLogger(MulgaraInternalTransactionFactory.class.getName()); /** Flag indicating current explicit-transaction has been rolledback. */ private boolean isFailed; /** The reason for the failure if {@link #isFailed} is true. */ private Throwable failureCause; /** Map of threads to active transactions. */ private final Map<Thread, MulgaraTransaction> activeTransactions; /** Are we in auto-commit mode. */ public boolean autoCommit; /** All uncompleted transactions (may be more than 1 because of unclosed answers) */ public final Set<MulgaraTransaction> transactions; /** Currently associated explicit transaction */ public MulgaraInternalTransaction explicitXA; private final TransactionManager transactionManager; public MulgaraInternalTransactionFactory(DatabaseSession session, MulgaraTransactionManager manager, TransactionManagerFactory transactionManagerFactory) { super(session, manager); this.isFailed = false; this.failureCause = null; this.activeTransactions = new HashMap<Thread, MulgaraTransaction>(); this.autoCommit = true; this.transactions = new HashSet<MulgaraTransaction>(); this.explicitXA = null; this.transactionManager = transactionManagerFactory.newTransactionManager(); try { /* "disable" the TM's timeout because we implement timeouts ourselves; we also don't set * it to our timeout because that can interfere with txn cleanup if the TM's timeout has * fired by causing rollback or commit to fail. */ this.transactionManager.setTransactionTimeout(Integer.MAX_VALUE); } catch (SystemException es) { logger.warn("Unable to disable transaction timeout on jta tm", es); } } public MulgaraTransaction getTransaction(boolean write) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { if (explicitXA != null) { return explicitXA; } try { MulgaraInternalTransaction transaction; if (write) { manager.obtainWriteLock(session); try { assert writeTransaction == null; writeTransaction = transaction = new MulgaraInternalTransaction(this, session.newOperationContext(true)); } catch (Throwable th) { manager.releaseWriteLock(session); throw new MulgaraTransactionException("Error creating write transaction", th); } } else { transaction = new MulgaraInternalTransaction(this, session.newOperationContext(false)); } transactions.add(transaction); transactionCreated(transaction); return transaction; } catch (MulgaraTransactionException em) { throw em; } catch (Exception e) { throw new MulgaraTransactionException("Error creating transaction", e); } } finally { releaseMutex(); } } public Set<MulgaraTransaction> getTransactions() { return transactions; } public void commit() throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { if (isFailed) { if (failureCause != null) throw new MulgaraTransactionException("Attempting to commit failed session", failureCause); else throw new MulgaraTransactionException("Attempting to commit failed session"); } else if (!manager.isHoldingWriteLock(session)) { throw new MulgaraTransactionException( "Attempting to commit while not the current writing transaction"); } manager.reserveWriteLock(session); try { setAutoCommit(true); setAutoCommit(false); } finally { manager.releaseReserve(session); } } finally { releaseMutex(); } } /** * This is an explicit, user-specified rollback. * * This needs to be distinguished from an implicit rollback triggered by failure. */ public void rollback() throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { if (manager.isHoldingWriteLock(session)) { manager.reserveWriteLock(session); try { try { writeTransaction.execute(new TransactionOperation() { public void execute() throws MulgaraTransactionException { writeTransaction.dereference(); ((MulgaraInternalTransaction)writeTransaction).explicitRollback(); } }); // FIXME: Should be checking status here, not writelock. if (manager.isHoldingWriteLock(session)) { // transaction referenced by something - need to explicitly end it. writeTransaction.abortTransaction("Rollback failed", new MulgaraTransactionException("Rollback failed to terminate write transaction")); } } finally { explicitXA = null; setAutoCommit(false); } } finally { manager.releaseReserve(session); } } else if (isFailed) { explicitXA = null; isFailed = false; failureCause = null; setAutoCommit(false); } else { throw new MulgaraTransactionException( "Attempt to rollback while not in the current writing transaction"); } } finally { releaseMutex(); } } public void setAutoCommit(boolean autoCommit) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { if (manager.isHoldingWriteLock(session) && isFailed) { writeTransaction.abortTransaction("Session failed and still holding writeLock", new MulgaraTransactionException("Failed Session in setAutoCommit")); } if (manager.isHoldingWriteLock(session) || isFailed) { if (autoCommit) { this.autoCommit = true; this.explicitXA = null; // AutoCommit off -> on === branch on current state of transaction. if (manager.isHoldingWriteLock(session)) { // Within active transaction - commit and finalise. try { writeTransaction.execute(new TransactionOperation() { public void execute() throws MulgaraTransactionException { writeTransaction.dereference(); ((MulgaraInternalTransaction)writeTransaction).commitTransaction(); } }); } finally { // This should have been cleaned up by the commit above, but if it // hasn't then if we don't release here we could deadlock the // transaction manager if (manager.isHoldingWriteLock(session)) { manager.releaseWriteLock(session); } } } else if (isFailed) { // Within failed transaction - cleanup. isFailed = false; failureCause = null; } } else { if (!manager.isHoldingWriteLock(session)) { throw new MulgaraTransactionException("Attempting set auto-commit false in failed session"); } else { // AutoCommit off -> off === no-op. Log info. if (logger.isDebugEnabled()) { logger.debug("Attempt to set autocommit false twice\n" + new StackTrace()); } } } } else { explicitXA = null; if (autoCommit) { // AutoCommit on -> on === no-op. Log info. if (logger.isDebugEnabled()) logger.debug("Attempting to set autocommit true without setting it false"); } else { // AutoCommit on -> off == Start new transaction. getTransaction(true); // Set's writeTransaction. writeTransaction.reference(); explicitXA = (MulgaraInternalTransaction) writeTransaction; this.autoCommit = false; } } } finally { releaseMutex(); } } // // Transaction livecycle callbacks. // public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { try { if (logger.isDebugEnabled()) logger.debug("Beginning Transaction"); if (activeTransactions.get(Thread.currentThread()) != null) { throw new MulgaraTransactionException( "Attempt to start transaction in thread with exiting active transaction."); } else if (activeTransactions.containsValue(transaction)) { throw new MulgaraTransactionException("Attempt to start transaction twice"); } transactionManager.begin(); Transaction jtaTrans = transactionManager.getTransaction(); activeTransactions.put(Thread.currentThread(), transaction); return jtaTrans; } catch (Exception e) { throw new MulgaraTransactionException("Transaction Begin Failed", e); } } finally { releaseMutex(); } } public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { if (activeTransactions.get(Thread.currentThread()) != null) { throw new MulgaraTransactionException( "Attempt to resume transaction in already activated thread"); } else if (activeTransactions.containsValue(transaction)) { throw new MulgaraTransactionException("Attempt to resume active transaction"); } try { transactionManager.resume(jtaXA); activeTransactions.put(Thread.currentThread(), transaction); } catch (Exception e) { throw new MulgaraTransactionException("Resume Failed", e); } } finally { releaseMutex(); } } public Transaction transactionSuspended(MulgaraTransaction transaction) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { try { if (transaction != activeTransactions.get(Thread.currentThread())) { throw new MulgaraTransactionException( "Attempt to suspend transaction from outside thread"); } if (autoCommit && transaction == writeTransaction) { logger.error("Attempt to suspend write transaction without setting AutoCommit Off"); throw new MulgaraTransactionException( "Attempt to suspend write transaction without setting AutoCommit Off"); } Transaction xa = transactionManager.suspend(); activeTransactions.remove(Thread.currentThread()); return xa; } catch (Throwable th) { logger.error("Attempt to suspend failed", th); try { transactionManager.setRollbackOnly(); } catch (Throwable t) { logger.error("Attempt to setRollbackOnly() failed", t); } throw new MulgaraTransactionException("Suspend failed", th); } } finally { releaseMutex(); } } public void closingSession() throws MulgaraTransactionException { acquireMutexWithInterrupt(0, MulgaraTransactionException.class); try { try { super.closingSession(); } finally { transactions.clear(); } } finally { releaseMutex(); } } public void transactionComplete(MulgaraTransaction transaction) throws MulgaraTransactionException { acquireMutex(0, MulgaraTransactionException.class); try { super.transactionComplete(transaction); logger.debug("Transaction Complete"); if (transaction == writeTransaction) { if (manager.isHoldingWriteLock(session)) { manager.releaseWriteLock(session); writeTransaction = null; } } transactions.remove(transaction); activeTransactions.remove(Thread.currentThread()); } finally { releaseMutex(); } } public void transactionAborted(MulgaraTransaction transaction, Throwable cause) { acquireMutex(0, RuntimeException.class); try { try { // Make sure this cleans up the transaction metadata - this transaction is DEAD! if (!autoCommit && transaction == writeTransaction) { isFailed = true; failureCause = cause; } transactionComplete(transaction); } catch (Throwable th) { // FIXME: This should probably abort the entire server after logging the error! logger.error("Error managing transaction abort", th); } } finally { releaseMutex(); } } }