Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / TransactionImpl.java
blobf07902e18e0b819e16de3c2f551ac1d1cd575972
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;
7 import java.util.List;
8 import java.util.concurrent.Future;
10 /**
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 {
23 /**
24 * Interface to a coupled object which handles the actual transaction RPCs
25 * and other service protocol dependent details.
27 interface InternalTransaction {
28 /**
29 * Issues an asynchronous RPC to commit this transaction.
31 Future<Void> doCommitAsync();
33 /**
34 * Issues an asynchronous RPC to rollback this transaction.
36 Future<Void> doRollbackAsync();
38 String getId();
40 @Override
41 boolean equals(Object o);
43 @Override
44 int hashCode();
47 enum TransactionState {
48 BEGUN,
49 COMPLETION_IN_PROGRESS,
50 COMMITTED,
51 ROLLED_BACK,
52 ERROR
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;
69 /**
70 * A {@link PostOpFuture} implementation that runs both post put and post
71 * delete callbacks.
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;
84 @Override
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) {
96 this.app = app;
97 this.txnStack = txnStack;
98 this.callbacks = callbacks;
99 this.entityCachingStrategy = entityCachingStrategy;
100 this.isExplicit = isExplicit;
101 this.internalTransaction = txnProvider;
104 @Override
105 public String getId() {
106 return internalTransaction.getId();
109 @Override
110 public boolean equals(Object o) {
111 if (o instanceof TransactionImpl) {
112 return internalTransaction.equals(((TransactionImpl) o).internalTransaction);
114 return false;
117 @Override
118 public int hashCode() {
119 return internalTransaction.hashCode();
122 @Override
123 public void commit() {
124 FutureHelper.quietGet(commitAsync());
127 @Override
128 public Future<Void> commitAsync() {
129 ensureTxnStarted();
130 try {
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) {
140 @Override
141 protected Void wrap(Void ignore) throws Exception {
142 state = TransactionState.COMMITTED;
143 return null;
146 @Override
147 protected Throwable convertException(Throwable cause) {
148 state = TransactionState.ERROR;
149 return cause;
152 result = entityCachingStrategy.createPostMutationFuture(result, preMutationCachingResult);
153 return new PostCommitFuture(txnStack.getPutEntities(this), txnStack.getDeletedKeys(this),
154 result);
155 } finally {
156 if (isExplicit) {
157 txnStack.remove(this);
162 @Override
163 public void rollback() {
164 FutureHelper.quietGet(rollbackAsync());
167 @Override
168 public Future<Void> rollbackAsync() {
169 ensureTxnStarted();
170 try {
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) {
177 @Override
178 protected Void wrap(Void ignore) throws Exception {
179 state = TransactionState.ROLLED_BACK;
180 return null;
183 @Override
184 protected Throwable convertException(Throwable cause) {
185 state = TransactionState.ERROR;
186 return cause;
189 } finally {
190 if (isExplicit) {
191 txnStack.remove(this);
196 @Override
197 public String getApp() {
198 return app;
201 @Override
202 public boolean isActive() {
203 return state == TransactionState.BEGUN || state == TransactionState.COMPLETION_IN_PROGRESS;
206 @Override
207 public Transaction getCurrentTransaction(Transaction defaultValue) {
208 return this;
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.");
229 @Override
230 public String toString() {
231 return "Txn [" + app + "." + getId() + ", " + state + "]";