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
;
8 import java
.util
.concurrent
.Future
;
11 * State and behavior that is common to all {@link Transaction} implementations.
13 * Our implementation is implicitly async. BeginTransaction RPCs always return
14 * instantly, and this class maintains a reference to the {@link Future}
15 * associated with the RPC. We service as much of the {@link Transaction}
16 * interface as we can without retrieving the result of the future.
18 * There is no synchronization in this code because transactions are associated
19 * with a single thread and are documented as such.
21 class TransactionImpl
implements Transaction
, CurrentTransactionProvider
{
24 * Interface to a coupled object which handles the actual transaction RPCs
25 * and other service protocol dependent details.
27 interface InternalTransaction
{
29 * Issues an asynchronous RPC to commit this transaction.
31 Future
<Void
> doCommitAsync();
34 * Issues an asynchronous RPC to rollback this transaction.
36 Future
<Void
> doRollbackAsync();
41 boolean equals(Object o
);
47 enum TransactionState
{
49 COMPLETION_IN_PROGRESS
,
55 private final String app
;
57 private final TransactionStack txnStack
;
59 private final DatastoreCallbacks callbacks
;
61 private final EntityCachingStrategy entityCachingStrategy
;
63 private final boolean isExplicit
;
65 private final InternalTransaction internalTransaction
;
67 TransactionState state
= TransactionState
.BEGUN
;
70 * A {@link PostOpFuture} implementation that runs both post put and post
73 private class PostCommitFuture
extends PostOpFuture
<Void
> {
74 private final List
<Entity
> putEntities
;
75 private final List
<Key
> deletedKeys
;
77 private PostCommitFuture(
78 List
<Entity
> putEntities
, List
<Key
> deletedKeys
, Future
<Void
> delegate
) {
79 super(delegate
, callbacks
);
80 this.putEntities
= putEntities
;
81 this.deletedKeys
= deletedKeys
;
85 void executeCallbacks(Void ignoreMe
) {
86 PutContext putContext
= new PutContext(TransactionImpl
.this, putEntities
);
87 callbacks
.executePostPutCallbacks(putContext
);
88 DeleteContext deleteContext
= new DeleteContext(TransactionImpl
.this, deletedKeys
);
89 callbacks
.executePostDeleteCallbacks(deleteContext
);
93 TransactionImpl(String app
, TransactionStack txnStack
,
94 DatastoreCallbacks callbacks
, EntityCachingStrategy entityCachingStrategy
,
95 boolean isExplicit
, InternalTransaction txnProvider
) {
97 this.txnStack
= txnStack
;
98 this.callbacks
= callbacks
;
99 this.entityCachingStrategy
= entityCachingStrategy
;
100 this.isExplicit
= isExplicit
;
101 this.internalTransaction
= txnProvider
;
105 public String
getId() {
106 return internalTransaction
.getId();
110 public boolean equals(Object o
) {
111 if (o
instanceof TransactionImpl
) {
112 return internalTransaction
.equals(((TransactionImpl
) o
).internalTransaction
);
118 public int hashCode() {
119 return internalTransaction
.hashCode();
123 public void commit() {
124 FutureHelper
.quietGet(commitAsync());
128 public Future
<Void
> commitAsync() {
131 for (Future
<?
> f
: txnStack
.getFutures(this)) {
132 FutureHelper
.quietGet(f
);
134 PreMutationCachingResult preMutationCachingResult
=
135 entityCachingStrategy
.preCommit(txnStack
.getPutEntities(this),
136 txnStack
.getDeletedKeys(this));
137 Future
<Void
> commitResponse
= internalTransaction
.doCommitAsync();
138 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
139 Future
<Void
> result
= new FutureWrapper
<Void
, Void
>(commitResponse
) {
141 protected Void
wrap(Void ignore
) throws Exception
{
142 state
= TransactionState
.COMMITTED
;
147 protected Throwable
convertException(Throwable cause
) {
148 state
= TransactionState
.ERROR
;
152 result
= entityCachingStrategy
.createPostMutationFuture(result
, preMutationCachingResult
);
153 return new PostCommitFuture(txnStack
.getPutEntities(this), txnStack
.getDeletedKeys(this),
157 txnStack
.remove(this);
163 public void rollback() {
164 FutureHelper
.quietGet(rollbackAsync());
168 public Future
<Void
> rollbackAsync() {
171 for (Future
<?
> f
: txnStack
.getFutures(this)) {
172 FutureHelper
.quietGet(f
);
174 Future
<Void
> future
= internalTransaction
.doRollbackAsync();
175 state
= TransactionState
.COMPLETION_IN_PROGRESS
;
176 return new FutureWrapper
<Void
, Void
>(future
) {
178 protected Void
wrap(Void ignore
) throws Exception
{
179 state
= TransactionState
.ROLLED_BACK
;
184 protected Throwable
convertException(Throwable cause
) {
185 state
= TransactionState
.ERROR
;
191 txnStack
.remove(this);
197 public String
getApp() {
202 public boolean isActive() {
203 return state
== TransactionState
.BEGUN
|| state
== TransactionState
.COMPLETION_IN_PROGRESS
;
207 public Transaction
getCurrentTransaction(Transaction defaultValue
) {
212 * If {@code txn} is not null and not active, throw
213 * {@link IllegalStateException}.
215 static void ensureTxnActive(Transaction txn
) {
216 if (txn
!= null && !txn
.isActive()) {
217 throw new IllegalStateException("Transaction with which this operation is "
218 + "associated is not active.");
222 private void ensureTxnStarted() {
223 if (state
!= TransactionState
.BEGUN
) {
224 throw new IllegalStateException("Transaction is in state " + state
+ ". There is no legal "
225 + "transition out of this state.");
230 public String
toString() {
231 return "Txn [" + app
+ "." + getId() + ", " + state
+ "]";