KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > transaction > memory > TransactionalMapWrapper


1 /*
2  * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//transaction/src/java/org/apache/commons/transaction/memory/TransactionalMapWrapper.java,v 1.1 2004/11/18 23:27:18 ozeigermann Exp $
3 <<<<<<< .mine
4  * $Revision: 1.1 $
5  * $Date: 2005-02-26 14:16:14 +0100 (Sa, 26 Feb 2005) $
6 =======
7  * $Revision$
8  * $Date: 2005-02-26 14:16:14 +0100 (Sa, 26 Feb 2005) $
9 >>>>>>> .r168169
10  *
11  * ====================================================================
12  *
13  * Copyright 1999-2002 The Apache Software Foundation
14  *
15  * Licensed under the Apache License, Version 2.0 (the "License");
16  * you may not use this file except in compliance with the License.
17  * You may obtain a copy of the License at
18  *
19  * http://www.apache.org/licenses/LICENSE-2.0
20  *
21  * Unless required by applicable law or agreed to in writing, software
22  * distributed under the License is distributed on an "AS IS" BASIS,
23  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24  * See the License for the specific language governing permissions and
25  * limitations under the License.
26  *
27  */

28
29 package org.apache.commons.transaction.memory;
30
31 import java.util.ArrayList JavaDoc;
32 import java.util.Collection JavaDoc;
33 import java.util.Collections JavaDoc;
34 import java.util.HashSet JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.Map JavaDoc;
37 import java.util.Set JavaDoc;
38
39 import javax.transaction.Status JavaDoc;
40
41 /**
42  * Wrapper that adds transactional control to all kinds of maps that implement the {@link Map} interface.
43  * This wrapper has rather weak isolation, but is simply, neven blocks and commits will never fail for logical
44  * reasons.
45  * <br>
46  * Start a transaction by calling {@link #startTransaction()}. Then perform the normal actions on the map and
47  * finally either call {@link #commitTransaction()} to make your changes permanent or {@link #rollbackTransaction()} to
48  * undo them.
49  * <br>
50  * <em>Caution:</em> Do not modify values retrieved by {@link #get(Object)} as this will circumvent the transactional mechanism.
51  * Rather clone the value or copy it in a way you see fit and store it back using {@link #put(Object, Object)}.
52  * <br>
53  * <em>Note:</em> This wrapper guarantees isolation level <code>READ COMMITTED</code> only. I.e. as soon a value
54  * is committed in one transaction it will be immediately visible in all other concurrent transactions.
55  *
56  * @version $Revision$
57  * @see OptimisticMapWrapper
58  * @see PessimisticMapWrapper
59  */

60 public class TransactionalMapWrapper implements Map JavaDoc, Status JavaDoc {
61
62     /** The map wrapped. */
63     protected Map JavaDoc wrapped;
64
65     /** Factory to be used to create temporary maps for transactions. */
66     protected MapFactory mapFactory;
67     /** Factory to be used to create temporary sets for transactions. */
68     protected SetFactory setFactory;
69
70     private ThreadLocal JavaDoc activeTx = new ThreadLocal JavaDoc();
71
72     /**
73      * Creates a new transactional map wrapper. Temporary maps and sets to store transactional
74      * data will be instances of {@link java.util.HashMap} and {@link java.util.HashSet}.
75      *
76      * @param wrapped map to be wrapped
77      */

78     public TransactionalMapWrapper(Map JavaDoc wrapped) {
79         this(wrapped, new HashMapFactory(), new HashSetFactory());
80     }
81
82     /**
83      * Creates a new transactional map wrapper. Temporary maps and sets to store transactional
84      * data will be created and disposed using {@link MapFactory} and {@link SetFactory}.
85      *
86      * @param wrapped map to be wrapped
87      * @param mapFactory factory for temporary maps
88      * @param setFactory factory for temporary sets
89      */

90     public TransactionalMapWrapper(Map JavaDoc wrapped, MapFactory mapFactory, SetFactory setFactory) {
91         this.wrapped = Collections.synchronizedMap(wrapped);
92         this.mapFactory = mapFactory;
93         this.setFactory = setFactory;
94     }
95
96     /**
97      * Checks if any write operations have been performed inside this transaction.
98      *
99      * @return <code>true</code> if no write opertation has been performed inside the current transaction,
100      * <code>false</code> otherwise
101      */

102     public boolean isReadOnly() {
103         TxContext txContext = getActiveTx();
104
105         if (txContext == null) {
106             throw new IllegalStateException JavaDoc(
107                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
108         }
109
110         return txContext.readOnly;
111     }
112
113     /**
114      * Checks whether this transaction has been marked to allow a rollback as the only
115      * valid outcome. This can be set my method {@link #markTransactionForRollback()} or might
116      * be set internally be any fatal error. Once a transaction is marked for rollback there
117      * is no way to undo this. A transaction that is marked for rollback can not be committed,
118      * also rolled back.
119      *
120      * @return <code>true</code> if this transaction has been marked for a roll back
121      * @see #markTransactionForRollback()
122      */

123     public boolean isTransactionMarkedForRollback() {
124         TxContext txContext = getActiveTx();
125
126         if (txContext == null) {
127             throw new IllegalStateException JavaDoc(
128                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
129         }
130
131         return (txContext.status == Status.STATUS_MARKED_ROLLBACK);
132     }
133
134     /**
135      * Marks the current transaction to allow only a rollback as valid outcome.
136      *
137      * @see #isTransactionMarkedForRollback()
138      */

139     public void markTransactionForRollback() {
140         TxContext txContext = getActiveTx();
141
142         if (txContext == null) {
143             throw new IllegalStateException JavaDoc(
144                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
145         }
146
147         txContext.status = Status.STATUS_MARKED_ROLLBACK;
148     }
149
150     /**
151      * Suspends the transaction associated to the current thread. I.e. the associated between the
152      * current thread and the transaction is deleted. This is useful when you want to continue the transaction
153      * in another thread later. Call {@link #resumeTransaction(TxContext)} - possibly in another thread than the current -
154      * to resume work on the transaction.
155      * <br><br>
156      * <em>Caution:</em> When calling this method the returned identifier
157      * for the transaction is the only remaining reference to the transaction, so be sure to remember it or
158      * the transaction will be eventually deleted (and thereby rolled back) as garbage.
159      *
160      * @return an identifier for the suspended transaction, will be needed to later resume the transaction by
161      * {@link #resumeTransaction(TxContext)}
162      *
163      * @see #resumeTransaction(TxContext)
164      */

165     public TxContext suspendTransaction() {
166         TxContext txContext = getActiveTx();
167
168         if (txContext == null) {
169             throw new IllegalStateException JavaDoc(
170                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
171         }
172
173         txContext.suspended = true;
174         setActiveTx(null);
175         return txContext;
176     }
177
178     /**
179      * Resumes a transaction in the current thread that has previously been suspened by {@link #suspendTransaction()}.
180      *
181      * @param suspendedTx the identifier for the transaction to be resumed, delivered by {@link #suspendTransaction()}
182      *
183      * @see #suspendTransaction()
184      */

185     public void resumeTransaction(TxContext suspendedTx) {
186         if (getActiveTx() != null) {
187             throw new IllegalStateException JavaDoc(
188                 "Active thread " + Thread.currentThread() + " already associated with a transaction!");
189         }
190
191         if (suspendedTx == null) {
192             throw new IllegalStateException JavaDoc("No transaction to resume!");
193         }
194
195         if (!suspendedTx.suspended) {
196             throw new IllegalStateException JavaDoc("Transaction to resume needs to be suspended!");
197         }
198
199         suspendedTx.suspended = false;
200         setActiveTx(suspendedTx);
201     }
202
203     /**
204      * Returns the state of the current transaction.
205      *
206      * @return state of the current transaction as decribed in the {@link Status} interface.
207      */

208     public int getTransactionState() {
209         TxContext txContext = getActiveTx();
210
211         if (txContext == null) {
212             return STATUS_NO_TRANSACTION;
213         }
214         return txContext.status;
215     }
216
217     /**
218      * Starts a new transaction and associates it with the current thread. All subsequent changes in the same
219      * thread made to the map are invisible from other threads until {@link #commitTransaction()} is called.
220      * Use {@link #rollbackTransaction()} to discard your changes. After calling either method there will be
221      * no transaction associated to the current thread any longer.
222          * <br><br>
223      * <em>Caution:</em> Be careful to finally call one of those methods,
224      * as otherwise the transaction will lurk around for ever.
225      *
226      * @see #commitTransaction()
227      * @see #rollbackTransaction()
228      */

229     public void startTransaction() {
230         if (getActiveTx() != null) {
231             throw new IllegalStateException JavaDoc(
232                 "Active thread " + Thread.currentThread() + " already associated with a transaction!");
233         }
234         setActiveTx(new TxContext());
235     }
236
237     /**
238      * Discards all changes made in the current transaction and deletes the association between the current thread
239      * and the transaction.
240      *
241      * @see #startTransaction()
242      * @see #commitTransaction()
243      */

244     public void rollbackTransaction() {
245         TxContext txContext = getActiveTx();
246
247         if (txContext == null) {
248             throw new IllegalStateException JavaDoc(
249                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
250         }
251
252         // simply forget about tx
253
txContext.dispose();
254         setActiveTx(null);
255     }
256
257     /**
258      * Commits all changes made in the current transaction and deletes the association between the current thread
259      * and the transaction.
260      *
261      * @see #startTransaction()
262      * @see #rollbackTransaction()
263      */

264     public void commitTransaction() {
265         TxContext txContext = getActiveTx();
266
267         if (txContext == null) {
268             throw new IllegalStateException JavaDoc(
269                 "Active thread " + Thread.currentThread() + " not associated with a transaction!");
270         }
271
272         if (txContext.status == Status.STATUS_MARKED_ROLLBACK) {
273             throw new IllegalStateException JavaDoc("Active thread " + Thread.currentThread() + " is marked for rollback!");
274         }
275
276         txContext.merge();
277         txContext.dispose();
278         setActiveTx(null);
279     }
280
281     //
282
// Map methods
283
//
284

285     /**
286      * @see Map#clear()
287      */

288     public void clear() {
289         TxContext txContext = getActiveTx();
290         if (txContext != null) {
291             txContext.clear();
292         } else {
293             wrapped.clear();
294         }
295     }
296
297     /**
298      * @see Map#size()
299      */

300     public int size() {
301         TxContext txContext = getActiveTx();
302         if (txContext != null) {
303             return txContext.size();
304         } else {
305             return wrapped.size();
306         }
307     }
308
309     /**
310      * @see Map#isEmpty()
311      */

312     public boolean isEmpty() {
313         TxContext txContext = getActiveTx();
314         if (txContext == null) {
315             return wrapped.isEmpty();
316         } else {
317             return txContext.isEmpty();
318         }
319     }
320
321     /**
322      * @see Map#containsKey(java.lang.Object)
323      */

324     public boolean containsKey(Object JavaDoc key) {
325         return (get(key) != null);
326     }
327
328     /**
329      * @see Map#containsValue(java.lang.Object)
330      */

331     public boolean containsValue(Object JavaDoc value) {
332         TxContext txContext = getActiveTx();
333
334         if (txContext == null) {
335             return wrapped.containsValue(value);
336         } else {
337             return values().contains(value);
338         }
339     }
340
341     /**
342      * @see Map#values()
343      */

344     public Collection JavaDoc values() {
345
346         TxContext txContext = getActiveTx();
347
348         if (txContext == null) {
349             return wrapped.values();
350         } else {
351             // XXX expensive :(
352
Collection JavaDoc values = new ArrayList JavaDoc();
353             for (Iterator JavaDoc it = keySet().iterator(); it.hasNext();) {
354                 Object JavaDoc key = it.next();
355                 Object JavaDoc value = get(key);
356                 // XXX we have no isolation, so get entry might have been deleted in the meantime
357
if (value != null) {
358                     values.add(value);
359                 }
360             }
361             return values;
362         }
363     }
364
365     /**
366      * @see Map#putAll(java.util.Map)
367      */

368     public void putAll(Map JavaDoc map) {
369         TxContext txContext = getActiveTx();
370
371         if (txContext == null) {
372             wrapped.putAll(map);
373         } else {
374             for (Iterator JavaDoc it = map.entrySet().iterator(); it.hasNext();) {
375                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
376                 txContext.put(entry.getKey(), entry.getValue());
377             }
378         }
379     }
380
381     /**
382      * @see Map#entrySet()
383      */

384     public Set JavaDoc entrySet() {
385         TxContext txContext = getActiveTx();
386         if (txContext == null) {
387             return wrapped.entrySet();
388         } else {
389             Set JavaDoc entrySet = new HashSet JavaDoc();
390             // XXX expensive :(
391
for (Iterator JavaDoc it = keySet().iterator(); it.hasNext();) {
392                 Object JavaDoc key = it.next();
393                 Object JavaDoc value = get(key);
394                 // XXX we have no isolation, so get entry might have been deleted in the meantime
395
if (value != null) {
396                     entrySet.add(new HashEntry(key, value));
397                 }
398             }
399             return entrySet;
400         }
401     }
402
403     /**
404      * @see Map#keySet()
405      */

406     public Set JavaDoc keySet() {
407         TxContext txContext = getActiveTx();
408
409         if (txContext == null) {
410             return wrapped.keySet();
411         } else {
412             return txContext.keys();
413         }
414     }
415
416     /**
417      * @see Map#get(java.lang.Object)
418      */

419     public Object JavaDoc get(Object JavaDoc key) {
420         TxContext txContext = getActiveTx();
421
422         if (txContext != null) {
423             return txContext.get(key);
424         } else {
425             return wrapped.get(key);
426         }
427     }
428
429     /**
430      * @see Map#remove(java.lang.Object)
431      */

432     public Object JavaDoc remove(Object JavaDoc key) {
433         TxContext txContext = getActiveTx();
434
435         if (txContext == null) {
436             return wrapped.remove(key);
437         } else {
438             Object JavaDoc oldValue = get(key);
439             txContext.remove(key);
440             return oldValue;
441         }
442     }
443
444     /**
445      * @see Map#put(java.lang.Object, java.lang.Object)
446      */

447     public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
448         TxContext txContext = getActiveTx();
449
450         if (txContext == null) {
451             return wrapped.put(key, value);
452         } else {
453             Object JavaDoc oldValue = get(key);
454             txContext.put(key, value);
455             return oldValue;
456         }
457
458     }
459
460     protected TxContext getActiveTx() {
461         return (TxContext) activeTx.get();
462     }
463
464     protected void setActiveTx(TxContext txContext) {
465         activeTx.set(txContext);
466     }
467
468     // mostly copied from org.apache.commons.collections.map.AbstractHashedMap
469
protected static class HashEntry implements Map.Entry JavaDoc {
470         /** The key */
471         protected Object JavaDoc key;
472         /** The value */
473         protected Object JavaDoc value;
474
475         protected HashEntry(Object JavaDoc key, Object JavaDoc value) {
476             this.key = key;
477             this.value = value;
478         }
479
480         public Object JavaDoc getKey() {
481             return key;
482         }
483
484         public Object JavaDoc getValue() {
485             return value;
486         }
487
488         public Object JavaDoc setValue(Object JavaDoc value) {
489             Object JavaDoc old = this.value;
490             this.value = value;
491             return old;
492         }
493
494         public boolean equals(Object JavaDoc obj) {
495             if (obj == this) {
496                 return true;
497             }
498             if (!(obj instanceof Map.Entry JavaDoc)) {
499                 return false;
500             }
501             Map.Entry JavaDoc other = (Map.Entry JavaDoc) obj;
502             return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()))
503                 && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
504         }
505
506         public int hashCode() {
507             return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode());
508         }
509
510         public String JavaDoc toString() {
511             return new StringBuffer JavaDoc().append(getKey()).append('=').append(getValue()).toString();
512         }
513     }
514
515     public class TxContext {
516         protected Set JavaDoc deletes;
517         protected Map JavaDoc changes;
518         protected Map JavaDoc adds;
519         protected int status;
520         protected boolean cleared;
521         protected boolean readOnly;
522         protected boolean suspended = false;
523
524         protected TxContext() {
525             deletes = setFactory.createSet();
526             changes = mapFactory.createMap();
527             adds = mapFactory.createMap();
528             status = Status.STATUS_ACTIVE;
529             cleared = false;
530             readOnly = true;
531         }
532
533         protected Set JavaDoc keys() {
534             Set JavaDoc keySet = new HashSet JavaDoc();
535             if (!cleared) {
536                 keySet.addAll(wrapped.keySet());
537             }
538             keySet.addAll(adds.keySet());
539             return keySet;
540         }
541
542         protected Object JavaDoc get(Object JavaDoc key) {
543
544             if (deletes.contains(key)) {
545                 // reflects that entry has been deleted in this tx
546
return null;
547             }
548
549             Object JavaDoc changed = changes.get(key);
550             if (changed != null) {
551                 return changed;
552             }
553
554             Object JavaDoc added = adds.get(key);
555             if (added != null) {
556                 return added;
557             }
558
559             if (cleared) {
560                 return null;
561             } else {
562                 // not modified in this tx
563
return wrapped.get(key);
564             }
565         }
566
567         protected void put(Object JavaDoc key, Object JavaDoc value) {
568             try {
569                 readOnly = false;
570                 deletes.remove(key);
571                 if (wrapped.get(key) != null) {
572                     changes.put(key, value);
573                 } else {
574                     adds.put(key, value);
575                 }
576             } catch (RuntimeException JavaDoc e) {
577                 status = Status.STATUS_MARKED_ROLLBACK;
578                 throw e;
579             } catch (Error JavaDoc e) {
580                 status = Status.STATUS_MARKED_ROLLBACK;
581                 throw e;
582             }
583         }
584
585         protected void remove(Object JavaDoc key) {
586
587             try {
588                 readOnly = false;
589                 changes.remove(key);
590                 adds.remove(key);
591                 if (wrapped.containsKey(key) && !cleared) {
592                     deletes.add(key);
593                 }
594             } catch (RuntimeException JavaDoc e) {
595                 status = Status.STATUS_MARKED_ROLLBACK;
596                 throw e;
597             } catch (Error JavaDoc e) {
598                 status = Status.STATUS_MARKED_ROLLBACK;
599                 throw e;
600             }
601         }
602
603         protected int size() {
604             int size = (cleared ? 0 : wrapped.size());
605
606             size -= deletes.size();
607             size += adds.size();
608
609             return size;
610         }
611
612         protected void clear() {
613             readOnly = false;
614             cleared = true;
615             deletes.clear();
616             changes.clear();
617             adds.clear();
618         }
619
620         protected boolean isEmpty() {
621             return (size() == 0);
622         }
623
624         protected void merge() {
625             if (!readOnly) {
626
627                 if (cleared) {
628                     wrapped.clear();
629                 }
630
631                 wrapped.putAll(changes);
632                 wrapped.putAll(adds);
633
634                 for (Iterator JavaDoc it = deletes.iterator(); it.hasNext();) {
635                     Object JavaDoc key = it.next();
636                     wrapped.remove(key);
637                 }
638             }
639         }
640
641         protected void dispose() {
642             setFactory.disposeSet(deletes);
643             deletes = null;
644             mapFactory.disposeMap(changes);
645             changes = null;
646             mapFactory.disposeMap(adds);
647             adds = null;
648             status = Status.STATUS_NO_TRANSACTION;
649         }
650     }
651 }
652
Popular Tags