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
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 }