package backtype.storm.transactional.state;
import backtype.storm.transactional.TransactionalSpoutCoordinator;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* A map from txid to a value. Automatically deletes txids that have been committed.
*/
public class RotatingTransactionalState {
public static interface StateInitializer {
Object init(BigInteger txid, Object lastState);
}
private TransactionalState _state;
private String _subdir;
private boolean _strictOrder;
private TreeMap<BigInteger, Object> _curr = new TreeMap<BigInteger, Object>();
public RotatingTransactionalState(TransactionalState state, String subdir, boolean strictOrder) {
_state = state;
_subdir = subdir;
_strictOrder = strictOrder;
state.mkdir(subdir);
sync();
}
public RotatingTransactionalState(TransactionalState state, String subdir) {
this(state, subdir, false);
}
public Object getLastState() {
if(_curr.isEmpty()) return null;
else return _curr.lastEntry().getValue();
}
public void overrideState(BigInteger txid, Object state) {
_state.setData(txPath(txid), state);
_curr.put(txid, state);
}
public void removeState(BigInteger txid) {
if(_curr.containsKey(txid)) {
_curr.remove(txid);
_state.delete(txPath(txid));
}
}
public Object getState(BigInteger txid, StateInitializer init) {
if(!_curr.containsKey(txid)) {
SortedMap<BigInteger, Object> prevMap = _curr.headMap(txid);
SortedMap<BigInteger, Object> afterMap = _curr.tailMap(txid);
BigInteger prev = null;
if(!prevMap.isEmpty()) prev = prevMap.lastKey();
if(_strictOrder) {
if(prev==null && !txid.equals(TransactionalSpoutCoordinator.INIT_TXID)) {
throw new IllegalStateException("Trying to initialize transaction for which there should be a previous state");
}
if(prev!=null && !prev.equals(txid.subtract(BigInteger.ONE))) {
throw new IllegalStateException("Expecting previous txid state to be the previous transaction");
}
if(!afterMap.isEmpty()) {
throw new IllegalStateException("Expecting tx state to be initialized in strict order but there are txids after that have state");
}
}
Object data;
if(afterMap.isEmpty()) {
Object prevData;
if(prev!=null) {
prevData = _curr.get(prev);
} else {
prevData = null;
}
data = init.init(txid, prevData);
} else {
data = null;
}
_curr.put(txid, data);
_state.setData(txPath(txid), data);
}
return _curr.get(txid);
}
/**
* Returns null if it was created, the value otherwise.
*/
public Object getStateOrCreate(BigInteger txid, StateInitializer init) {
if(_curr.containsKey(txid)) {
return _curr.get(txid);
} else {
getState(txid, init);
return null;
}
}
public void cleanupBefore(BigInteger txid) {
SortedMap<BigInteger, Object> toDelete = _curr.headMap(txid);
for(BigInteger tx: new HashSet<BigInteger>(toDelete.keySet())) {
_curr.remove(tx);
_state.delete(txPath(tx));
}
}
private void sync() {
List<String> txids = _state.list(_subdir);
for(String txid_s: txids) {
Object data = _state.getData(txPath(txid_s));
_curr.put(new BigInteger(txid_s), data);
}
}
private String txPath(BigInteger tx) {
return txPath(tx.toString());
}
private String txPath(String tx) {
return _subdir + "/" + tx;
}
}