package com.googlecode.objectify.impl;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.TxnType;
import com.googlecode.objectify.Work;
import com.googlecode.objectify.util.ResultWrapper;
import java.util.concurrent.Future;
/**
* Implementation for when we start a transaction. Maintains a separate session, but then copies all
* data into the original session on successful commit.
*
* @author Jeff Schnitzer <jeff@infohazard.org>
*/
public class TransactorYes<O extends Objectify> extends Transactor<O>
{
/** Our transaction. */
protected Result<TransactionImpl> transaction;
/** The non-transactional transactor that spawned us */
protected TransactorNo<O> parentTransactor;
/**
*/
public TransactorYes(ObjectifyImpl<O> current, TransactorNo<O> parentTransactor) {
super(current);
this.parentTransactor = parentTransactor;
// There is no overhead for XG transactions on a single entity group, so there is
// no good reason to ever have withXG false when on the HRD.
Future<Transaction> fut = current.createAsyncDatastoreService().beginTransaction(TransactionOptions.Builder.withXG(true));
transaction = new ResultWrapper<Transaction, TransactionImpl>(new ResultAdapter<>(fut)) {
private static final long serialVersionUID = 1L;
@Override
protected TransactionImpl wrap(Transaction raw) {
return new TransactionImpl(raw, TransactorYes.this);
}
};
}
/* (non-Javadoc)
* @see com.googlecode.objectify.impl.cmd.Transactor#getTransaction()
*/
@Override
public TransactionImpl getTransaction() {
return this.transaction.now();
}
/**
* This version goes back to life without a transaction, but preserves current state regarding deadline, consistency, etc.
* We use the session from the parent, ie life before transactions.
*/
@Override
public ObjectifyImpl<O> transactionless(ObjectifyImpl<O> parent) {
ObjectifyImpl<O> next = parent.clone();
next.transactor = new TransactorNo<>(next, parentTransactor.getSession());
return next;
}
/* (non-Javadoc)
* @see com.googlecode.objectify.impl.cmd.Transactor#execute(com.googlecode.objectify.TxnType, com.googlecode.objectify.Work)
*/
@Override
public <R> R execute(ObjectifyImpl<O> parent, TxnType txnType, Work<R> work) {
switch (txnType) {
case MANDATORY:
case REQUIRED:
case SUPPORTS:
return work.run();
case NOT_SUPPORTED:
try {
ObjectifyService.push(transactionless(parent));
return work.run();
} finally {
ObjectifyService.pop();
}
case NEVER:
throw new IllegalStateException("MANDATORY transaction but no transaction present");
case REQUIRES_NEW:
return transactNew(parent, Integer.MAX_VALUE, work);
default:
throw new IllegalStateException("Impossible, some unknown txn type");
}
}
/* (non-Javadoc)
* @see com.googlecode.objectify.impl.Transactor#transact(com.googlecode.objectify.impl.ObjectifyImpl, com.googlecode.objectify.Work)
*/
@Override
public <R> R transact(ObjectifyImpl<O> parent, Work<R> work) {
return work.run();
}
/**
* We need to make sure the parentSession is the transactionless session, not the session
* for our transaction. This gives proper transaction isolation.
*/
@Override
public <R> R transactNew(ObjectifyImpl<O> parent, int limitTries, Work<R> work) {
return transactionless(parent).transactNew(limitTries, work);
}
/**
* Called when the associated transaction is committed. Dumps the contents of the transactional session into the parent's
* session.
*/
public void committed() {
parentTransactor.getSession().addAll(session);
}
}