/* * JBoss, Home of Professional Open Source. * Copyright 2011, 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.capedwarf.datastore; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Stack; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import javax.transaction.InvalidTransactionException; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import com.google.appengine.api.datastore.DatastoreFailureException; import com.google.appengine.api.datastore.Transaction; import com.google.appengine.api.datastore.TransactionOptions; import org.jboss.capedwarf.common.app.Application; import org.jboss.capedwarf.common.async.Wrappers; import org.jboss.capedwarf.common.threads.ExecutorFactory; import org.jboss.capedwarf.common.tx.TxUtils; /** * JBoss GAE transaction. * * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a> */ final class CapedwarfTransaction implements Transaction { private static final Logger log = Logger.getLogger(CapedwarfTransaction.class.getName()); private static final int asyncRetryCount = Integer.parseInt(System.getProperty("jboss.capedwarf.tx.asyncRetryCount", "3")); private final static TransactionManager tm = TxUtils.getTransactionManager(); private final static ThreadLocal<Stack<CapedwarfTransaction>> current = new ThreadLocal<Stack<CapedwarfTransaction>>(); private final TransactionOptions options; private ThreadLocal<javax.transaction.Transaction> transactions = new ThreadLocal<javax.transaction.Transaction>(); private String txId; private CapedwarfTransaction(TransactionOptions options) { this.options = options; } static Transaction newTransaction(TransactionOptions options) { Stack<CapedwarfTransaction> stack = current.get(); if (stack == null) { stack = new Stack<CapedwarfTransaction>(); current.set(stack); } else { CapedwarfTransaction jt = stack.peek(); jt.suspend(); // suspend existing } try { tm.begin(); // being new } catch (Exception e) { if (stack.isEmpty()) current.remove(); else stack.peek().resume(true); // resume back previous throw new DatastoreFailureException("Cannot begin tx.", e); } final CapedwarfTransaction tx = new CapedwarfTransaction(options); stack.push(tx); return tx; } javax.transaction.Transaction getTransaction() { return transactions.get(); } static javax.transaction.Transaction getTx() { try { return tm.getTransaction(); } catch (SystemException e) { throw new DatastoreFailureException("Cannot obtain tx.", e); } } static TransactionWrapper getTxWrapper(Transaction tx) { if (tx != null) { return new TransactionWrapper(getTx(), unwrap(tx)); } else { return null; } } static CapedwarfTransaction unwrap(Transaction tx) { if (tx instanceof CapedwarfTransaction) { return CapedwarfTransaction.class.cast(tx); } else { return currentTransaction(); } } static boolean isTxActive() { return (getTxStatus() == Status.STATUS_ACTIVE); } static int getTxStatus() { try { return tm.getStatus(); } catch (SystemException e) { throw new RuntimeException(e); } } static void attach(TransactionWrapper tw) { if (tw == null) return; tw.setPrevious(suspendTx()); resumeTx(tw.getDelegate()); final CapedwarfTransaction tx = tw.getTransaction(); Stack<CapedwarfTransaction> stack = current.get(); if (stack == null) { stack = new Stack<CapedwarfTransaction>(); current.set(stack); } stack.push(tx); } static void detach(TransactionWrapper tw) { if (tw == null) return; final Stack<CapedwarfTransaction> stack = current.get(); if (stack == null || stack.isEmpty()) throw new IllegalStateException("Illegal call to cleanup - stack should exist"); stack.pop(); if (stack.isEmpty()) { current.remove(); } suspendTx(); javax.transaction.Transaction previous = tw.getPrevious(); if (previous != null) { resumeTx(previous); } } static javax.transaction.Transaction suspendTx() { try { return tm.suspend(); } catch (SystemException e) { throw new DatastoreFailureException("Cannot suspend tx.", e); } } static void resumeTx(javax.transaction.Transaction transaction) { try { tm.resume(transaction); } catch (InvalidTransactionException | SystemException e) { throw new DatastoreFailureException("Cannot resume tx.", e); } } private void checkIfCurrent() { if (getTransaction() != null) throw new IllegalStateException("Not current transaction -- other tx in progress!"); } private void suspend() { try { transactions.set(tm.suspend()); } catch (SystemException e) { throw new DatastoreFailureException("Cannot suspend tx.", e); } } private void resume(boolean ignoreException) { javax.transaction.Transaction t = getTransaction(); try { transactions.remove(); // cleanup tm.resume(t); } catch (Exception e) { if (ignoreException == false) throw new DatastoreFailureException("Cannot resume tx.", e); else log.log(Level.SEVERE, "Failed to resume previous tx: " + t, e); } } static CapedwarfTransaction currentTransaction() { final Stack<CapedwarfTransaction> stack = current.get(); return (stack != null) ? stack.peek() : null; } static TransactionOptions currentTransactionOptions() { final Stack<CapedwarfTransaction> stack = current.get(); return (stack != null) ? stack.peek().options : null; } static boolean isXG() { final TransactionOptions to = currentTransactionOptions(); return (to != null && to.isXG()); } private CapedwarfTransaction cleanup(boolean resume) { final Stack<CapedwarfTransaction> stack = current.get(); if (stack == null) throw new IllegalStateException("Illegal call to cleanup - stack should exist"); final CapedwarfTransaction jt = stack.peek(); if (jt != this) throw new IllegalArgumentException("Cannot cleanup non-current tx!"); stack.pop(); // remove current CapedwarfTransaction previous = null; if (stack.isEmpty()) { current.remove(); } else { previous = stack.peek(); if (resume) { previous.resume(false); // resume previous } } return previous; } @SuppressWarnings({"unchecked"}) static Collection<Transaction> getTransactions() { Stack stack = current.get(); return (stack != null) ? Collections.unmodifiableCollection(stack) : Collections.<Transaction>emptyList(); } public void commit() { checkIfCurrent(); try { finish(getTx(), new Callable<Void>() { public Void call() throws Exception { tm.commit(); return null; } }); } catch (Exception e) { throw new DatastoreFailureException("Cannot commit tx.", e); } finally { cleanup(true); } } public Future<Void> commitAsync() { checkIfCurrent(); final CapedwarfTransaction previous = cleanup(false); final javax.transaction.Transaction tx = resumeAsync(previous); if (tx == null) { throw new IllegalArgumentException("No Tx -- should exist?!"); } final Callable<Void> callable = new Callable<Void>() { public Void call() throws Exception { resumeTx(tx); try { return finish(tx, new Callable<Void>() { public Void call() throws Exception { tx.commit(); return null; } }); } finally { suspendTx(); } } }; return ExecutorFactory.wrap(Wrappers.wrap(callable)); } public void rollback() { checkIfCurrent(); try { finish(getTx(), new Callable<Void>() { public Void call() throws Exception { tm.rollback(); return null; } }); } catch (Exception e) { throw new DatastoreFailureException("Cannot rollback tx.", e); } finally { cleanup(true); } } public Future<Void> rollbackAsync() { checkIfCurrent(); final CapedwarfTransaction previous = cleanup(false); final javax.transaction.Transaction tx = resumeAsync(previous); if (tx == null) { throw new IllegalArgumentException("No Tx -- should exist?!"); } final Callable<Void> callable = new Callable<Void>() { public Void call() throws Exception { resumeTx(tx); try { return finish(tx, new Callable<Void>() { public Void call() throws Exception { tx.rollback(); return null; } }); } finally { suspendTx(); } } }; return ExecutorFactory.wrap(Wrappers.wrap(callable)); } private static javax.transaction.Transaction resumeAsync(CapedwarfTransaction previous) { final javax.transaction.Transaction tx = suspendTx(); // reset current thread if (previous != null) { previous.resume(false); // resume previous } return tx; } public synchronized String getId() { if (txId == null) { txId = getTxId(getTx()); } return txId; } public String getApp() { return Application.getAppId(); } public boolean isActive() { return isTxActive(); } private static Void finish(final javax.transaction.Transaction tx, final Callable<Void> callable) throws Exception { try { int counter = asyncRetryCount; while(counter > 0 && TxTasks.isDone(tx) == false) { counter--; TxTasks.finish(tx); } if (TxTasks.isDone(tx)) { return callable.call(); } else { throw new IllegalStateException("Cannot execute tx operation, unfinished tasks for tx: " + tx); } } finally { TxTasks.clear(tx); // clear anyway } } /** * Impl detail! * - https://github.com/jbosstm/narayana/blob/master/ArjunaJTS/jtax/classes/com/arjuna/ats/internal/jta/transaction/jts/TransactionImple.java#L1091 * - https://github.com/jbosstm/narayana/blob/master/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/transaction/arjunacore/TransactionImple.java#L1010 * - https://github.com/jbosstm/narayana/blob/master/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/common/Uid.java */ static String getTxId(javax.transaction.Transaction tx) { try { Method get_uid = tx.getClass().getMethod("get_uid"); return get_uid.invoke(tx).toString(); } catch (Exception e) { throw new IllegalStateException(e); } } }