View Javadoc

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 }