1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.transaction.memory;
18  
19  import static junit.framework.Assert.assertEquals;
20  import static junit.framework.Assert.assertFalse;
21  import static junit.framework.Assert.assertNotNull;
22  import static junit.framework.Assert.assertNull;
23  import static junit.framework.Assert.assertTrue;
24  import static junit.framework.Assert.fail;
25  
26  import java.util.Collection;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.concurrent.TimeUnit;
30  
31  import junit.framework.JUnit4TestAdapter;
32  import org.junit.Test;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.commons.transaction.util.RendezvousBarrier;
37  
38  /**
39   * Tests for basic tx map.
40   * 
41   */
42  public class BasicTxMapTest {
43  
44      private static final Log log = LogFactory.getLog(BasicTxMapTest.class.getName());
45  
46      protected static final long BARRIER_TIMEOUT = 20000;
47  
48      // XXX need this, as JUnit seems to print only part of these strings
49      protected static void report(String should, String is) {
50          if (!should.equals(is)) {
51              fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n");
52          }
53      }
54  
55      protected static void checkCollection(Collection col, Object[] values) {
56          int cnt = 0;
57          int trueCnt = 0;
58  
59          for (Iterator it = col.iterator(); it.hasNext();) {
60              cnt++;
61              Object value1 = it.next();
62              for (int i = 0; i < values.length; i++) {
63                  Object value2 = values[i];
64                  if (value2.equals(value1))
65                      trueCnt++;
66              }
67          }
68          assertEquals(cnt, values.length);
69          assertEquals(trueCnt, values.length);
70      }
71  
72      public static junit.framework.Test suite() {
73          return new JUnit4TestAdapter(BasicTxMapTest.class);
74      }
75  
76      public static void main(java.lang.String[] args) {
77          junit.textui.TestRunner.run(suite());
78      }
79  
80      @Test
81      public void testBasic() {
82  
83          log.info("Checking basic transaction features");
84  
85          final BasicTxMap<String, String> txMap1 = new BasicTxMap("txMap1");
86          final Map map1 = txMap1.getWrappedMap();
87  
88          assertTrue(txMap1.isEmpty());
89  
90          // make sure changes are propagated to wrapped map outside tx
91          txMap1.put("key1", "value1");
92          report("value1", (String) map1.get("key1"));
93          assertFalse(txMap1.isEmpty());
94  
95          // make sure changes are propagated to wrapped map only after commit
96          txMap1.startTransaction(1, TimeUnit.HOURS);
97          assertFalse(txMap1.isEmpty());
98          txMap1.put("key1", "value2");
99          report("value1", (String) map1.get("key1"));
100         report("value2", (String) txMap1.get("key1"));
101         txMap1.commitTransaction();
102         report("value2", (String) map1.get("key1"));
103         report("value2", (String) txMap1.get("key1"));
104 
105         // make sure changes are reverted after roll back
106         txMap1.startTransaction(1, TimeUnit.HOURS);
107         txMap1.put("key1", "value3");
108         txMap1.rollbackTransaction();
109         report("value2", (String) map1.get("key1"));
110         report("value2", (String) txMap1.get("key1"));
111     }
112 
113     @Test
114     public void testComplex() {
115 
116         log.info("Checking advanced and complex transaction features");
117 
118         final BasicTxMap<String, String> txMap1 = new BasicTxMap("txMap1");
119         final Map map1 = txMap1.getWrappedMap();
120 
121         // first fill in some global values:
122         txMap1.put("key1", "value1");
123         txMap1.put("key2", "value2");
124 
125         // let's see if we have all values:
126         log.info("Checking if global values are present");
127 
128         assertTrue(txMap1.containsValue("value1"));
129         assertTrue(txMap1.containsValue("value2"));
130         assertFalse(txMap1.containsValue("novalue"));
131 
132         // ... and all keys
133         log.info("Checking if global keys are present");
134         assertTrue(txMap1.containsKey("key1"));
135         assertTrue(txMap1.containsKey("key2"));
136         assertFalse(txMap1.containsKey("nokey"));
137 
138         // and now some inside a transaction
139         txMap1.startTransaction(1, TimeUnit.HOURS);
140         txMap1.put("key3", "value3");
141         txMap1.put("key4", "value4");
142 
143         // let's see if we have all values:
144         log.info("Checking if values inside transactions are present");
145         assertTrue(txMap1.containsValue("value1"));
146         assertTrue(txMap1.containsValue("value2"));
147         assertTrue(txMap1.containsValue("value3"));
148         assertTrue(txMap1.containsValue("value4"));
149         assertFalse(txMap1.containsValue("novalue"));
150 
151         // ... and all keys
152         log.info("Checking if keys inside transactions are present");
153         assertTrue(txMap1.containsKey("key1"));
154         assertTrue(txMap1.containsKey("key2"));
155         assertTrue(txMap1.containsKey("key3"));
156         assertTrue(txMap1.containsKey("key4"));
157         assertFalse(txMap1.containsKey("nokey"));
158 
159         // now let's delete some old stuff
160         log.info("Checking remove inside transactions");
161         txMap1.remove("key1");
162         assertFalse(txMap1.containsKey("key1"));
163         assertFalse(txMap1.containsValue("value1"));
164         assertNull(txMap1.get("key1"));
165         assertEquals(3, txMap1.size());
166 
167         // and some newly created
168         txMap1.remove("key3");
169         assertFalse(txMap1.containsKey("key3"));
170         assertFalse(txMap1.containsValue("value3"));
171         assertNull(txMap1.get("key3"));
172         assertEquals(2, txMap1.size());
173 
174         log.info("Checking remove and propagation after commit");
175         txMap1.commitTransaction();
176 
177         txMap1.remove("key1");
178         assertFalse(txMap1.containsKey("key1"));
179         assertFalse(txMap1.containsValue("value1"));
180         assertNull(txMap1.get("key1"));
181         assertFalse(txMap1.containsKey("key3"));
182         assertFalse(txMap1.containsValue("value3"));
183         assertNull(txMap1.get("key3"));
184         assertEquals(2, txMap1.size());
185     }
186 
187     @Test
188     public void testSets() {
189 
190         log.info("Checking set opertaions");
191 
192         final BasicTxMap<String, String> txMap1 = new BasicTxMap("txMap1");
193         final Map map1 = txMap1.getWrappedMap();
194 
195         // first fill in some global values:
196         txMap1.put("key1", "value1");
197         txMap1.put("key2", "value200");
198 
199         // and now some inside a transaction
200         txMap1.startTransaction(1, TimeUnit.HOURS);
201         txMap1.put("key2", "value2"); // modify
202         txMap1.put("key3", "value3");
203         txMap1.put("key4", "value4");
204 
205         // check entry set
206         boolean key1P, key2P, key3P, key4P;
207         key1P = key2P = key3P = key4P = false;
208         int cnt = 0;
209         for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) {
210             cnt++;
211             Map.Entry entry = (Map.Entry) it.next();
212             if (entry.getKey().equals("key1") && entry.getValue().equals("value1"))
213                 key1P = true;
214             else if (entry.getKey().equals("key2") && entry.getValue().equals("value2"))
215                 key2P = true;
216             else if (entry.getKey().equals("key3") && entry.getValue().equals("value3"))
217                 key3P = true;
218             else if (entry.getKey().equals("key4") && entry.getValue().equals("value4"))
219                 key4P = true;
220         }
221         assertEquals(cnt, 4);
222         assertTrue(key1P && key2P && key3P && key4P);
223 
224         checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" });
225         checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" });
226 
227         txMap1.commitTransaction();
228 
229         // check again after commit (should be the same)
230         key1P = key2P = key3P = key4P = false;
231         cnt = 0;
232         for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) {
233             cnt++;
234             Map.Entry entry = (Map.Entry) it.next();
235             if (entry.getKey().equals("key1") && entry.getValue().equals("value1"))
236                 key1P = true;
237             else if (entry.getKey().equals("key2") && entry.getValue().equals("value2"))
238                 key2P = true;
239             else if (entry.getKey().equals("key3") && entry.getValue().equals("value3"))
240                 key3P = true;
241             else if (entry.getKey().equals("key4") && entry.getValue().equals("value4"))
242                 key4P = true;
243         }
244         assertEquals(cnt, 4);
245         assertTrue(key1P && key2P && key3P && key4P);
246 
247         checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" });
248         checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" });
249 
250         // now try clean
251 
252         txMap1.startTransaction(1, TimeUnit.HOURS);
253 
254         // add
255         txMap1.put("key5", "value5");
256         // modify
257         txMap1.put("key4", "value400");
258         // delete
259         txMap1.remove("key1");
260 
261         assertEquals(txMap1.size(), 4);
262 
263         txMap1.clear();
264         assertEquals(txMap1.size(), 0);
265         assertEquals(map1.size(), 4);
266 
267         // add
268         txMap1.put("key5", "value5");
269         // delete
270         txMap1.remove("key1");
271 
272         // adding one, not removing anything gives size 1
273         assertEquals(txMap1.size(), 1);
274         assertEquals(map1.size(), 4);
275         assertNull(txMap1.get("key4"));
276         assertNotNull(txMap1.get("key5"));
277 
278         txMap1.commitTransaction();
279 
280         // after commit clear must have been propagated to wrapped map:
281         assertEquals(txMap1.size(), 1);
282         assertEquals(map1.size(), 1);
283         assertNull(txMap1.get("key4"));
284         assertNotNull(txMap1.get("key5"));
285         assertNull(map1.get("key4"));
286         assertNotNull(map1.get("key5"));
287     }
288 
289     @Test
290     public void testMulti() {
291         log.info("Checking concurrent transaction features");
292 
293         final BasicTxMap<String, String> txMap1 = new BasicTxMap("txMap1");
294         final Map map1 = txMap1.getWrappedMap();
295 
296         final RendezvousBarrier beforeCommitBarrier = new RendezvousBarrier("Before Commit", 2,
297                 BARRIER_TIMEOUT);
298 
299         final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2,
300                 BARRIER_TIMEOUT);
301 
302         Thread thread1 = new Thread(new Runnable() {
303             public void run() {
304                 txMap1.startTransaction(1, TimeUnit.HOURS);
305                 try {
306                     beforeCommitBarrier.meet();
307                     txMap1.put("key1", "value2");
308                     txMap1.commitTransaction();
309                     afterCommitBarrier.call();
310                 } catch (InterruptedException e) {
311                     log.warn("Thread interrupted", e);
312                     afterCommitBarrier.reset();
313                     beforeCommitBarrier.reset();
314                 }
315             }
316         }, "Thread1");
317 
318         txMap1.put("key1", "value1");
319 
320         txMap1.startTransaction(1, TimeUnit.HOURS);
321         thread1.start();
322 
323         report("value1", (String) txMap1.get("key1"));
324         beforeCommitBarrier.call();
325         try {
326             afterCommitBarrier.meet();
327         } catch (InterruptedException e) {
328             // TODO Auto-generated catch block
329             e.printStackTrace();
330         }
331         // we have read committed as isolation level, that's why I will see the
332         // new value of the other thread now
333         report("value2", (String) txMap1.get("key1"));
334 
335         // now when I override it it should of course be my value again
336         txMap1.put("key1", "value3");
337         report("value3", (String) txMap1.get("key1"));
338 
339         // after rollback it must be the value written by the other thread again
340         txMap1.rollbackTransaction();
341         report("value2", (String) txMap1.get("key1"));
342     }
343 
344 }