package org.prevayler.implementation;
import org.prevayler.Clock;
import org.prevayler.Query;
import org.prevayler.foundation.Cool;
import org.prevayler.foundation.DeepCopier;
import org.prevayler.foundation.serialization.Serializer;
import org.prevayler.implementation.publishing.TransactionPublisher;
import org.prevayler.implementation.publishing.TransactionSubscriber;
import org.prevayler.implementation.snapshot.GenericSnapshotManager;
import java.io.IOException;
import java.util.Date;
public class PrevalentSystemGuard implements TransactionSubscriber {
private Object _prevalentSystem; // All access to field is synchronized on "this", and all access to object is synchronized on itself; "this" is always locked before the object
private long _systemVersion; // All access is synchronized on "this"
private boolean _ignoreRuntimeExceptions; // All access is synchronized on "this"
private final Serializer _journalSerializer;
public PrevalentSystemGuard(Object prevalentSystem, long systemVersion, Serializer journalSerializer) {
_prevalentSystem = prevalentSystem;
_systemVersion = systemVersion;
_ignoreRuntimeExceptions = false;
_journalSerializer = journalSerializer;
}
public Object prevalentSystem() {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer allowing access to the prevalent system due to an Error thrown from an earlier transaction.");
}
return _prevalentSystem;
}
}
public void subscribeTo(TransactionPublisher publisher) throws IOException, ClassNotFoundException {
long initialTransaction;
synchronized (this) {
_ignoreRuntimeExceptions = true; //During pending transaction recovery (rolling forward), RuntimeExceptions are ignored because they were already thrown and handled during the first transaction execution.
initialTransaction = _systemVersion + 1;
}
publisher.subscribe(this, initialTransaction);
synchronized (this) {
_ignoreRuntimeExceptions = false;
}
}
public void receive(TransactionTimestamp transactionTimestamp) {
Capsule capsule = transactionTimestamp.capsule();
long systemVersion = transactionTimestamp.systemVersion();
Date executionTime = transactionTimestamp.executionTime();
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer processing transactions due to an Error thrown from an earlier transaction.");
}
if (systemVersion != _systemVersion + 1) {
throw new IllegalStateException(
"Attempted to apply transaction " + systemVersion + " when prevalent system was only at " + _systemVersion);
}
_systemVersion = systemVersion;
try {
// Don't synchronize on _prevalentSystem here so that the capsule can deserialize a fresh
// copy of the transaction without blocking queries.
capsule.executeOn(_prevalentSystem, executionTime, _journalSerializer);
} catch (RuntimeException rx) {
if (!_ignoreRuntimeExceptions) throw rx; //TODO Guarantee that transactions received from pending transaction recovery don't ever throw RuntimeExceptions. Maybe use a wrapper for that.
} catch (Error error) {
_prevalentSystem = null;
throw error;
} finally {
notifyAll();
}
}
}
public Object executeQuery(Query sensitiveQuery, Clock clock) throws Exception {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer processing queries due to an Error thrown from an earlier transaction.");
}
synchronized (_prevalentSystem) {
return sensitiveQuery.query(_prevalentSystem, clock.time());
}
}
}
public void takeSnapshot(GenericSnapshotManager snapshotManager) throws IOException {
synchronized (this) {
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer allowing snapshots due to an Error thrown from an earlier transaction.");
}
synchronized (_prevalentSystem) {
snapshotManager.writeSnapshot(_prevalentSystem, _systemVersion);
}
}
}
public PrevalentSystemGuard deepCopy(long systemVersion, Serializer snapshotSerializer) throws IOException, ClassNotFoundException {
synchronized (this) {
while (_systemVersion < systemVersion && _prevalentSystem != null) {
Cool.wait(this);
}
if (_prevalentSystem == null) {
throw new Error("Prevayler is no longer accepting transactions due to an Error thrown from an earlier transaction.");
}
if (_systemVersion > systemVersion) {
throw new IllegalStateException("Already at " + _systemVersion + "; can't go back to " + systemVersion);
}
synchronized (_prevalentSystem) {
return new PrevalentSystemGuard(DeepCopier.deepCopyParallel(_prevalentSystem, snapshotSerializer), _systemVersion, _journalSerializer);
}
}
}
}