package co.codewizards.cloudstore.local;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.context.ExtensibleContextSupport;
import co.codewizards.cloudstore.core.repo.local.ContextWithLocalRepoManager;
import co.codewizards.cloudstore.core.repo.local.LocalRepoManager;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransaction;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransactionListenerRegistry;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransactionPostCloseEvent;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransactionPostCloseListener;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransactionPreCloseEvent;
import co.codewizards.cloudstore.core.repo.local.LocalRepoTransactionPreCloseListener;
import co.codewizards.cloudstore.core.util.AssertUtil;
import co.codewizards.cloudstore.local.persistence.Dao;
import co.codewizards.cloudstore.local.persistence.LocalRepository;
import co.codewizards.cloudstore.local.persistence.LocalRepositoryDao;
public class LocalRepoTransactionImpl implements LocalRepoTransaction, ContextWithLocalRepoManager, ContextWithPersistenceManager {
private static final Logger logger = LoggerFactory.getLogger(LocalRepoTransactionImpl.class);
private final LocalRepoManager localRepoManager;
private final PersistenceManagerFactory persistenceManagerFactory;
private final boolean write;
private PersistenceManager persistenceManager;
private Transaction jdoTransaction;
private final Lock lock;
private long localRevision = -1;
private final Map<Class<?>, Object> daoClass2Dao = new HashMap<>();
private final ExtensibleContextSupport extensibleContextSupport = new ExtensibleContextSupport();
private final LocalRepoTransactionListenerRegistry listenerRegistry = new LocalRepoTransactionListenerRegistry(this);
private final CopyOnWriteArrayList<LocalRepoTransactionPreCloseListener> preCloseListeners = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<LocalRepoTransactionPostCloseListener> postCloseListeners = new CopyOnWriteArrayList<>();
public LocalRepoTransactionImpl(final LocalRepoManagerImpl localRepoManager, final boolean write) {
this.localRepoManager = AssertUtil.assertNotNull(localRepoManager, "localRepoManager");
this.persistenceManagerFactory = AssertUtil.assertNotNull(localRepoManager.getPersistenceManagerFactory(), "localRepoManager.persistenceManagerFactory");
this.lock = localRepoManager.getLock();
this.write = write;
begin();
}
private void begin() {
lock.lock();
try {
if (isActive())
throw new IllegalStateException("Transaction is already active!");
lockIfWrite();
persistenceManager = persistenceManagerFactory.getPersistenceManager();
jdoTransaction = persistenceManager.currentTransaction();
jdoTransaction.begin();
listenerRegistry.onBegin();
} finally {
lock.unlock();
}
}
private final void lockIfWrite() {
if (write)
lock.lock(); // UNbalance lock to keep it after method returns!
}
private final void unlockIfWrite() {
if (write)
lock.unlock(); // UNbalance unlock to counter the unbalanced lock in lockIfWrite().
}
@Override
public void commit() {
lock.lock();
try {
if (!isActive())
throw new IllegalStateException("Transaction is not active!");
listenerRegistry.onCommit();
firePreCloseListeners(true);
daoClass2Dao.clear();
jdoTransaction.commit();
persistenceManager.close();
jdoTransaction = null;
persistenceManager = null;
localRevision = -1;
unlockIfWrite();
} finally {
lock.unlock();
}
firePostCloseListeners(true);
}
@Override
public boolean isActive() {
lock.lock();
try {
return jdoTransaction != null && jdoTransaction.isActive();
} finally {
lock.unlock();
}
}
@Override
public void rollback() {
_rollback();
firePostCloseListeners(false);
}
@Override
public void rollbackIfActive() {
boolean active;
lock.lock();
try {
active = isActive();
if (active) {
_rollback();
}
} finally {
lock.unlock();
}
if (active) {
firePostCloseListeners(false);
}
}
protected void _rollback() {
lock.lock();
try {
if (!isActive())
throw new IllegalStateException("Transaction is not active!");
listenerRegistry.onRollback();
firePreCloseListeners(false);
daoClass2Dao.clear();
jdoTransaction.rollback();
persistenceManager.close();
jdoTransaction = null;
persistenceManager = null;
localRevision = -1;
unlockIfWrite();
} finally {
lock.unlock();
}
}
@Override
public void close() {
rollbackIfActive();
}
@Override
public PersistenceManager getPersistenceManager() {
if (!isActive()) {
throw new IllegalStateException("Transaction is not active!");
}
return persistenceManager;
}
@Override
public long getLocalRevision() {
if (localRevision < 0) {
if (!write)
throw new IllegalStateException("This is a read-only transaction!");
jdoTransaction.setSerializeRead(true);
final LocalRepository lr = getDao(LocalRepositoryDao.class).getLocalRepositoryOrFail();
jdoTransaction.setSerializeRead(null);
localRevision = lr.getRevision() + 1;
lr.setRevision(localRevision);
persistenceManager.flush();
}
return localRevision;
}
@Override
public LocalRepoManager getLocalRepoManager() {
return localRepoManager;
}
@Override
public <D> D getDao(final Class<D> daoClass) {
assertNotNull(daoClass, "daoClass");
@SuppressWarnings("unchecked")
D dao = (D) daoClass2Dao.get(daoClass);
if (dao == null) {
final PersistenceManager pm = getPersistenceManager();
try {
dao = daoClass.newInstance();
} catch (final InstantiationException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
if (!(dao instanceof Dao))
throw new IllegalStateException(String.format("dao class %s does not extend Dao!", daoClass.getName()));
((Dao<?, ?>)dao).setPersistenceManager(pm);
((Dao<?, ?>)dao).setDaoProvider(this);
daoClass2Dao.put(daoClass, dao);
}
return dao;
}
@Override
public void flush() {
final PersistenceManager pm = getPersistenceManager();
pm.flush();
}
@Override
public void setContextObject(final Object object) {
extensibleContextSupport.setContextObject(object);
}
@Override
public <T> T getContextObject(final Class<T> clazz) {
return extensibleContextSupport.getContextObject(clazz);
}
@Override
public void removeContextObject(Object object) {
extensibleContextSupport.removeContextObject(object);
}
@Override
public void removeContextObject(Class<?> clazz) {
extensibleContextSupport.removeContextObject(clazz);
}
@Override
public void addPreCloseListener(LocalRepoTransactionPreCloseListener listener) {
preCloseListeners.add(assertNotNull(listener, "listener"));
}
@Override
public void addPostCloseListener(LocalRepoTransactionPostCloseListener listener) {
postCloseListeners.add(assertNotNull(listener, "listener"));
}
protected void firePreCloseListeners(final boolean commit) {
LocalRepoTransactionPreCloseEvent event = null;
for (final LocalRepoTransactionPreCloseListener listener : preCloseListeners) {
try {
if (event == null)
event = new LocalRepoTransactionPreCloseEvent(this);
if (commit)
listener.preCommit(event);
else
listener.preRollback(event);
} catch (Exception x) {
logger.error("firePreCloseListeners: " + x, x);
}
}
}
protected void firePostCloseListeners(final boolean commit) {
LocalRepoTransactionPostCloseEvent event = null;
for (final LocalRepoTransactionPostCloseListener listener : postCloseListeners) {
try {
if (event == null)
event = new LocalRepoTransactionPostCloseEvent(this);
if (commit)
listener.postCommit(event);
else
listener.postRollback(event);
} catch (Exception x) {
logger.error("firePostCloseListeners: " + x, x);
}
}
}
}