1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.ehcache;
17
18 import net.sf.ehcache.transaction.TransactionIDFactory;
19 import net.sf.ehcache.transaction.TransactionTimeoutException;
20 import net.sf.ehcache.transaction.local.LocalTransactionContext;
21 import net.sf.ehcache.transaction.TransactionException;
22 import net.sf.ehcache.transaction.TransactionID;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25 import org.slf4j.MDC;
26
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap;
29 import java.util.concurrent.atomic.AtomicLong;
30
31 /***
32 * TransactionController is used to begin, commit and rollback local transactions
33 *
34 * @author Ludovic Orban
35 */
36 public final class TransactionController {
37
38 private static final Logger LOG = LoggerFactory.getLogger(TransactionController.class.getName());
39 private static final String MDC_KEY = "ehcache-txid";
40
41 private final ThreadLocal<TransactionID> currentTransactionIdThreadLocal = new ThreadLocal<TransactionID>();
42 private final ConcurrentMap<TransactionID, LocalTransactionContext> contextMap =
43 new ConcurrentHashMap<TransactionID, LocalTransactionContext>();
44 private final TransactionIDFactory transactionIDFactory;
45
46 private volatile int defaultTransactionTimeout;
47
48 private final TransactionControllerStatistics statistics = new TransactionControllerStatistics();
49
50 /***
51 * Create a TransactionController instance
52 * @param transactionIDFactory the TransactionIDFactory
53 * @param defaultTransactionTimeoutInSeconds the default transaction timeout in seconds
54 */
55 TransactionController(TransactionIDFactory transactionIDFactory, int defaultTransactionTimeoutInSeconds) {
56 this.transactionIDFactory = transactionIDFactory;
57 this.defaultTransactionTimeout = defaultTransactionTimeoutInSeconds;
58 }
59
60 /***
61 * Get the default transaction timeout in seconds
62 * @return the default transaction timeout
63 */
64 public int getDefaultTransactionTimeout() {
65 return defaultTransactionTimeout;
66 }
67
68 /***
69 * Set the default transaction timeout in seconds, it must be > 0
70 * @param defaultTransactionTimeoutSeconds the default transaction timeout
71 */
72 public void setDefaultTransactionTimeout(int defaultTransactionTimeoutSeconds) {
73 if (defaultTransactionTimeoutSeconds < 0) {
74 throw new IllegalArgumentException("timeout cannot be < 0");
75 }
76 this.defaultTransactionTimeout = defaultTransactionTimeoutSeconds;
77 }
78
79 /***
80 * Begin a new transaction and bind its context to the current thread
81 */
82 public void begin() {
83 begin(defaultTransactionTimeout);
84 }
85
86 /***
87 * Begin a new transaction with the specified timeout and bind its context to the current thread
88 * @param transactionTimeoutSeconds the timeout foe this transaction in seconds
89 */
90 public void begin(int transactionTimeoutSeconds) {
91 TransactionID txId = currentTransactionIdThreadLocal.get();
92 if (txId != null) {
93 throw new TransactionException("transaction already started");
94 }
95
96 LocalTransactionContext newTx = new LocalTransactionContext(transactionTimeoutSeconds, transactionIDFactory.createTransactionID());
97 contextMap.put(newTx.getTransactionId(), newTx);
98 currentTransactionIdThreadLocal.set(newTx.getTransactionId());
99
100 MDC.put(MDC_KEY, newTx.getTransactionId().toString());
101 LOG.debug("begun transaction {}", newTx.getTransactionId());
102 }
103
104 /***
105 * Commit the transaction bound to the current thread
106 */
107 public void commit() {
108 commit(false);
109 }
110
111 /***
112 * Commit the transaction bound to the current thread, ignoring if the transaction
113 * timed out
114 * @param ignoreTimeout true if the transaction should be committed no matter if it timed out or not
115 */
116 public void commit(boolean ignoreTimeout) {
117 TransactionID txId = currentTransactionIdThreadLocal.get();
118 if (txId == null) {
119 throw new TransactionException("no transaction started");
120 }
121
122 LocalTransactionContext currentTx = contextMap.get(txId);
123
124 try {
125 currentTx.commit(ignoreTimeout);
126 statistics.transactionCommitted();
127 } catch (TransactionTimeoutException tte) {
128 statistics.transactionTimedOut();
129 statistics.transactionRolledBack();
130 throw tte;
131 } catch (TransactionException te) {
132 statistics.transactionRolledBack();
133 throw te;
134 } finally {
135 contextMap.remove(txId);
136 currentTransactionIdThreadLocal.remove();
137 MDC.remove(MDC_KEY);
138 }
139 }
140
141 /***
142 * Rollback the transaction bound to the current thread
143 */
144 public void rollback() {
145 TransactionID txId = currentTransactionIdThreadLocal.get();
146 if (txId == null) {
147 throw new TransactionException("no transaction started");
148 }
149
150 LocalTransactionContext currentTx = contextMap.get(txId);
151
152 try {
153 currentTx.rollback();
154 statistics.transactionRolledBack();
155 } finally {
156 contextMap.remove(txId);
157 currentTransactionIdThreadLocal.remove();
158 MDC.remove(MDC_KEY);
159 }
160 }
161
162 /***
163 * Mark the transaction bound to the current thread for rollback only
164 */
165 public void setRollbackOnly() {
166 TransactionID txId = currentTransactionIdThreadLocal.get();
167 if (txId == null) {
168 throw new TransactionException("no transaction started");
169 }
170
171 LocalTransactionContext currentTx = contextMap.get(txId);
172
173 currentTx.setRollbackOnly();
174 }
175
176 /***
177 * Get the transaction context bond to the current thread
178 * @return the transaction context bond to the current thread or null if no transaction
179 * started on the current thread
180 */
181 public LocalTransactionContext getCurrentTransactionContext() {
182 TransactionID txId = currentTransactionIdThreadLocal.get();
183 if (txId == null) {
184 return null;
185 }
186 return contextMap.get(txId);
187 }
188
189 /***
190 * Get the committed transactions count
191 * @return the committed transactions count
192 */
193 public long getTransactionCommittedCount() {
194 return statistics.getTransactionCommittedCount();
195 }
196
197 /***
198 * Get the rolled back transactions count
199 * @return the rolled back transactions count
200 */
201 public long getTransactionRolledBackCount() {
202 return statistics.getTransactionRolledBackCount();
203 }
204
205 /***
206 * Get the timed out transactions count. Note that only transactions which failed to
207 * commit due to a timeout are taken into account
208 * @return the timed out transactions count
209 */
210 public long getTransactionTimedOutCount() {
211 return statistics.getTransactionTimedOutCount();
212 }
213
214
215 /***
216 * Holder for TransactionController statistics
217 */
218 private static class TransactionControllerStatistics {
219 private final AtomicLong committed = new AtomicLong();
220 private final AtomicLong rolledBack = new AtomicLong();
221 private final AtomicLong timedOut = new AtomicLong();
222
223 void transactionCommitted() {
224 committed.incrementAndGet();
225 }
226
227 void transactionRolledBack() {
228 rolledBack.incrementAndGet();
229 }
230
231 void transactionTimedOut() {
232 timedOut.incrementAndGet();
233 }
234
235 long getTransactionCommittedCount() {
236 return committed.get();
237 }
238
239 long getTransactionRolledBackCount() {
240 return rolledBack.get();
241 }
242
243 long getTransactionTimedOutCount() {
244 return timedOut.get();
245 }
246 }
247
248 }