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
;
13 import java
.util
.NoSuchElementException
;
14 import java
.util
.concurrent
.Future
;
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());
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
) {
44 public void push(Transaction txn
) {
46 throw new NullPointerException("txn cannot be null");
48 getStack().txns
.addFirst(txn
);
53 Transaction txn
= getStack().txns
.removeFirst();
54 getStack().txnIdToTransactionData
.remove(txn
.getId());
56 } catch (NoSuchElementException e
) {
57 throw new IllegalStateException(e
);
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());
71 public Transaction
peek() {
73 return getStack().txns
.getFirst();
74 } catch (NoSuchElementException e
) {
75 throw new IllegalStateException(e
);
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
;
87 public Collection
<Transaction
> getAll() {
88 return new ArrayList
<Transaction
>(getStack().txns
);
91 TransactionDataMap
getStack() {
96 public void clearAll() {
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());
109 data
= new TransactionData();
110 txnDataMap
.txnIdToTransactionData
.put(txn
.getId(), data
);
116 public LinkedHashSet
<Future
<?
>> getFutures(Transaction txn
) {
117 return getTransactionData(txn
).futures
;
121 public void addPutEntities(Transaction txn
, List
<Entity
> putEntities
) {
122 getTransactionData(txn
).puts
.addAll(putEntities
);
126 public void addDeletedKeys(Transaction txn
, List
<Key
> deletedKeys
) {
127 getTransactionData(txn
).deletes
.addAll(deletedKeys
);
131 public List
<Entity
> getPutEntities(Transaction txn
) {
132 return getTransactionData(txn
).puts
;
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
148 interface ThreadLocalTransactionStack
{
150 TransactionDataMap
get();
152 class StaticMember
implements ThreadLocalTransactionStack
{
153 private static final ThreadLocal
<TransactionDataMap
> STACK
=
154 new ThreadLocal
<TransactionDataMap
>() {
156 protected TransactionDataMap
initialValue() {
157 return new TransactionDataMap();
162 public TransactionDataMap
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
>();
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();