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
.ApiBasePb
;
7 import com
.google
.apphosting
.api
.ApiProxy
;
8 import com
.google
.apphosting
.api
.ApiProxy
.ApiConfig
;
9 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
;
10 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.CommitResponse
;
11 import com
.google
.io
.protocol
.ProtocolMessage
;
13 import java
.util
.List
;
14 import java
.util
.concurrent
.Future
;
17 * Implementation of the {@link Transaction} interface that routes all calls
18 * through the {@link ApiProxy}. Our implementation is implicitly async.
19 * BeginTransaction RPCs always return instantly, and this class maintains a
20 * reference to the {@link Future} associated with the RPC. We service as
21 * much of the {@link Transaction} interface as we can without retrieving
22 * the result of the future.
24 * There is no synchronization in this code because transactions are associated
25 * with a single thread and are documented as such.
28 class TransactionImpl
implements Transaction
, CurrentTransactionProvider
{
30 enum TransactionState
{
32 COMPLETION_IN_PROGRESS
,
38 private final ApiConfig apiConfig
;
40 private final String app
;
43 * The {@link Future} associated with the BeginTransaction RPC we sent to the
46 private final Future
<DatastoreV3Pb
.Transaction
> future
;
48 private final TransactionStack txnStack
;
50 private final DatastoreCallbacks callbacks
;
52 private final EntityCachingStrategy entityCachingStrategy
;
54 private final boolean isExplicit
;
56 TransactionState state
= TransactionState
.BEGUN
;
59 * A {@link PostOpFuture} implementation that runs both post put and post
62 private class PostCommitFuture
extends PostOpFuture
<Void
> {
63 private final List
<Entity
> putEntities
;
64 private final List
<Key
> deletedKeys
;
66 private PostCommitFuture(
67 List
<Entity
> putEntities
, List
<Key
> deletedKeys
, Future
<Void
> delegate
) {
68 super(delegate
, callbacks
);
69 this.putEntities
= putEntities
;
70 this.deletedKeys
= deletedKeys
;
74 void executeCallbacks(Void ignoreMe
) {
75 PutContext putContext
= new PutContext(TransactionImpl
.this, putEntities
);
76 callbacks
.executePostPutCallbacks(putContext
);
77 DeleteContext deleteContext
= new DeleteContext(TransactionImpl
.this, deletedKeys
);
78 callbacks
.executePostDeleteCallbacks(deleteContext
);
82 TransactionImpl(ApiConfig apiConfig
, String app
, Future
<DatastoreV3Pb
.Transaction
> future
,
83 TransactionStack txnStack
, DatastoreCallbacks callbacks
,
84 EntityCachingStrategy entityCachingStrategy
, boolean isExplicit
) {
85 this.apiConfig
= apiConfig
;
88 this.txnStack
= txnStack
;
89 this.callbacks
= callbacks
;
90 this.entityCachingStrategy
= entityCachingStrategy
;
91 this.isExplicit
= isExplicit
;
95 * Provides the unique identifier for the txn.
96 * Blocks on the future since the handle comes back from the datastore
99 private long getHandle() {
100 return FutureHelper
.quietGet(future
).getHandle();
103 <T
extends ProtocolMessage
<T
>> Future
<T
> makeAsyncCall(
104 String methodName
, ProtocolMessage
<?
> request
, T response
) {
105 return DatastoreApiHelper
.makeAsyncCall(apiConfig
, methodName
, request
, response
);
108 private <T
extends ProtocolMessage
<T
>> Future
<T
> makeAsyncCall(String methodName
, T response
) {
109 DatastoreV3Pb
.Transaction txn
= new DatastoreV3Pb
.Transaction();
111 txn
.setHandle(getHandle());
113 return makeAsyncCall(methodName
, txn
, response
);
117 public void commit() {
118 FutureHelper
.quietGet(commitAsync());
122 public Future
<Void
> commitAsync() {
125 for (Future
<?
> f
: txnStack
.getFutures(this)) {
126 FutureHelper
.quietGet(f
);
128 PreMutationCachingResult preMutationCachingResult
=
129 entityCachingStrategy
.preCommit(txnStack
.getPutEntities(this),
130 txnStack
.getDeletedKeys(this));
131 Future
<CommitResponse
> commitResponse
= makeAsyncCall("Commit", new CommitResponse());
132 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
133 Future
<Void
> result
= new FutureWrapper
<CommitResponse
, Void
>(commitResponse
) {
135 protected Void
wrap(CommitResponse ignore
) throws Exception
{
136 state
= TransactionState
.COMMITTED
;
141 protected Throwable
convertException(Throwable cause
) {
142 state
= TransactionState
.ERROR
;
146 result
= entityCachingStrategy
.createPostMutationFuture(result
, preMutationCachingResult
);
147 return new PostCommitFuture(txnStack
.getPutEntities(this), txnStack
.getDeletedKeys(this),
151 txnStack
.remove(this);
157 public void rollback() {
158 FutureHelper
.quietGet(rollbackAsync());
162 public Future
<Void
> rollbackAsync() {
165 for (Future
<?
> f
: txnStack
.getFutures(this)) {
166 FutureHelper
.quietGet(f
);
168 Future
<ApiBasePb
.VoidProto
> future
= makeAsyncCall("Rollback", new ApiBasePb
.VoidProto());
169 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
170 return new FutureWrapper
<ApiBasePb
.VoidProto
, Void
>(future
) {
172 protected Void
wrap(ApiBasePb
.VoidProto ignore
) throws Exception
{
173 state
= TransactionState
.ROLLED_BACK
;
178 protected Throwable
convertException(Throwable cause
) {
179 state
= TransactionState
.ERROR
;
185 txnStack
.remove(this);
191 public String
getApp() {
196 public String
getId() {
197 return Long
.toString(getHandle());
201 public boolean equals(Object o
) {
205 if (o
== null || getClass() != o
.getClass()) {
209 TransactionImpl that
= (TransactionImpl
) o
;
211 return getHandle() == that
.getHandle();
215 public int hashCode() {
216 return (int) (getHandle() ^
(getHandle() >>> 32));
220 public String
toString() {
221 return "Txn [" + app
+ "." + getHandle() + ", " + state
+ "]";
225 public boolean isActive() {
226 return state
== TransactionState
.BEGUN
|| state
== TransactionState
.COMPLETION_IN_PROGRESS
;
230 public Transaction
getCurrentTransaction(Transaction defaultValue
) {
235 * If {@code txn} is not null and not active, throw
236 * {@link IllegalStateException}.
238 static void ensureTxnActive(Transaction txn
) {
239 if (txn
!= null && !txn
.isActive()) {
240 throw new IllegalStateException("Transaction with which this operation is "
241 + "associated is not active.");
245 private void ensureTxnStarted() {
246 if (state
!= TransactionState
.BEGUN
) {
247 throw new IllegalStateException("Transaction is in state " + state
+ ". There is no legal "
248 + "transition out of this state.");