1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import com
.google
.appengine
.api
.utils
.FutureWrapper
;
5 import com
.google
.apphosting
.api
.ApiBasePb
;
6 import com
.google
.apphosting
.api
.ApiProxy
;
7 import com
.google
.apphosting
.api
.ApiProxy
.ApiConfig
;
8 import com
.google
.apphosting
.api
.DatastorePb
;
9 import com
.google
.apphosting
.api
.DatastorePb
.CommitResponse
;
10 import com
.google
.io
.protocol
.ProtocolMessage
;
12 import java
.util
.List
;
13 import java
.util
.concurrent
.Future
;
16 * Implementation of the {@link Transaction} interface that routes all calls
17 * through the {@link ApiProxy}. Our implementation is implicitly async.
18 * BeginTransaction RPCs always return instantly, and this class maintains a
19 * reference to the {@link Future} associated with the RPC. We service as
20 * much of the {@link Transaction} interface as we can without retrieving
21 * the result of the future.
23 * There is no synchronization in this code because transactions are associated
24 * with a single thread and are documented as such.
27 class TransactionImpl
implements Transaction
, CurrentTransactionProvider
{
29 enum TransactionState
{
31 COMPLETION_IN_PROGRESS
,
37 private final ApiConfig apiConfig
;
39 private final String app
;
42 * The {@link Future} associated with the BeginTransaction RPC we sent to the
45 private final Future
<DatastorePb
.Transaction
> future
;
47 private final TransactionStack txnStack
;
49 private final DatastoreCallbacks callbacks
;
51 TransactionState state
= TransactionState
.BEGUN
;
54 * A {@link PostOpFuture} implementation that runs both post put and post
57 private class PostCommitFuture
extends PostOpFuture
<Void
> {
58 private final List
<Entity
> putEntities
;
59 private final List
<Key
> deletedKeys
;
61 private PostCommitFuture(
62 List
<Entity
> putEntities
, List
<Key
> deletedKeys
, Future
<Void
> delegate
) {
63 super(delegate
, callbacks
);
64 this.putEntities
= putEntities
;
65 this.deletedKeys
= deletedKeys
;
69 void executeCallbacks(Void ignoreMe
) {
70 PutContext putContext
= new PutContext(TransactionImpl
.this, putEntities
);
71 callbacks
.executePostPutCallbacks(putContext
);
72 DeleteContext deleteContext
= new DeleteContext(TransactionImpl
.this, deletedKeys
);
73 callbacks
.executePostDeleteCallbacks(deleteContext
);
77 TransactionImpl(ApiConfig apiConfig
, String app
, Future
<DatastorePb
.Transaction
> future
,
78 TransactionStack txnStack
, DatastoreCallbacks callbacks
) {
79 this.apiConfig
= apiConfig
;
82 this.txnStack
= txnStack
;
83 this.callbacks
= callbacks
;
87 * Provides the unique identifier for the txn.
88 * Blocks on the future since the handle comes back from the datastore
91 private long getHandle() {
92 return FutureHelper
.quietGet(future
).getHandle();
95 <T
extends ProtocolMessage
<T
>> Future
<T
> makeAsyncCall(
96 String methodName
, ProtocolMessage
<?
> request
, T response
) {
97 return DatastoreApiHelper
.makeAsyncCall(apiConfig
, methodName
, request
, response
);
100 private <T
extends ProtocolMessage
<T
>> Future
<T
> makeAsyncCall(String methodName
, T response
) {
101 DatastorePb
.Transaction txn
= new DatastorePb
.Transaction();
103 txn
.setHandle(getHandle());
105 return makeAsyncCall(methodName
, txn
, response
);
109 public void commit() {
110 FutureHelper
.quietGet(commitAsync());
114 public Future
<Void
> commitAsync() {
117 for (Future
<?
> f
: txnStack
.getFutures(this)) {
118 FutureHelper
.quietGet(f
);
120 Future
<CommitResponse
> future
= makeAsyncCall("Commit", new CommitResponse());
121 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
122 return new PostCommitFuture(txnStack
.getPutEntities(this), txnStack
.getDeletedKeys(this),
123 new FutureWrapper
<CommitResponse
, Void
>(future
) {
125 protected Void
wrap(CommitResponse ignore
) throws Exception
{
126 state
= TransactionState
.COMMITTED
;
131 protected Throwable
convertException(Throwable cause
) {
132 state
= TransactionState
.ERROR
;
137 txnStack
.remove(this);
142 public void rollback() {
143 FutureHelper
.quietGet(rollbackAsync());
147 public Future
<Void
> rollbackAsync() {
150 for (Future
<?
> f
: txnStack
.getFutures(this)) {
151 FutureHelper
.quietGet(f
);
153 Future
<ApiBasePb
.VoidProto
> future
= makeAsyncCall("Rollback", new ApiBasePb
.VoidProto());
154 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
155 return new FutureWrapper
<ApiBasePb
.VoidProto
, Void
>(future
) {
157 protected Void
wrap(ApiBasePb
.VoidProto ignore
) throws Exception
{
158 state
= TransactionState
.ROLLED_BACK
;
163 protected Throwable
convertException(Throwable cause
) {
164 state
= TransactionState
.ERROR
;
169 txnStack
.remove(this);
174 public String
getApp() {
179 public String
getId() {
180 return Long
.toString(getHandle());
184 public boolean equals(Object o
) {
188 if (o
== null || getClass() != o
.getClass()) {
192 TransactionImpl that
= (TransactionImpl
) o
;
194 return getHandle() == that
.getHandle();
198 public int hashCode() {
199 return (int) (getHandle() ^
(getHandle() >>> 32));
203 public String
toString() {
204 return "Txn [" + app
+ "." + getHandle() + ", " + state
+ "]";
208 public boolean isActive() {
209 return state
== TransactionState
.BEGUN
|| state
== TransactionState
.COMPLETION_IN_PROGRESS
;
213 public Transaction
getCurrentTransaction(Transaction defaultValue
) {
218 * If {@code txn} is not null and not active, throw
219 * {@link IllegalStateException}.
221 static void ensureTxnActive(Transaction txn
) {
222 if (txn
!= null && !txn
.isActive()) {
223 throw new IllegalStateException("Transaction with which this operation is "
224 + "associated is not active.");
228 private void ensureTxnStarted() {
229 if (state
!= TransactionState
.BEGUN
) {
230 throw new IllegalStateException("Transaction is in state " + state
+ ". There is no legal "
231 + "transition out of this state.");