1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.transaction.memory;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26
27 import javax.transaction.Status;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class TransactionalMapWrapper implements Map, Status {
49
50
51 protected Map wrapped;
52
53
54 protected MapFactory mapFactory;
55
56 protected SetFactory setFactory;
57
58 private ThreadLocal activeTx = new ThreadLocal();
59
60
61
62
63
64
65
66 public TransactionalMapWrapper(Map wrapped) {
67 this(wrapped, new HashMapFactory(), new HashSetFactory());
68 }
69
70
71
72
73
74
75
76
77
78 public TransactionalMapWrapper(Map wrapped, MapFactory mapFactory, SetFactory setFactory) {
79 this.wrapped = Collections.synchronizedMap(wrapped);
80 this.mapFactory = mapFactory;
81 this.setFactory = setFactory;
82 }
83
84
85
86
87
88
89
90 public boolean isReadOnly() {
91 TxContext txContext = getActiveTx();
92
93 if (txContext == null) {
94 throw new IllegalStateException(
95 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
96 }
97
98 return txContext.readOnly;
99 }
100
101
102
103
104
105
106
107
108
109
110
111 public boolean isTransactionMarkedForRollback() {
112 TxContext txContext = getActiveTx();
113
114 if (txContext == null) {
115 throw new IllegalStateException(
116 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
117 }
118
119 return (txContext.status == Status.STATUS_MARKED_ROLLBACK);
120 }
121
122
123
124
125
126
127 public void markTransactionForRollback() {
128 TxContext txContext = getActiveTx();
129
130 if (txContext == null) {
131 throw new IllegalStateException(
132 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
133 }
134
135 txContext.status = Status.STATUS_MARKED_ROLLBACK;
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 public TxContext suspendTransaction() {
154 TxContext txContext = getActiveTx();
155
156 if (txContext == null) {
157 throw new IllegalStateException(
158 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
159 }
160
161 txContext.suspended = true;
162 setActiveTx(null);
163 return txContext;
164 }
165
166
167
168
169
170
171
172
173 public void resumeTransaction(TxContext suspendedTx) {
174 if (getActiveTx() != null) {
175 throw new IllegalStateException(
176 "Active thread " + Thread.currentThread() + " already associated with a transaction!");
177 }
178
179 if (suspendedTx == null) {
180 throw new IllegalStateException("No transaction to resume!");
181 }
182
183 if (!suspendedTx.suspended) {
184 throw new IllegalStateException("Transaction to resume needs to be suspended!");
185 }
186
187 suspendedTx.suspended = false;
188 setActiveTx(suspendedTx);
189 }
190
191
192
193
194
195
196 public int getTransactionState() {
197 TxContext txContext = getActiveTx();
198
199 if (txContext == null) {
200 return STATUS_NO_TRANSACTION;
201 }
202 return txContext.status;
203 }
204
205
206
207
208
209
210
211
212
213
214
215
216
217 public void startTransaction() {
218 if (getActiveTx() != null) {
219 throw new IllegalStateException(
220 "Active thread " + Thread.currentThread() + " already associated with a transaction!");
221 }
222 setActiveTx(new TxContext());
223 }
224
225
226
227
228
229
230
231
232 public void rollbackTransaction() {
233 TxContext txContext = getActiveTx();
234
235 if (txContext == null) {
236 throw new IllegalStateException(
237 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
238 }
239
240
241 txContext.dispose();
242 setActiveTx(null);
243 }
244
245
246
247
248
249
250
251
252 public void commitTransaction() {
253 TxContext txContext = getActiveTx();
254
255 if (txContext == null) {
256 throw new IllegalStateException(
257 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
258 }
259
260 if (txContext.status == Status.STATUS_MARKED_ROLLBACK) {
261 throw new IllegalStateException("Active thread " + Thread.currentThread() + " is marked for rollback!");
262 }
263
264 txContext.merge();
265 txContext.dispose();
266 setActiveTx(null);
267 }
268
269
270
271
272
273
274
275
276 public void clear() {
277 TxContext txContext = getActiveTx();
278 if (txContext != null) {
279 txContext.clear();
280 } else {
281 wrapped.clear();
282 }
283 }
284
285
286
287
288 public int size() {
289 TxContext txContext = getActiveTx();
290 if (txContext != null) {
291 return txContext.size();
292 } else {
293 return wrapped.size();
294 }
295 }
296
297
298
299
300 public boolean isEmpty() {
301 TxContext txContext = getActiveTx();
302 if (txContext == null) {
303 return wrapped.isEmpty();
304 } else {
305 return txContext.isEmpty();
306 }
307 }
308
309
310
311
312 public boolean containsKey(Object key) {
313 return keySet().contains(key);
314 }
315
316
317
318
319 public boolean containsValue(Object value) {
320 TxContext txContext = getActiveTx();
321
322 if (txContext == null) {
323 return wrapped.containsValue(value);
324 } else {
325 return values().contains(value);
326 }
327 }
328
329
330
331
332 public Collection values() {
333
334 TxContext txContext = getActiveTx();
335
336 if (txContext == null) {
337 return wrapped.values();
338 } else {
339
340 Collection values = new ArrayList();
341 for (Iterator it = keySet().iterator(); it.hasNext();) {
342 Object key = it.next();
343 Object value = get(key);
344
345 if (value != null) {
346 values.add(value);
347 }
348 }
349 return values;
350 }
351 }
352
353
354
355
356 public void putAll(Map map) {
357 TxContext txContext = getActiveTx();
358
359 if (txContext == null) {
360 wrapped.putAll(map);
361 } else {
362 for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
363 Map.Entry entry = (Map.Entry) it.next();
364 txContext.put(entry.getKey(), entry.getValue());
365 }
366 }
367 }
368
369
370
371
372 public Set entrySet() {
373 TxContext txContext = getActiveTx();
374 if (txContext == null) {
375 return wrapped.entrySet();
376 } else {
377 Set entrySet = new HashSet();
378
379 for (Iterator it = keySet().iterator(); it.hasNext();) {
380 Object key = it.next();
381 Object value = get(key);
382
383 if (value != null) {
384 entrySet.add(new HashEntry(key, value));
385 }
386 }
387 return entrySet;
388 }
389 }
390
391
392
393
394 public Set keySet() {
395 TxContext txContext = getActiveTx();
396
397 if (txContext == null) {
398 return wrapped.keySet();
399 } else {
400 return txContext.keys();
401 }
402 }
403
404
405
406
407 public Object get(Object key) {
408 TxContext txContext = getActiveTx();
409
410 if (txContext != null) {
411 return txContext.get(key);
412 } else {
413 return wrapped.get(key);
414 }
415 }
416
417
418
419
420 public Object remove(Object key) {
421 TxContext txContext = getActiveTx();
422
423 if (txContext == null) {
424 return wrapped.remove(key);
425 } else {
426 Object oldValue = get(key);
427 txContext.remove(key);
428 return oldValue;
429 }
430 }
431
432
433
434
435 public Object put(Object key, Object value) {
436 TxContext txContext = getActiveTx();
437
438 if (txContext == null) {
439 return wrapped.put(key, value);
440 } else {
441 Object oldValue = get(key);
442 txContext.put(key, value);
443 return oldValue;
444 }
445
446 }
447
448 protected TxContext getActiveTx() {
449 return (TxContext) activeTx.get();
450 }
451
452 protected void setActiveTx(TxContext txContext) {
453 activeTx.set(txContext);
454 }
455
456
457 protected static class HashEntry implements Map.Entry {
458
459 protected Object key;
460
461 protected Object value;
462
463 protected HashEntry(Object key, Object value) {
464 this.key = key;
465 this.value = value;
466 }
467
468 public Object getKey() {
469 return key;
470 }
471
472 public Object getValue() {
473 return value;
474 }
475
476 public Object setValue(Object value) {
477 Object old = this.value;
478 this.value = value;
479 return old;
480 }
481
482 public boolean equals(Object obj) {
483 if (obj == this) {
484 return true;
485 }
486 if (!(obj instanceof Map.Entry)) {
487 return false;
488 }
489 Map.Entry other = (Map.Entry) obj;
490 return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()))
491 && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
492 }
493
494 public int hashCode() {
495 return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode());
496 }
497
498 public String toString() {
499 return new StringBuffer().append(getKey()).append('=').append(getValue()).toString();
500 }
501 }
502
503 public class TxContext {
504 protected Set deletes;
505 protected Map changes;
506 protected Map adds;
507 protected int status;
508 protected boolean cleared;
509 protected boolean readOnly;
510 protected boolean suspended = false;
511
512 protected TxContext() {
513 deletes = setFactory.createSet();
514 changes = mapFactory.createMap();
515 adds = mapFactory.createMap();
516 status = Status.STATUS_ACTIVE;
517 cleared = false;
518 readOnly = true;
519 }
520
521 protected Set keys() {
522 Set keySet = new HashSet();
523 if (!cleared) {
524 keySet.addAll(wrapped.keySet());
525 keySet.removeAll(deletes);
526 }
527 keySet.addAll(adds.keySet());
528 return keySet;
529 }
530
531 protected Object get(Object key) {
532
533 if (deletes.contains(key)) {
534
535 return null;
536 }
537
538 if(changes.containsKey(key)){
539 return changes.get(key);
540 }
541
542 if(adds.containsKey(key)){
543 return adds.get(key);
544 }
545
546 if (cleared) {
547 return null;
548 } else {
549
550 return wrapped.get(key);
551 }
552 }
553
554 protected void put(Object key, Object value) {
555 try {
556 readOnly = false;
557 deletes.remove(key);
558 if (wrapped.containsKey(key)) {
559 changes.put(key, value);
560 } else {
561 adds.put(key, value);
562 }
563 } catch (RuntimeException e) {
564 status = Status.STATUS_MARKED_ROLLBACK;
565 throw e;
566 } catch (Error e) {
567 status = Status.STATUS_MARKED_ROLLBACK;
568 throw e;
569 }
570 }
571
572 protected void remove(Object key) {
573
574 try {
575 readOnly = false;
576 changes.remove(key);
577 adds.remove(key);
578 if (wrapped.containsKey(key) && !cleared) {
579 deletes.add(key);
580 }
581 } catch (RuntimeException e) {
582 status = Status.STATUS_MARKED_ROLLBACK;
583 throw e;
584 } catch (Error e) {
585 status = Status.STATUS_MARKED_ROLLBACK;
586 throw e;
587 }
588 }
589
590 protected int size() {
591 int size = (cleared ? 0 : wrapped.size());
592
593 size -= deletes.size();
594 size += adds.size();
595
596 return size;
597 }
598
599 protected void clear() {
600 readOnly = false;
601 cleared = true;
602 deletes.clear();
603 changes.clear();
604 adds.clear();
605 }
606
607 protected boolean isEmpty() {
608 return (size() == 0);
609 }
610
611 protected void merge() {
612 if (!readOnly) {
613
614 if (cleared) {
615 wrapped.clear();
616 }
617
618 wrapped.putAll(changes);
619 wrapped.putAll(adds);
620
621 for (Iterator it = deletes.iterator(); it.hasNext();) {
622 Object key = it.next();
623 wrapped.remove(key);
624 }
625 }
626 }
627
628 protected void dispose() {
629 setFactory.disposeSet(deletes);
630 deletes = null;
631 mapFactory.disposeMap(changes);
632 changes = null;
633 mapFactory.disposeMap(adds);
634 adds = null;
635 status = Status.STATUS_NO_TRANSACTION;
636 }
637 }
638 }