package com.thinkaurelius.titan.diskstorage.infinispan; import java.util.concurrent.atomic.AtomicReference; 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 javax.transaction.TransactionManager; import org.infinispan.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.thinkaurelius.titan.diskstorage.PermanentStorageException; import com.thinkaurelius.titan.diskstorage.StorageException; import com.thinkaurelius.titan.diskstorage.TemporaryStorageException; import com.thinkaurelius.titan.diskstorage.common.AbstractStoreTransaction; import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTxConfig; /** * Titan's diskstorage API requires transactions at the backend level, whereas * Infinispan's API encourages the use of transactions at the cache level. */ public class InfinispanCacheTransaction extends AbstractStoreTransaction { private final AtomicReference<State> state; private static final Logger log = LoggerFactory.getLogger(InfinispanCacheTransaction.class); public InfinispanCacheTransaction(StoreTxConfig config) { super(config); state = new AtomicReference<State>(); } public void init(Cache<?,?> c) throws StorageException { assert null != c; State current = state.get(); if (null != current) return; State s = makeStateForCache(c); if (!state.compareAndSet(null, s)) { Preconditions.checkArgument(s.m == c.getAdvancedCache().getTransactionManager()); // Cleanup unused transaction try { TransactionManager tm = c.getAdvancedCache().getTransactionManager(); tm.resume(s.t); tm.rollback(); } catch (IllegalStateException e) { throw new PermanentStorageException(e); } catch (SystemException e) { throw new TemporaryStorageException(e); } catch (InvalidTransactionException e) { throw new TemporaryStorageException(e); } } // boolean wasNull = state.compareAndSet(null, s); // // if (wasNull) // return; // // State actual = state.get(); // assert null != actual; // assert null != actual.c; // return c.equals(actual.c); } private State makeStateForCache(Cache<?,?> c) throws StorageException { try { TransactionManager tm = c.getAdvancedCache().getTransactionManager(); tm.begin(); Transaction t = tm.suspend(); log.trace("Begin {}", t); return new State(c, t); } catch (NotSupportedException e) { throw new PermanentStorageException(e); } catch (SystemException e) { throw new TemporaryStorageException(e); } } @SuppressWarnings("unchecked") public <K,V> Cache<K,V> getCache() { return (Cache<K,V>)state.get().c; } @Override public void commit() throws StorageException { State s = state.get(); if (null == s) return; try { s.m.resume(s.t); log.trace("Commit {}", s.m); s.m.commit(); } catch (SecurityException e) { throw new PermanentStorageException(e); } catch (IllegalStateException e) { throw new TemporaryStorageException(e); } catch (RollbackException e) { throw new TemporaryStorageException(e); } catch (HeuristicMixedException e) { throw new TemporaryStorageException(e); } catch (HeuristicRollbackException e) { throw new TemporaryStorageException(e); } catch (SystemException e) { throw new TemporaryStorageException(e); } catch (InvalidTransactionException e) { throw new PermanentStorageException(e); } } @Override public void rollback() throws StorageException { State s = state.get(); if (null == s) return; try { s.m.resume(s.t); log.trace("Rollback {}", s.m); s.m.rollback(); } catch (IllegalStateException e) { throw new TemporaryStorageException(e); } catch (SecurityException e) { throw new PermanentStorageException(e); } catch (SystemException e) { throw new TemporaryStorageException(e); } catch (InvalidTransactionException e) { throw new PermanentStorageException(e); } } @Override public void flush() throws StorageException { // TODO ? log.debug("Flush is not meaningful under Infinispan"); } public void resume() throws StorageException { State s = state.get(); if (null == s) return; try { s.m.resume(s.t); } catch (InvalidTransactionException e) { throw new PermanentStorageException(e); } catch (IllegalStateException e) { throw new TemporaryStorageException(e); } catch (SystemException e) { throw new TemporaryStorageException(e); } } public void suspend() throws StorageException { State s = state.get(); if (null == s) return; // In Arjuna local, this only mutates a threadlocal and not s.t // This assumption may not hold for Arjuna XA or, say, Atomikos // Worst case: we would have to serialize all actions within a particular tx try { s.m.suspend(); } catch (SystemException e) { throw new TemporaryStorageException(e); } } private static class State { private final Cache<?,?> c; private final TransactionManager m; private final Transaction t; public State(Cache<?,?> c, Transaction t) { this.c = c; this.m = c.getAdvancedCache().getTransactionManager(); this.t = t; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((c == null) ? 0 : c.hashCode()); result = prime * result + ((t == null) ? 0 : t.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; State other = (State) obj; if (c == null) { if (other.c != null) return false; } else if (!c.equals(other.c)) return false; if (t == null) { if (other.t != null) return false; } else if (!t.equals(other.t)) return false; return true; } } }