package org.infinispan.transaction.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import org.infinispan.transaction.totalorder.TotalOrderLatch;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* Represents a state for a Remote Transaction when the Total Order based protocol is used.
*
* @author Pedro Ruivo
* @since 5.3
*/
public class TotalOrderRemoteTransactionState {
private static final Log log = LogFactory.getLog(TotalOrderRemoteTransactionState.class);
private static final boolean trace = log.isTraceEnabled();
private final EnumSet<State> transactionState;
private final GlobalTransaction globalTransaction;
private List<Object> lockedKeys;
private TotalOrderLatch block;
private List<TotalOrderLatch> dependencies;
public TotalOrderRemoteTransactionState(GlobalTransaction globalTransaction) {
this.transactionState = EnumSet.noneOf(State.class);
this.globalTransaction = globalTransaction;
}
/**
* check if the transaction is marked for rollback (by the Rollback Command)
*
* @return true if it is marked for rollback, false otherwise
*/
public synchronized boolean isRollbackReceived() {
return transactionState.contains(State.ROLLBACK_ONLY);
}
/**
* check if the transaction is marked for commit (by the Commit Command)
*
* @return true if it is marked for commit, false otherwise
*/
public synchronized boolean isCommitReceived() {
return transactionState.contains(State.COMMIT_ONLY);
}
/**
* mark the transaction as prepared (the validation was finished) and notify a possible pending commit or rollback
* command
*/
public synchronized void prepared() {
if (trace) {
log.tracef("[%s] Current status is %s, setting status to: PREPARED", globalTransaction.globalId(),
transactionState);
}
transactionState.add(State.PREPARED);
notifyAll();
}
/**
* mark the transaction as preparing, blocking the commit and rollback commands until the {@link #prepared()} is
* invoked
*/
public synchronized void preparing() {
if (trace) {
log.tracef("[%s] Current status is %s, setting status to: PREPARING", globalTransaction.globalId(),
transactionState);
}
transactionState.add(State.PREPARING);
}
/**
* Commit and rollback commands invokes this method and they are blocked here if the state is PREPARING
*
* @param commit true if it is a commit command, false otherwise
* @return true if the command needs to be processed, false otherwise
* @throws InterruptedException when it is interrupted while waiting
*/
public final synchronized boolean waitUntilPrepared(boolean commit)
throws InterruptedException {
boolean result;
State state = commit ? State.COMMIT_ONLY : State.ROLLBACK_ONLY;
if (trace) {
log.tracef("[%s] Current status is %s, setting status to: %s", globalTransaction.globalId(),
transactionState, state);
}
transactionState.add(state);
if (transactionState.contains(State.PREPARED)) {
result = true;
if (trace) {
log.tracef("[%s] Transaction is PREPARED", globalTransaction.globalId());
}
} else if (transactionState.contains(State.PREPARING)) {
wait();
result = true;
if (trace) {
log.tracef("[%s] Transaction was in PREPARING state but now it is prepared", globalTransaction.globalId());
}
} else {
if (trace) {
log.tracef("[%s] Transaction is not delivered yet", globalTransaction.globalId());
}
result = false;
}
return result;
}
/**
* @return true if the transaction has received the prepare and the commit or rollback
*/
public final synchronized boolean isFinished() {
return transactionState.contains(State.PREPARED) &&
(transactionState.contains(State.COMMIT_ONLY) || transactionState.contains(State.ROLLBACK_ONLY));
}
/**
* @return the keys locked in {@code org.infinispan.transaction.totalorder.TotalOrderManager}
*/
public final synchronized Collection<Object> getLockedKeys() {
return lockedKeys;
}
/**
* @return the {@code TotalOrderLatch} associated to this transaction
*/
public final synchronized TotalOrderLatch getTransactionSynchronizedBlock() {
return block;
}
/**
* Sets the {@code TotalOrderLatch} to be associated to this transaction
*/
public final synchronized void setTransactionSynchronizedBlock(TotalOrderLatch block) {
this.block = block;
}
/**
* @return the global transaction
*/
public final synchronized GlobalTransaction getGlobalTransaction() {
return globalTransaction;
}
public final synchronized void awaitUntilReset() throws InterruptedException {
while (block != null && lockedKeys != null) {
wait();
}
}
public final synchronized void reset() {
this.block = null;
this.lockedKeys = null;
notifyAll();
}
@Override
public String toString() {
return "TotalOrderRemoteTransactionState{" +
"transactionState=" + transactionState +
", globalTransaction='" + globalTransaction.globalId() + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TotalOrderRemoteTransactionState state = (TotalOrderRemoteTransactionState) o;
return !(globalTransaction != null ? !globalTransaction.equals(state.globalTransaction) :
state.globalTransaction != null);
}
@Override
public int hashCode() {
return globalTransaction != null ? globalTransaction.hashCode() : 0;
}
public final synchronized void addSynchronizedBlock(TotalOrderLatch block) {
if (dependencies == null) {
dependencies = new ArrayList<TotalOrderLatch>(8);
}
dependencies.add(block);
}
public final synchronized void addAllSynchronizedBlocks(Collection<TotalOrderLatch> blocks) {
if (dependencies == null) {
dependencies = new ArrayList<TotalOrderLatch>(blocks);
} else {
dependencies.addAll(blocks);
}
}
public final synchronized void addKeysLockedForClear() {
lockedKeys = null;
}
public final synchronized void addLockedKey(Object key) {
if (lockedKeys == null) {
lockedKeys = new ArrayList<Object>(8);
}
lockedKeys.add(key);
}
public synchronized Collection<TotalOrderLatch> getConflictingTransactionBlocks() {
return dependencies == null ? Collections.<TotalOrderLatch>emptyList() : dependencies;
}
private static enum State {
/**
* the prepare command was received and started the validation
*/
PREPARING,
/**
* the prepare command was received and finished the validation
*/
PREPARED,
/**
* the rollback command was received before the prepare command and the transaction must be aborted
*/
ROLLBACK_ONLY,
/**
* the commit command was received before the prepare command and the transaction must be committed
*/
COMMIT_ONLY
}
}