package pt.ist.fenixframework.backend.infinispan;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.infinispan.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pt.ist.fenixframework.Atomic;
import pt.ist.fenixframework.CallableWithoutException;
import pt.ist.fenixframework.CommitListener;
import pt.ist.fenixframework.TransactionManager;
import pt.ist.fenixframework.util.TxMap;
public class InfinispanTransactionManager implements TransactionManager {
private static final Logger logger = LoggerFactory.getLogger(InfinispanTransactionManager.class);
private static javax.transaction.TransactionManager delegateTxManager;
private final ConcurrentLinkedQueue<CommitListener> listeners = new ConcurrentLinkedQueue<CommitListener>();
void setDelegateTxManager(javax.transaction.TransactionManager delegate) {
delegateTxManager = delegate;
}
@Override
public void begin() throws NotSupportedException, SystemException {
begin(false);
}
@Override
public void begin(boolean readOnly) throws NotSupportedException, SystemException {
if (readOnly) {
logger.warn("InfinispanBackEnd does not enforce read-only transactions. Starting as normal transaction");
}
logger.trace("Begin transaction");
delegateTxManager.begin();
}
@Override
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SystemException {
logger.trace("Commit transaction");
pt.ist.fenixframework.Transaction tx = getTransaction();
try {
for (CommitListener listener : listeners) {
listener.beforeCommit(tx);
}
} catch (RuntimeException e) {
/**
* As specified in CommitListener.beforeCommit(), any unchecked
* exception will cause the transaction to be rolled back.
*/
rollback();
throw new RollbackException(e.getMessage());
}
try {
delegateTxManager.commit();
} finally {
for (CommitListener listener : listeners) {
listener.afterCommit(tx);
}
}
}
@Override
public pt.ist.fenixframework.Transaction getTransaction() {
try {
Transaction tx = delegateTxManager.getTransaction();
return TxMap.getTx(tx);
} catch (Exception e) {
return null;
}
}
@Override
public void rollback() throws SystemException {
logger.trace("Rollback transaction");
delegateTxManager.rollback();
}
@Override
public <T> T withTransaction(CallableWithoutException<T> command) {
try {
return withTransaction(command, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public <T> T withTransaction(Callable<T> command) throws Exception {
return withTransaction(command, null);
}
/**
* For now, it ignores the value of the atomic parameter.
*/
@Override
public <T> T withTransaction(Callable<T> command, Atomic atomic) throws Exception {
T result = null;
boolean txFinished = false;
while (!txFinished) {
try {
boolean inTopLevelTransaction = false;
// the purpose of this test is to enable reuse of the existing
// transaction
if (getTransaction() == null) {
logger.trace("No previous transaction. Will begin a new one.");
begin();
inTopLevelTransaction = true;
} else {
logger.trace("Already inside a transaction. Not nesting.");
}
// do some work
result = command.call();
if (inTopLevelTransaction) {
logger.trace("Will commit a top-level transaction.");
commit();
} else {
logger.trace("Leaving an inner transaction.");
}
txFinished = true;
return result;
} catch (CacheException ce) {
// If the execution fails
logException(ce);
} catch (RollbackException re) {
// If the transaction was marked for rollback only, the
// transaction is rolled back and this exception is thrown.
logException(re);
} catch (HeuristicMixedException hme) {
// If a heuristic decision was made and some some parts of the
// transaction have been committed while other parts have been
// rolled back.
// Pedro -- most of the time, happens when some nodes fails...
logException(hme);
} catch (HeuristicRollbackException hre) {
// If a heuristic decision to roll back the transaction was made
logException(hre);
} catch (Exception e) { // any other exception out
logger.debug("Exception within transaction", e);
throw e;
} finally {
if (!txFinished) {
try {
rollback();
} catch (IllegalStateException ise) {
// If the transaction is in a state where it cannot be
// rolled back.
// Pedro -- happen when the commit fails. When commit
// fails, it invokes the rollback().
// so rollback() will be invoked again, but the
// transaction no longer exists
// Pedro -- just ignore it
} catch (Exception ex) {
logger.error("Exception while aborting transaction");
ex.printStackTrace();
}
}
}
// Pedro had this wait here. Why?
// waitingBeforeRetry();
logger.debug("Retrying transaction: " + command);
}
// never reached
throw new RuntimeException("code never reached");
}
// private static final Random rand = new Random();
// private void waitingBeforeRetry() {
// try {
// //smf: why so long?!
// Thread.sleep(rand.nextInt(10000));
// } catch(InterruptedException ie) {
// //do nothing
// }
// }
private void logException(Exception e) {
logger.info("Exception caught in transaction: " + e.getLocalizedMessage());
logger.trace("Exception caught in transaction:", e);
}
@Override
public int getStatus() throws SystemException {
return delegateTxManager.getStatus();
}
@Override
public void resume(Transaction tx) throws InvalidTransactionException, IllegalStateException, SystemException {
delegateTxManager.resume(tx);
}
@Override
public void setRollbackOnly() throws IllegalStateException, SystemException {
delegateTxManager.setRollbackOnly();
}
@Override
public void setTransactionTimeout(int timeout) throws SystemException {
delegateTxManager.setTransactionTimeout(timeout);
}
@Override
public Transaction suspend() throws SystemException {
return delegateTxManager.suspend();
}
/**
* @see pt.ist.fenixframework.TransactionManager#addCommitListener(pt.ist.fenixframework.CommitListener)
*/
@Override
public void addCommitListener(CommitListener listener) {
listeners.add(listener);
}
/**
* @see pt.ist.fenixframework.TransactionManager#removeCommitListener(pt.ist.fenixframework.CommitListener)
*/
@Override
public void removeCommitListener(CommitListener listener) {
listeners.remove(listener);
}
}