App Engine Java SDK version 1.9.8
[gae.git] / java / src / main / com / google / appengine / api / datastore / TransactionImpl.java
blob1d1739ed4772eb77c40d29794b343f868dd0358b
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;
8 import java.util.List;
9 import java.util.concurrent.Future;
11 /**
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 {
24 /**
25 * Interface to a coupled object which handles the actual transaction RPCs
26 * and other service protocol dependent details.
28 interface InternalTransaction {
29 /**
30 * Issues an asynchronous RPC to commit this transaction.
32 Future<Void> doCommitAsync();
34 /**
35 * Issues an asynchronous RPC to rollback this transaction.
37 Future<Void> doRollbackAsync();
39 String getId();
41 @Override
42 boolean equals(Object o);
44 @Override
45 int hashCode();
48 enum TransactionState {
49 BEGUN,
50 COMPLETION_IN_PROGRESS,
51 COMMITTED,
52 ROLLED_BACK,
53 ERROR
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;
72 /**
73 * A {@link PostOpFuture} implementation that runs both post put and post
74 * delete callbacks.
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;
87 @Override
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;
100 this.app = app;
101 this.txnStack = txnStack;
102 this.callbacks = callbacks;
103 this.entityCachingStrategy = entityCachingStrategy;
104 this.isExplicit = isExplicit;
105 this.internalTransaction = txnProvider;
108 @Override
109 public String getId() {
110 return internalTransaction.getId();
113 @Override
114 public boolean equals(Object o) {
115 if (o instanceof TransactionImpl) {
116 return internalTransaction.equals(((TransactionImpl) o).internalTransaction);
118 return false;
121 @Override
122 public int hashCode() {
123 return internalTransaction.hashCode();
126 @Override
127 public void commit() {
128 FutureHelper.quietGet(commitAsync());
131 @Override
132 public Future<Void> commitAsync() {
133 ensureTxnStarted();
134 try {
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) {
144 @Override
145 protected Void wrap(Void ignore) throws Exception {
146 state = TransactionState.COMMITTED;
147 return null;
150 @Override
151 protected Throwable convertException(Throwable cause) {
152 state = TransactionState.ERROR;
153 return cause;
156 result = entityCachingStrategy.createPostMutationFuture(result, preMutationCachingResult);
157 return new PostCommitFuture(txnStack.getPutEntities(this), txnStack.getDeletedKeys(this),
158 result);
159 } finally {
160 if (isExplicit) {
161 txnStack.remove(this);
166 @Override
167 public void rollback() {
168 FutureHelper.quietGet(rollbackAsync());
171 @Override
172 public Future<Void> rollbackAsync() {
173 ensureTxnStarted();
174 try {
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) {
181 @Override
182 protected Void wrap(Void ignore) throws Exception {
183 state = TransactionState.ROLLED_BACK;
184 return null;
187 @Override
188 protected Throwable convertException(Throwable cause) {
189 state = TransactionState.ERROR;
190 return cause;
193 } finally {
194 if (isExplicit) {
195 txnStack.remove(this);
200 @Override
201 public String getApp() {
202 return app;
205 @Override
206 public boolean isActive() {
207 return state == TransactionState.BEGUN || state == TransactionState.COMPLETION_IN_PROGRESS;
210 @Override
211 public Transaction getCurrentTransaction(Transaction defaultValue) {
212 return this;
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.");
233 @Override
234 public String toString() {
235 return "Txn [" + app + "." + getId() + ", " + state + "]";