package er.rest.util; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.eof.ERXEC; public class ERXRestTransactionManager { private NSMutableDictionary<String, ERXRestTransaction> _transactions; private IntRangeSet _sequenceIDs; public ERXRestTransactionManager() { _transactions = new NSMutableDictionary<>(); _sequenceIDs = new IntRangeSet(); } protected EOEditingContext newEditingContext() { return ERXEC.newEditingContext(); } public void addSequenceID(int sequenceID) { _sequenceIDs.add(sequenceID); } public ERXRestTransaction transactionForID(String id) { ERXRestTransaction transaction = _transactions.objectForKey(id); if (transaction == null) { transaction = new ERXRestTransaction(id, newEditingContext()); _transactions.setObjectForKey(transaction, id); } return transaction; } public void removeTransaction(ERXRestTransaction transaction) { _transactions.removeObjectForKey(transaction.identifier()); } public boolean isTransactionReady(ERXRestTransaction transaction) { boolean transactionReady = false; // If we have a commit, the transaction might be ready if (transaction.hasCommit()) { int minimumSequenceID = transaction.minimumSequenceID(); int maximumSequenceID = transaction.maximumSequenceID(); // If we've seen every sequence ID in this transaction, the transaction might be ready if (_sequenceIDs.contains(minimumSequenceID, maximumSequenceID)) { // If the minimum sequence ID was the lowest we can possibly see, the transaction is ready if (minimumSequenceID == minimumPossibleSequenceID()) { transactionReady = true; } // If we've seen the sequence ID just below the minimum, the transaction is ready else if (_sequenceIDs.contains(minimumSequenceID - 1)) { transactionReady = true; } else { // This is the vague part. Say the transaction is [5-10] and 4 got dropped by the // client somehow. WTF now? Technically the transaction is ready, but we have no // way to know because we don't know if 4 was the real start of the transaction. // To protect against a hung transaction, we should probably let it go, but // from a safety perspective, we just don't know. } } } return transactionReady; } protected int minimumPossibleSequenceID() { return 1; } public static class IntRangeSet { private List<IntRange> _ranges; public IntRangeSet() { _ranges = new LinkedList<>(); } public boolean contains(int start, int end) { boolean contains = false; for (IntRange range : _ranges) { if (start >= range.start && end <= range.end) { contains = true; break; } else if (range.start > start) { break; } } return contains; } public boolean contains(int value) { boolean contains = false; for (IntRange range : _ranges) { if (value >= range.start && value <= range.end) { contains = true; break; } else if (range.start > value) { break; } } return contains; } public void add(int value) { Iterator<IntRange> rangeIter = _ranges.iterator(); int index = 0; boolean valueAdded = false; while (rangeIter.hasNext()) { IntRange range = rangeIter.next(); if (value <= range.end) { if (value < range.start) { if (value == range.start - 1) { range.start = value; } else { _ranges.add(index, new IntRange(value, value)); } } valueAdded = true; break; } else if (value == range.end + 1) { range.end = value; if (rangeIter.hasNext()) { IntRange nextRange = rangeIter.next(); if (nextRange.start == range.end + 1) { range.end = nextRange.end; rangeIter.remove(); } } valueAdded = true; break; } index++; } if (!valueAdded) { _ranges.add(new IntRange(value, value)); } } protected static class IntRange { public int start; public int end; public IntRange(int start, int end) { this.start = start; this.end = end; } public boolean contains(int value) { return value >= start && value <= end; } @Override public String toString() { return "[" + start + "-" + end + "]"; } } } }