Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / TransactionStackImpl.java
blob201834a5d9d1c42991c29d2e846e9c665521ab1c
1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com.google.appengine.api.datastore;
4 import com.google.common.collect.Lists;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.HashMap;
9 import java.util.LinkedHashSet;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.NoSuchElementException;
14 import java.util.concurrent.Future;
16 /**
17 * For now each thread is going to have its own stack. This prevents users
18 * from sharing a transaction across threads and also prevents users from
19 * reliably sharing a transaction across requests that happen to be serviced
20 * by the same thread. When we start allowing users to create threads
21 * we could change this implementation to allow transactions to be shared
22 * across threads, but there's little point in supporting this now.
25 class TransactionStackImpl implements TransactionStack {
27 private final ThreadLocalTransactionStack stack;
29 public TransactionStackImpl() {
30 this(new ThreadLocalTransactionStack.StaticMember());
33 /**
34 * Just for testing. Gives tests the opportunity to use some other
35 * implementation of {@link ThreadLocalTransactionStack} that doesn't
36 * maintain state across instances like
37 * {@link ThreadLocalTransactionStack.StaticMember} does.
39 TransactionStackImpl(ThreadLocalTransactionStack stack) {
40 this.stack = stack;
43 @Override
44 public void push(Transaction txn) {
45 if (txn == null) {
46 throw new NullPointerException("txn cannot be null");
48 getStack().txns.addFirst(txn);
51 Transaction pop() {
52 try {
53 Transaction txn = getStack().txns.removeFirst();
54 getStack().txnIdToTransactionData.remove(txn.getId());
55 return txn;
56 } catch (NoSuchElementException e) {
57 throw new IllegalStateException(e);
61 @Override
62 public void remove(Transaction txn) {
63 if (!getStack().txns.remove(txn)) {
64 throw new IllegalStateException(
65 "Attempted to deregister a transaction that is not currently registered.");
67 getStack().txnIdToTransactionData.remove(txn.getId());
70 @Override
71 public Transaction peek() {
72 try {
73 return getStack().txns.getFirst();
74 } catch (NoSuchElementException e) {
75 throw new IllegalStateException(e);
79 @Override
80 public Transaction peek(Transaction returnedIfNoTxn) {
81 LinkedList<Transaction> txns = getStack().txns;
82 Transaction txn = txns.isEmpty() ? null : txns.peek();
83 return txn == null ? returnedIfNoTxn : txn;
86 @Override
87 public Collection<Transaction> getAll() {
88 return new ArrayList<Transaction>(getStack().txns);
91 TransactionDataMap getStack() {
92 return stack.get();
95 @Override
96 public void clearAll() {
97 getStack().clear();
100 @Override
101 public void addFuture(Transaction txn, Future<?> future) {
102 getFutures(txn).add(future);
105 private TransactionData getTransactionData(Transaction txn) {
106 TransactionDataMap txnDataMap = getStack();
107 TransactionData data = txnDataMap.txnIdToTransactionData.get(txn.getId());
108 if (data == null) {
109 data = new TransactionData();
110 txnDataMap.txnIdToTransactionData.put(txn.getId(), data);
112 return data;
115 @Override
116 public LinkedHashSet<Future<?>> getFutures(Transaction txn) {
117 return getTransactionData(txn).futures;
120 @Override
121 public void addPutEntities(Transaction txn, List<Entity> putEntities) {
122 getTransactionData(txn).puts.addAll(putEntities);
125 @Override
126 public void addDeletedKeys(Transaction txn, List<Key> deletedKeys) {
127 getTransactionData(txn).deletes.addAll(deletedKeys);
130 @Override
131 public List<Entity> getPutEntities(Transaction txn) {
132 return getTransactionData(txn).puts;
135 @Override
136 public List<Key> getDeletedKeys(Transaction txn) {
137 return getTransactionData(txn).deletes;
141 * A wrapper for a ThreadLocal<TransactionDataMap> that gives us
142 * flexibility in terms of the lifecycle of the ThreadLocal values. This
143 * just exists so that our production code can use a static member and our
144 * test code can use an instance member (it's easy to end up with flaky tests
145 * when your tests rely on static members because it's too easy to forget to
146 * clear them out).
148 interface ThreadLocalTransactionStack {
150 TransactionDataMap get();
152 class StaticMember implements ThreadLocalTransactionStack {
153 private static final ThreadLocal<TransactionDataMap> STACK =
154 new ThreadLocal<TransactionDataMap>() {
155 @Override
156 protected TransactionDataMap initialValue() {
157 return new TransactionDataMap();
161 @Override
162 public TransactionDataMap get() {
163 return STACK.get();
169 * Associates a list of {@link Transaction Transactions} (the stack) with a
170 * map that ties transaction ids to its associated {@link TransactionData}.
171 * Given a given Transaction in the list we can find:
172 * The Futures whose completion must be blocked on when committing or
173 * rolling back the Transaction can be found.
174 * The entities that have been put and deleted as part of the Transaction
175 * (used for post op callbacks).
177 static final class TransactionDataMap {
178 final LinkedList<Transaction> txns = new LinkedList<Transaction>();
179 final Map<String, TransactionData> txnIdToTransactionData =
180 new HashMap<String, TransactionData>();
182 void clear() {
183 txns.clear();
184 txnIdToTransactionData.clear();
189 * Data associated with a Transaction. We can't store this data inside
190 * {@link TransactionImpl} because of an API design mistake we made very
191 * early on - making Transaction an interface and allowing users to pass
192 * arbitrary implementations to us without checking the runtime type.
194 static final class TransactionData {
195 final LinkedHashSet<Future<?>> futures = new LinkedHashSet<Future<?>>();
196 final List<Key> deletes = Lists.newArrayList();
197 final List<Entity> puts = Lists.newArrayList();