package com.googlecode.objectify.cache;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.googlecode.objectify.util.FutureHelper;
import com.googlecode.objectify.util.cmd.TransactionWrapper;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
/**
* This is necessary to track writes and update the cache only on successful commit.
*/
class CachingTransaction extends TransactionWrapper
{
/** */
private EntityMemcache cache;
/** Lazily constructed set of keys we will EMPTY if transaction commits */
private Set<Key> deferred;
/**
* All futures that have been enlisted in this transaction. In the future, when we can
* hook into the raw Future<?>, we shouldn't need this - the GAE SDK automatically calls
* quietGet() on all the enlisted Futures before a transaction commits.
*/
private List<Future<?>> enlistedFutures = new ArrayList<>();
/** */
public CachingTransaction(EntityMemcache cache, Transaction raw) {
super(raw);
this.cache = cache;
}
@Override
public void commit() {
FutureHelper.quietGet(this.commitAsync());
}
@Override
public Future<Void> commitAsync() {
// We need to ensure that any enlisted Futures are completed before we try
// to run the commit. The GAE SDK does this itself, but unfortunately this
// doesn't help our wrapped Futures. When we can hook into Futures natively
// we won't have to do this ourselves
for (Future<?> fut: this.enlistedFutures)
FutureHelper.quietGet(fut);
return new TriggerFuture<Void>(super.commitAsync()) {
@Override
protected void trigger()
{
// Only after a commit should we modify the cache
if (deferred != null)
{
// According to Alfred, ConcurrentModificationException does not necessarily mean
// the write failed. So this optimization is a bad idea.
//
// There is one special case - if we have a ConcurrentModificationException, we don't
// need to empty the cache because whoever succeeded in their write took care of it.
//try {
// this.raw.get();
//} catch (ExecutionException ex) {
// if (ex.getCause() instanceof ConcurrentModificationException)
// return;
//} catch (Exception ex) {}
cache.empty(deferred);
}
}
};
}
/**
* Adds some keys which will be deleted if the commit is successful.
*/
public void deferEmptyFromCache(Key key) {
if (this.deferred == null)
this.deferred = new HashSet<>();
this.deferred.add(key);
}
/**
* Adds a Future to our transaction; this Future will be completed before the transaction commits.
* TODO: remove this method when the GAE SDK provides a way to hook into Futures.
*/
public void enlist(Future<?> future) {
this.enlistedFutures.add(future);
}
}