1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import com
.google
.appengine
.api
.datastore
.EntityCachingStrategy
.PreMutationCachingResult
;
5 import com
.google
.appengine
.api
.utils
.FutureWrapper
;
6 import com
.google
.apphosting
.api
.ApiProxy
.ApiConfig
;
9 import java
.util
.concurrent
.Future
;
12 * State and behavior that is common to all {@link Transaction} implementations.
14 * Our implementation is implicitly async. BeginTransaction RPCs always return
15 * instantly, and this class maintains a reference to the {@link Future}
16 * associated with the RPC. We service as much of the {@link Transaction}
17 * interface as we can without retrieving the result of the future.
19 * There is no synchronization in this code because transactions are associated
20 * with a single thread and are documented as such.
22 class TransactionImpl
implements Transaction
, CurrentTransactionProvider
{
25 * Interface to a coupled object which handles the actual transaction RPCs
26 * and other service protocol dependent details.
28 interface InternalTransaction
{
30 * Issues an asynchronous RPC to commit this transaction.
32 Future
<Void
> doCommitAsync();
35 * Issues an asynchronous RPC to rollback this transaction.
37 Future
<Void
> doRollbackAsync();
42 boolean equals(Object o
);
48 enum TransactionState
{
50 COMPLETION_IN_PROGRESS
,
56 final ApiConfig apiConfig
;
58 private final String app
;
60 private final TransactionStack txnStack
;
62 private final DatastoreCallbacks callbacks
;
64 private final EntityCachingStrategy entityCachingStrategy
;
66 private final boolean isExplicit
;
68 private final InternalTransaction internalTransaction
;
70 TransactionState state
= TransactionState
.BEGUN
;
73 * A {@link PostOpFuture} implementation that runs both post put and post
76 private class PostCommitFuture
extends PostOpFuture
<Void
> {
77 private final List
<Entity
> putEntities
;
78 private final List
<Key
> deletedKeys
;
80 private PostCommitFuture(
81 List
<Entity
> putEntities
, List
<Key
> deletedKeys
, Future
<Void
> delegate
) {
82 super(delegate
, callbacks
);
83 this.putEntities
= putEntities
;
84 this.deletedKeys
= deletedKeys
;
88 void executeCallbacks(Void ignoreMe
) {
89 PutContext putContext
= new PutContext(TransactionImpl
.this, putEntities
);
90 callbacks
.executePostPutCallbacks(putContext
);
91 DeleteContext deleteContext
= new DeleteContext(TransactionImpl
.this, deletedKeys
);
92 callbacks
.executePostDeleteCallbacks(deleteContext
);
96 TransactionImpl(ApiConfig apiConfig
, String app
, TransactionStack txnStack
,
97 DatastoreCallbacks callbacks
, EntityCachingStrategy entityCachingStrategy
,
98 boolean isExplicit
, InternalTransaction txnProvider
) {
99 this.apiConfig
= apiConfig
;
101 this.txnStack
= txnStack
;
102 this.callbacks
= callbacks
;
103 this.entityCachingStrategy
= entityCachingStrategy
;
104 this.isExplicit
= isExplicit
;
105 this.internalTransaction
= txnProvider
;
109 public String
getId() {
110 return internalTransaction
.getId();
114 public boolean equals(Object o
) {
115 if (o
instanceof TransactionImpl
) {
116 return internalTransaction
.equals(((TransactionImpl
) o
).internalTransaction
);
122 public int hashCode() {
123 return internalTransaction
.hashCode();
127 public void commit() {
128 FutureHelper
.quietGet(commitAsync());
132 public Future
<Void
> commitAsync() {
135 for (Future
<?
> f
: txnStack
.getFutures(this)) {
136 FutureHelper
.quietGet(f
);
138 PreMutationCachingResult preMutationCachingResult
=
139 entityCachingStrategy
.preCommit(txnStack
.getPutEntities(this),
140 txnStack
.getDeletedKeys(this));
141 Future
<Void
> commitResponse
= internalTransaction
.doCommitAsync();
142 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
143 Future
<Void
> result
= new FutureWrapper
<Void
, Void
>(commitResponse
) {
145 protected Void
wrap(Void ignore
) throws Exception
{
146 state
= TransactionState
.COMMITTED
;
151 protected Throwable
convertException(Throwable cause
) {
152 state
= TransactionState
.ERROR
;
156 result
= entityCachingStrategy
.createPostMutationFuture(result
, preMutationCachingResult
);
157 return new PostCommitFuture(txnStack
.getPutEntities(this), txnStack
.getDeletedKeys(this),
161 txnStack
.remove(this);
167 public void rollback() {
168 FutureHelper
.quietGet(rollbackAsync());
172 public Future
<Void
> rollbackAsync() {
175 for (Future
<?
> f
: txnStack
.getFutures(this)) {
176 FutureHelper
.quietGet(f
);
178 Future
<Void
> future
= internalTransaction
.doRollbackAsync();
179 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
180 return new FutureWrapper
<Void
, Void
>(future
) {
182 protected Void
wrap(Void ignore
) throws Exception
{
183 state
= TransactionState
.ROLLED_BACK
;
188 protected Throwable
convertException(Throwable cause
) {
189 state
= TransactionState
.ERROR
;
195 txnStack
.remove(this);
201 public String
getApp() {
206 public boolean isActive() {
207 return state
== TransactionState
.BEGUN
|| state
== TransactionState
.COMPLETION_IN_PROGRESS
;
211 public Transaction
getCurrentTransaction(Transaction defaultValue
) {
216 * If {@code txn} is not null and not active, throw
217 * {@link IllegalStateException}.
219 static void ensureTxnActive(Transaction txn
) {
220 if (txn
!= null && !txn
.isActive()) {
221 throw new IllegalStateException("Transaction with which this operation is "
222 + "associated is not active.");
226 private void ensureTxnStarted() {
227 if (state
!= TransactionState
.BEGUN
) {
228 throw new IllegalStateException("Transaction is in state " + state
+ ". There is no legal "
229 + "transition out of this state.");
234 public String
toString() {
235 return "Txn [" + app
+ "." + getId() + ", " + state
+ "]";