1
16 package net.sf.ehcache;
17
18 import net.sf.ehcache.transaction.TransactionException;
19 import net.sf.ehcache.transaction.TransactionID;
20 import net.sf.ehcache.transaction.TransactionIDFactory;
21 import net.sf.ehcache.transaction.TransactionTimeoutException;
22 import net.sf.ehcache.transaction.local.LocalRecoveryManager;
23 import net.sf.ehcache.transaction.local.LocalTransactionContext;
24 import net.sf.ehcache.util.lang.VicariousThreadLocal;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.slf4j.MDC;
28
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.atomic.AtomicLong;
32
33
38 public final class TransactionController {
39
40 private static final Logger LOG = LoggerFactory.getLogger(TransactionController.class.getName());
41 private static final String MDC_KEY = "ehcache-txid";
42
43 private final VicariousThreadLocal<TransactionID> currentTransactionIdThreadLocal = new VicariousThreadLocal<TransactionID>();
44 private final ConcurrentMap<TransactionID, LocalTransactionContext> contextMap =
45 new ConcurrentHashMap<TransactionID, LocalTransactionContext>();
46 private final TransactionIDFactory transactionIDFactory;
47 private final LocalRecoveryManager localRecoveryManager;
48
49 private volatile int defaultTransactionTimeout;
50
51 private final TransactionControllerStatistics statistics = new TransactionControllerStatistics();
52
53
58 TransactionController(TransactionIDFactory transactionIDFactory, int defaultTransactionTimeoutInSeconds) {
59 this.transactionIDFactory = transactionIDFactory;
60 this.localRecoveryManager = new LocalRecoveryManager(transactionIDFactory);
61 this.defaultTransactionTimeout = defaultTransactionTimeoutInSeconds;
62 }
63
64
68 public int getDefaultTransactionTimeout() {
69 return defaultTransactionTimeout;
70 }
71
72
76 public void setDefaultTransactionTimeout(int defaultTransactionTimeoutSeconds) {
77 if (defaultTransactionTimeoutSeconds < 0) {
78 throw new IllegalArgumentException("timeout cannot be < 0");
79 }
80 this.defaultTransactionTimeout = defaultTransactionTimeoutSeconds;
81 }
82
83
86 public void begin() {
87 begin(defaultTransactionTimeout);
88 }
89
90
94 public void begin(int transactionTimeoutSeconds) {
95 TransactionID txId = currentTransactionIdThreadLocal.get();
96 if (txId != null) {
97 throw new TransactionException("transaction already started");
98 }
99
100 LocalTransactionContext newTx = new LocalTransactionContext(transactionTimeoutSeconds, transactionIDFactory);
101 contextMap.put(newTx.getTransactionId(), newTx);
102 currentTransactionIdThreadLocal.set(newTx.getTransactionId());
103
104 MDC.put(MDC_KEY, newTx.getTransactionId().toString());
105 LOG.debug("begun transaction {}", newTx.getTransactionId());
106 }
107
108
111 public void commit() {
112 commit(false);
113 }
114
115
120 public void commit(boolean ignoreTimeout) {
121 TransactionID txId = currentTransactionIdThreadLocal.get();
122 if (txId == null) {
123 throw new TransactionException("no transaction started");
124 }
125
126 LocalTransactionContext currentTx = contextMap.get(txId);
127
128 try {
129 currentTx.commit(ignoreTimeout);
130 statistics.transactionCommitted();
131 } catch (TransactionTimeoutException tte) {
132 statistics.transactionTimedOut();
133 statistics.transactionRolledBack();
134 throw tte;
135 } catch (TransactionException te) {
136 statistics.transactionRolledBack();
137 throw te;
138 } finally {
139 contextMap.remove(txId);
140 transactionIDFactory.clear(txId);
141 currentTransactionIdThreadLocal.remove();
142 MDC.remove(MDC_KEY);
143 }
144 }
145
146
149 public void rollback() {
150 TransactionID txId = currentTransactionIdThreadLocal.get();
151 if (txId == null) {
152 throw new TransactionException("no transaction started");
153 }
154
155 LocalTransactionContext currentTx = contextMap.get(txId);
156
157 try {
158 currentTx.rollback();
159 statistics.transactionRolledBack();
160 } finally {
161 contextMap.remove(txId);
162 transactionIDFactory.clear(txId);
163 currentTransactionIdThreadLocal.remove();
164 MDC.remove(MDC_KEY);
165 }
166 }
167
168
171 public void setRollbackOnly() {
172 TransactionID txId = currentTransactionIdThreadLocal.get();
173 if (txId == null) {
174 throw new TransactionException("no transaction started");
175 }
176
177 LocalTransactionContext currentTx = contextMap.get(txId);
178
179 currentTx.setRollbackOnly();
180 }
181
182
187 public LocalTransactionContext getCurrentTransactionContext() {
188 TransactionID txId = currentTransactionIdThreadLocal.get();
189 if (txId == null) {
190 return null;
191 }
192 return contextMap.get(txId);
193 }
194
195
199 public long getTransactionCommittedCount() {
200 return statistics.getTransactionCommittedCount();
201 }
202
203
207 public long getTransactionRolledBackCount() {
208 return statistics.getTransactionRolledBackCount();
209 }
210
211
216 public long getTransactionTimedOutCount() {
217 return statistics.getTransactionTimedOutCount();
218 }
219
220
221
225 public LocalRecoveryManager getRecoveryManager() {
226 return localRecoveryManager;
227 }
228
229
232 private static class TransactionControllerStatistics {
233 private final AtomicLong committed = new AtomicLong();
234 private final AtomicLong rolledBack = new AtomicLong();
235 private final AtomicLong timedOut = new AtomicLong();
236
237 void transactionCommitted() {
238 committed.incrementAndGet();
239 }
240
241 void transactionRolledBack() {
242 rolledBack.incrementAndGet();
243 }
244
245 void transactionTimedOut() {
246 timedOut.incrementAndGet();
247 }
248
249 long getTransactionCommittedCount() {
250 return committed.get();
251 }
252
253 long getTransactionRolledBackCount() {
254 return rolledBack.get();
255 }
256
257 long getTransactionTimedOutCount() {
258 return timedOut.get();
259 }
260 }
261
262 }
263