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.transaction.local;
17  
18  import net.sf.ehcache.transaction.SoftLock;
19  import net.sf.ehcache.transaction.TransactionException;
20  import net.sf.ehcache.transaction.TransactionID;
21  import net.sf.ehcache.transaction.TransactionTimeoutException;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  /***
32   * A local transaction's thread context
33   *
34   * @author Ludovic Orban
35   */
36  public class LocalTransactionContext {
37  
38      private static final Logger LOG = LoggerFactory.getLogger(LocalTransactionContext.class.getName());
39      private static final long MILLISECONDS_PER_SECOND = 1000L;
40  
41      private boolean rollbackOnly;
42      private final long expirationTimestamp;
43      private final TransactionID transactionId;
44      private final Map<String, List<SoftLock>> softLockMap = new HashMap<String, List<SoftLock>>();
45      private final Map<String, LocalTransactionStore> storeMap = new HashMap<String, LocalTransactionStore>();
46      private final List<TransactionListener> listeners = new ArrayList<TransactionListener>();
47  
48      /***
49       * Create a new LocalTransactionContext
50       * @param transactionTimeout the timeout before the context expires
51       * @param transactionId the unique transaction ID of the context
52       */
53      public LocalTransactionContext(int transactionTimeout, TransactionID transactionId) {
54          this.expirationTimestamp = System.currentTimeMillis() + transactionTimeout * MILLISECONDS_PER_SECOND;
55          this.transactionId = transactionId;
56      }
57  
58      /***
59       * Get the timestamp at which this context will expire
60       * @return a timestamp in milliseconds at which this context will expire
61       */
62      public long getExpirationTimestamp() {
63          return expirationTimestamp;
64      }
65  
66      /***
67       * Check if the context timed out
68       * @return true if the context timed out, false otherwise
69       */
70      public boolean timedOut() {
71          return expirationTimestamp <= System.currentTimeMillis();
72      }
73  
74      /***
75       * Mark the context for rollback
76       */
77      public void setRollbackOnly() {
78          this.rollbackOnly = true;
79      }
80  
81      /***
82       * Register a soft lock in the context
83       * @param cacheName the name of the cache this soft lock is in
84       * @param store the LocalTransactionStore this soft lock is in
85       * @param softLock the soft lock
86       */
87      public void registerSoftLock(String cacheName, LocalTransactionStore store, SoftLock softLock) {
88          List<SoftLock> softLocks = softLockMap.get(cacheName);
89          if (softLocks == null) {
90              softLocks = new ArrayList<SoftLock>();
91              softLockMap.put(cacheName, softLocks);
92              storeMap.put(cacheName, store);
93          }
94          softLocks.add(softLock);
95      }
96  
97      //todo this method isn't needed if there is no copy on read/write in the underlying store
98      /***
99       * Update a soft lock already registered in the context
100      * @param cacheName the name of the cache this soft lock is in
101      * @param softLock the soft lock
102      */
103     public void updateSoftLock(String cacheName, SoftLock softLock) {
104         List<SoftLock> softLocks = softLockMap.get(cacheName);
105         softLocks.remove(softLock);
106         softLocks.add(softLock);
107     }
108 
109     /***
110      * Get all soft locks registered in this context for a specific cache
111      * @param cacheName the name of the cache
112      * @return a List of registered soft locks for this cache
113      */
114     public List<SoftLock> getSoftLocksForCache(String cacheName) {
115         List<SoftLock> softLocks = softLockMap.get(cacheName);
116         if (softLocks == null) {
117             return Collections.emptyList();
118         }
119 
120         return Collections.unmodifiableList(softLocks);
121     }
122 
123     /***
124      * Commit all work done in the context and release all registered soft locks
125      * @param ignoreTimeout true if commit should proceed no matter the timeout
126      */
127     public void commit(boolean ignoreTimeout) {
128         if (!ignoreTimeout && timedOut()) {
129             rollback();
130             throw new TransactionTimeoutException("transaction timed out, rolled back on commit");
131         }
132         if (rollbackOnly) {
133             rollback();
134             throw new TransactionException("transaction was marked as rollback only, rolled back on commit");
135         }
136 
137         try {
138             fireBeforeCommitEvent();
139             if (LOG.isDebugEnabled()) {
140                 LOG.debug("{} participating cache(s), committing transaction {}", softLockMap.keySet().size(), transactionId);
141             }
142             freeze();
143             transactionId.markForCommit();
144 
145             for (Map.Entry<String, List<SoftLock>> stringListEntry : softLockMap.entrySet()) {
146                 String cacheName = stringListEntry.getKey();
147                 LocalTransactionStore store = storeMap.get(cacheName);
148                 List<SoftLock> softLocks = stringListEntry.getValue();
149 
150                 LOG.debug("committing soft locked values of cache {}", cacheName);
151                 store.commit(softLocks);
152             }
153             LOG.debug("committed transaction {}", transactionId);
154         } finally {
155             unfreezeAndUnlock();
156             softLockMap.clear();
157             storeMap.clear();
158             fireAfterCommitEvent();
159         }
160     }
161 
162     /***
163      * Rollback all work done in the context and release all registered soft locks
164      */
165     public void rollback() {
166         try {
167             if (LOG.isDebugEnabled()) {
168                 LOG.debug("{} participating cache(s), rolling back transaction {}", softLockMap.keySet().size(), transactionId);
169             }
170             freeze();
171 
172             for (Map.Entry<String, List<SoftLock>> stringListEntry : softLockMap.entrySet()) {
173                 String cacheName = stringListEntry.getKey();
174                 LocalTransactionStore store = storeMap.get(cacheName);
175                 List<SoftLock> softLocks = stringListEntry.getValue();
176 
177                 LOG.debug("rolling back soft locked values of cache {}", cacheName);
178                 store.rollback(softLocks);
179             }
180             LOG.debug("rolled back transaction {}", transactionId);
181         } finally {
182             unfreezeAndUnlock();
183             softLockMap.clear();
184             storeMap.clear();
185             fireAfterRollbackEvent();
186         }
187     }
188 
189     /***
190      * Get the transaction ID of the context
191      * @return the transaction ID
192      */
193     public TransactionID getTransactionId() {
194         return transactionId;
195     }
196 
197     /***
198      * Add a TransactionListener to this context
199      * @param listener the listener
200      */
201     public void addListener(TransactionListener listener) {
202         this.listeners.add(listener);
203     }
204 
205     private void fireBeforeCommitEvent() {
206         for (TransactionListener listener : listeners) {
207             try {
208                 listener.beforeCommit();
209             } catch (Exception e) {
210                 LOG.error("beforeCommit error", e);
211             }
212         }
213     }
214 
215     private void fireAfterCommitEvent() {
216         for (TransactionListener listener : listeners) {
217             try {
218                 listener.afterCommit();
219             } catch (Exception e) {
220                 LOG.error("afterCommit error", e);
221             }
222         }
223     }
224 
225     private void fireAfterRollbackEvent() {
226         for (TransactionListener listener : listeners) {
227             try {
228                 listener.afterRollback();
229             } catch (Exception e) {
230                 LOG.error("afterRollback error", e);
231             }
232         }
233     }
234 
235     private void unfreezeAndUnlock() {
236         LOG.debug("unfreezing and unlocking {} soft lock(s)", softLockMap.size());
237         for (Map.Entry<String, List<SoftLock>> stringListEntry : softLockMap.entrySet()) {
238             List<SoftLock> softLocks = stringListEntry.getValue();
239 
240             for (SoftLock softLock : softLocks) {
241                 try {
242                     softLock.unfreeze();
243                     LOG.debug("unfroze {}", softLock);
244                 } catch (Exception e) {
245                     LOG.error("error unfreezing " + softLock, e);
246                 }
247                 try {
248                     softLock.unlock();
249                     LOG.debug("unlocked {}", softLock);
250                 } catch (Exception e) {
251                     LOG.error("error unlocking " + softLock, e);
252                 }
253             }
254         }
255     }
256 
257     private void freeze() {
258         for (Map.Entry<String, List<SoftLock>> stringListEntry : softLockMap.entrySet()) {
259             List<SoftLock> softLocks = stringListEntry.getValue();
260 
261             for (SoftLock softLock : softLocks) {
262                 softLock.freeze();
263             }
264         }
265     }
266 
267     /***
268      * {@inheritDoc}
269      */
270     @Override
271     public int hashCode() {
272         return transactionId.hashCode();
273     }
274 
275     /***
276      * {@inheritDoc}
277      */
278     @Override
279     public boolean equals(Object obj) {
280         if (obj instanceof LocalTransactionContext) {
281             LocalTransactionContext otherCtx = (LocalTransactionContext) obj;
282             return transactionId.equals(otherCtx.transactionId);
283         }
284         return false;
285     }
286 
287 }