001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.transaction.memory;
018
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.Iterator;
022import java.util.Map;
023
024import javax.transaction.Status;
025
026import junit.framework.Test;
027import junit.framework.TestCase;
028import junit.framework.TestSuite;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032
033import org.apache.commons.transaction.util.CommonsLoggingLogger;
034import org.apache.commons.transaction.util.LoggerFacade;
035import org.apache.commons.transaction.util.RendezvousBarrier;
036
037/**
038 * Tests for map wrapper. 
039 *
040 * @version $Id: MapWrapperTest.java 493628 2007-01-07 01:42:48Z joerg $
041 */
042public class MapWrapperTest extends TestCase {
043
044    private static final Log log = LogFactory.getLog(MapWrapperTest.class.getName());
045    private static final LoggerFacade sLogger = new CommonsLoggingLogger(log);
046
047    protected static final long BARRIER_TIMEOUT = 20000;
048
049    // XXX need this, as JUnit seems to print only part of these strings
050    protected static void report(String should, String is) {
051        if (!should.equals(is)) {
052            fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n");
053        }
054    }
055
056    protected static void checkCollection(Collection col, Object[] values) {
057        int cnt = 0;
058        int trueCnt = 0;
059
060        for (Iterator it = col.iterator(); it.hasNext();) {
061            cnt++;
062            Object value1 = it.next();
063            for (int i = 0; i < values.length; i++) {
064                Object value2 = values[i];
065                if (value2.equals(value1))
066                    trueCnt++;
067            }
068        }
069        assertEquals(cnt, values.length);
070        assertEquals(trueCnt, values.length);
071    }
072
073    public static Test suite() {
074        TestSuite suite = new TestSuite(MapWrapperTest.class);
075        return suite;
076    }
077
078    public static void main(java.lang.String[] args) {
079        junit.textui.TestRunner.run(suite());
080    }
081
082    public MapWrapperTest(String testName) {
083        super(testName);
084    }
085
086    protected TransactionalMapWrapper getNewWrapper(Map map) {
087        return new TransactionalMapWrapper(map);
088    }
089
090    public void testBasic() throws Throwable {
091
092        sLogger.logInfo("Checking basic transaction features");
093
094        final Map map1 = new HashMap();
095
096        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
097
098        assertTrue(txMap1.isEmpty());
099
100        // make sure changes are propagated to wrapped map outside tx
101        txMap1.put("key1", "value1");
102        report("value1", (String) map1.get("key1"));
103        assertFalse(txMap1.isEmpty());
104
105        // make sure changes are progated to wrapped map only after commit
106        txMap1.startTransaction();
107        assertFalse(txMap1.isEmpty());
108        txMap1.put("key1", "value2");
109        report("value1", (String) map1.get("key1"));
110        report("value2", (String) txMap1.get("key1"));
111        txMap1.commitTransaction();
112        report("value2", (String) map1.get("key1"));
113        report("value2", (String) txMap1.get("key1"));
114
115        // make sure changes are reverted after rollback
116        txMap1.startTransaction();
117        txMap1.put("key1", "value3");
118        txMap1.rollbackTransaction();
119        report("value2", (String) map1.get("key1"));
120        report("value2", (String) txMap1.get("key1"));
121    }
122
123    public void testContainsKeyWithNullValue() throws Throwable {
124
125        sLogger.logInfo("Checking containsKey returns true when the value is null");
126
127        final Map map1 = new HashMap();
128
129        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
130
131        assertTrue(txMap1.isEmpty());
132
133        // make sure changes are propagated to wrapped map outside tx
134        txMap1.put("key1", null);
135        assertTrue(txMap1.containsKey("key1"));
136
137        // make sure changes are progated to wrapped map after commit
138        txMap1.startTransaction();
139        txMap1.put("key2", null);
140        assertTrue(txMap1.containsKey("key2"));
141        txMap1.remove("key1");
142        assertTrue(map1.containsKey("key1"));
143        txMap1.commitTransaction();
144        assertTrue(txMap1.containsKey("key2"));
145        assertFalse(txMap1.containsKey("key1"));
146
147        txMap1.startTransaction();
148        assertTrue(txMap1.containsKey("key2"));
149        txMap1.remove("key2");
150        assertFalse(txMap1.containsKey("key2"));
151        txMap1.commitTransaction();
152    }
153
154    public void testComplex() throws Throwable {
155
156        sLogger.logInfo("Checking advanced and complex transaction features");
157
158        final Map map1 = new HashMap();
159
160        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
161
162        // first fill in some global values:
163        txMap1.put("key1", "value1");
164        txMap1.put("key2", "value2");
165
166        // let's see if we have all values:
167        sLogger.logInfo("Checking if global values are present");
168
169        assertTrue(txMap1.containsValue("value1"));
170        assertTrue(txMap1.containsValue("value2"));
171        assertFalse(txMap1.containsValue("novalue"));
172
173        // ... and all keys
174        sLogger.logInfo("Checking if global keys are present");
175        assertTrue(txMap1.containsKey("key1"));
176        assertTrue(txMap1.containsKey("key2"));
177        assertFalse(txMap1.containsKey("nokey"));
178
179        // and now some inside a transaction
180        txMap1.startTransaction();
181        txMap1.put("key3", "value3");
182        txMap1.put("key4", "value4");
183
184        // let's see if we have all values:
185        sLogger.logInfo("Checking if values inside transactions are present");
186        assertTrue(txMap1.containsValue("value1"));
187        assertTrue(txMap1.containsValue("value2"));
188        assertTrue(txMap1.containsValue("value3"));
189        assertTrue(txMap1.containsValue("value4"));
190        assertFalse(txMap1.containsValue("novalue"));
191
192        // ... and all keys
193        sLogger.logInfo("Checking if keys inside transactions are present");
194        assertTrue(txMap1.containsKey("key1"));
195        assertTrue(txMap1.containsKey("key2"));
196        assertTrue(txMap1.containsKey("key3"));
197        assertTrue(txMap1.containsKey("key4"));
198        assertFalse(txMap1.containsKey("nokey"));
199
200        // now let's delete some old stuff
201        sLogger.logInfo("Checking remove inside transactions");
202        txMap1.remove("key1");
203        assertFalse(txMap1.containsKey("key1"));
204        assertFalse(txMap1.containsValue("value1"));
205        assertNull(txMap1.get("key1"));
206        assertEquals(3, txMap1.size());
207
208        // and some newly created
209        txMap1.remove("key3");
210        assertFalse(txMap1.containsKey("key3"));
211        assertFalse(txMap1.containsValue("value3"));
212        assertNull(txMap1.get("key3"));
213        assertEquals(2, txMap1.size());
214
215        sLogger.logInfo("Checking remove and propagation after commit");
216        txMap1.commitTransaction();
217
218        txMap1.remove("key1");
219        assertFalse(txMap1.containsKey("key1"));
220        assertFalse(txMap1.containsValue("value1"));
221        assertNull(txMap1.get("key1"));
222        assertFalse(txMap1.containsKey("key3"));
223        assertFalse(txMap1.containsValue("value3"));
224        assertNull(txMap1.get("key3"));
225        assertEquals(2, txMap1.size());
226    }
227
228    public void testSets() throws Throwable {
229
230        sLogger.logInfo("Checking set opertaions");
231
232        final Map map1 = new HashMap();
233
234        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
235
236        // first fill in some global values:
237        txMap1.put("key1", "value1");
238        txMap1.put("key2", "value200");
239
240        // and now some inside a transaction
241        txMap1.startTransaction();
242        txMap1.put("key2", "value2"); // modify 
243        txMap1.put("key3", "value3");
244        txMap1.put("key4", "value4");
245
246        // check entry set
247        boolean key1P, key2P, key3P, key4P;
248        key1P = key2P = key3P = key4P = false;
249        int cnt = 0;
250        for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) {
251            cnt++;
252            Map.Entry entry = (Map.Entry) it.next();
253            if (entry.getKey().equals("key1") && entry.getValue().equals("value1"))
254                key1P = true;
255            else if (entry.getKey().equals("key2") && entry.getValue().equals("value2"))
256                key2P = true;
257            else if (entry.getKey().equals("key3") && entry.getValue().equals("value3"))
258                key3P = true;
259            else if (entry.getKey().equals("key4") && entry.getValue().equals("value4"))
260                key4P = true;
261        }
262        assertEquals(cnt, 4);
263        assertTrue(key1P && key2P && key3P && key4P);
264
265        checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" });
266        checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" });
267
268        txMap1.commitTransaction();
269
270        // check again after commit (should be the same)
271        key1P = key2P = key3P = key4P = false;
272        cnt = 0;
273        for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) {
274            cnt++;
275            Map.Entry entry = (Map.Entry) it.next();
276            if (entry.getKey().equals("key1") && entry.getValue().equals("value1"))
277                key1P = true;
278            else if (entry.getKey().equals("key2") && entry.getValue().equals("value2"))
279                key2P = true;
280            else if (entry.getKey().equals("key3") && entry.getValue().equals("value3"))
281                key3P = true;
282            else if (entry.getKey().equals("key4") && entry.getValue().equals("value4"))
283                key4P = true;
284        }
285        assertEquals(cnt, 4);
286        assertTrue(key1P && key2P && key3P && key4P);
287
288        checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" });
289        checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" });
290
291        // now try clean
292
293        txMap1.startTransaction();
294
295        // add
296        txMap1.put("key5", "value5");
297        // modify
298        txMap1.put("key4", "value400");
299        // delete
300        txMap1.remove("key1");
301
302        assertEquals(txMap1.size(), 4);
303
304        txMap1.clear();
305        assertEquals(txMap1.size(), 0);
306        assertEquals(map1.size(), 4);
307
308        // add
309        txMap1.put("key5", "value5");
310        // delete
311        txMap1.remove("key1");
312
313        // adding one, not removing anything gives size 1
314        assertEquals(txMap1.size(), 1);
315        assertEquals(map1.size(), 4);
316        assertNull(txMap1.get("key4"));
317        assertNotNull(txMap1.get("key5"));
318
319        txMap1.commitTransaction();
320
321        // after commit clear must have been propagated to wrapped map:
322        assertEquals(txMap1.size(), 1);
323        assertEquals(map1.size(), 1);
324        assertNull(txMap1.get("key4"));
325        assertNotNull(txMap1.get("key5"));
326        assertNull(map1.get("key4"));
327        assertNotNull(map1.get("key5"));
328    }
329
330    public void testMulti() throws Throwable {
331        sLogger.logInfo("Checking concurrent transaction features");
332
333        final Map map1 = new HashMap();
334
335        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
336
337        final RendezvousBarrier beforeCommitBarrier =
338            new RendezvousBarrier("Before Commit", 2, BARRIER_TIMEOUT, sLogger);
339
340        final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2, BARRIER_TIMEOUT, sLogger);
341
342        Thread thread1 = new Thread(new Runnable() {
343            public void run() {
344                txMap1.startTransaction();
345                try {
346                    beforeCommitBarrier.meet();
347                    txMap1.put("key1", "value2");
348                    txMap1.commitTransaction();
349                    afterCommitBarrier.call();
350                } catch (InterruptedException e) {
351                    sLogger.logWarning("Thread interrupted", e);
352                    afterCommitBarrier.reset();
353                    beforeCommitBarrier.reset();
354                }
355            }
356        }, "Thread1");
357
358        txMap1.put("key1", "value1");
359
360        txMap1.startTransaction();
361        thread1.start();
362
363        report("value1", (String) txMap1.get("key1"));
364        beforeCommitBarrier.call();
365        afterCommitBarrier.meet();
366        // we have read committed as isolation level, that's why I will see the new value of the other thread now
367        report("value2", (String) txMap1.get("key1"));
368
369        // now when I override it it should of course be my value again
370        txMap1.put("key1", "value3");
371        report("value3", (String) txMap1.get("key1"));
372
373        // after rollback it must be the value written by the other thread again
374        txMap1.rollbackTransaction();
375        report("value2", (String) txMap1.get("key1"));
376    }
377
378    public void testTxControl() throws Throwable {
379        sLogger.logInfo("Checking advanced transaction control (heavily used in JCA implementation)");
380
381        final Map map1 = new HashMap();
382
383        final TransactionalMapWrapper txMap1 = getNewWrapper(map1);
384
385        assertEquals(txMap1.getTransactionState(), Status.STATUS_NO_TRANSACTION);
386        txMap1.startTransaction();
387        assertEquals(txMap1.getTransactionState(), Status.STATUS_ACTIVE);
388
389        assertTrue(txMap1.isReadOnly());
390        txMap1.put("key", "value");
391        assertFalse(txMap1.isReadOnly());
392
393        assertFalse(txMap1.isTransactionMarkedForRollback());
394        txMap1.markTransactionForRollback();
395        assertTrue(txMap1.isTransactionMarkedForRollback());
396
397        boolean failed = false;
398        try {
399            txMap1.commitTransaction();
400        } catch (IllegalStateException ise) {
401            failed = true;
402        }
403        assertTrue(failed);
404        txMap1.rollbackTransaction();
405        assertEquals(txMap1.getTransactionState(), Status.STATUS_NO_TRANSACTION);
406
407        txMap1.startTransaction();
408        final TransactionalMapWrapper.TxContext ctx = txMap1.suspendTransaction();
409        final RendezvousBarrier afterSuspendBarrier =
410            new RendezvousBarrier("After Suspend", 2, BARRIER_TIMEOUT, sLogger);
411
412        new Thread(new Runnable() {
413            public void run() {
414                txMap1.resumeTransaction(ctx);
415                txMap1.put("key2", "value2");
416                txMap1.suspendTransaction();
417                afterSuspendBarrier.call();
418            }
419        }).start();
420
421        afterSuspendBarrier.meet();
422        txMap1.resumeTransaction(ctx);
423
424        assertEquals(txMap1.size(), 1);
425        txMap1.put("key3", "value3");
426        assertEquals(txMap1.size(), 2);
427        assertEquals(map1.size(), 0);
428
429        txMap1.commitTransaction();
430        assertEquals(txMap1.size(), 2);
431        assertEquals(map1.size(), 2);
432    }
433
434}