View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.util.store;
8   
9   import org.mule.api.lifecycle.InitialisationException;
10  import org.mule.tck.junit4.AbstractMuleTestCase;
11  
12  import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
13  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
14  import org.junit.After;
15  import org.junit.Test;
16  
17  import static org.junit.Assert.assertEquals;
18  import static org.junit.Assert.assertFalse;
19  import static org.junit.Assert.assertTrue;
20  
21  public class InMemoryStoreTestCase extends AbstractMuleTestCase
22  {
23      private InMemoryObjectStore<String> store = null;
24  
25      @After
26      public void disposeStore()
27      {
28          store.dispose();
29      }
30  
31      @Test
32      public void testSimpleTimedExpiry() throws Exception
33      {
34          int entryTTL = 3000;
35          createTimedObjectStore(entryTTL);
36  
37          // store entries in quick succession
38          storeObjects("1", "2", "3");
39  
40          // they should still be alive at this point
41          assertObjectsInStore("1", "2", "3");
42  
43          // wait until the entry TTL has been exceeded
44          Thread.sleep(entryTTL + 1000);
45  
46          // make sure all values are gone
47          assertObjectsExpired("1", "2", "3");
48      }
49  
50      @Test
51      public void testComplexTimedExpiry() throws Exception
52      {
53          int entryTTL = 3000;
54          createTimedObjectStore(entryTTL);
55          
56          // store an entry ...
57          storeObjects("1");
58          
59          // ... wait half of the expiry time ...
60          Thread.sleep(entryTTL / 2);
61          
62          // ... and store another object ...
63          storeObjects("2");
64          
65          // ... now wait until the first one is expired
66          Thread.sleep((entryTTL / 2) + 500);
67          
68          assertObjectsExpired("1");
69          assertObjectsInStore("2");
70      }
71  
72      @Test
73      public void testStoreAndRetrieve() throws Exception
74      {
75          String key = "key";
76          String value = "hello";
77          
78          createBoundedObjectStore(1);
79          
80          store.store(key, value);
81          assertObjectsInStore(key);
82          
83          String retrieved = store.retrieve(key);
84          assertEquals(value, retrieved);
85          
86          store.remove(key);        
87          assertObjectsExpired(key);
88      }
89  
90      @Test
91      public void testExpiringUnboundedStore() throws Exception
92      {
93          createUnboundedObjectStore();
94          
95          // put some items into the store
96          storeObjects("1", "2", "3");
97          
98          // expire ... this should keep all objects in the store
99          store.expire();
100         
101         assertObjectsInStore("1", "2", "3");
102     }
103 
104     @Test
105     public void testMaxSize() throws Exception
106     {
107         int maxEntries = 3;
108         createBoundedObjectStore(maxEntries);
109 
110         storeObjects("1", "2", "3");
111         assertObjectsInStore("1", "2", "3");
112 
113         // exceed threshold
114         store.store("4", "4");
115 
116         // the oldest entry should still be there, not yet expired
117         assertTrue(store.contains("1"));
118 
119         // expire manually
120         store.expire();
121         assertObjectsExpired("1");
122         assertObjectsInStore("2", "3", "4");
123 
124         // exceed some more
125         storeObjects("5");
126         store.expire();
127         assertObjectsExpired("2");
128         assertObjectsInStore("3", "4", "5");
129 
130         // exceed multiple times
131         storeObjects("6", "7", "8", "9");
132         store.expire();
133         assertObjectsInStore("7", "8", "9");
134         assertObjectsExpired("3", "4", "5", "6");
135     }
136 
137     private void storeObjects(String... objects) throws Exception
138     {
139         for (String entry : objects)
140         {
141             store.store(entry, entry);
142         }
143     }
144     
145     private void assertObjectsInStore(String... identifiers) throws Exception
146     {
147         for (String id : identifiers)
148         {
149             String message = "id " + id + " not in store " + store;
150             assertTrue(message, store.contains(id));
151         }
152     }
153     
154     private void assertObjectsExpired(String... identifiers) throws Exception
155     {
156         for (String id : identifiers)
157         {
158             assertFalse(store.contains(id));
159         }
160     }
161 
162     private void createTimedObjectStore(int timeToLive) throws InitialisationException
163     {
164         int expireInterval = 1000;
165         assertTrue("objects' time to live must be greater than the expire interval", 
166             timeToLive > expireInterval);
167         
168         store = new InMemoryObjectStore<String>();
169         store.setName("timed");
170         store.setMaxEntries(3);
171         store.setEntryTTL(timeToLive);
172         store.setExpirationInterval(expireInterval);
173         store.initialise();
174     }
175 
176     private void createBoundedObjectStore(int numberOfEntries) throws InitialisationException
177     {
178         createNonexpiringObjectStore();
179         store.setName("bounded");
180         store.setMaxEntries(numberOfEntries);
181         store.initialise();
182     }
183     
184     private void createUnboundedObjectStore() throws InitialisationException
185     {
186         createNonexpiringObjectStore();
187         store.setMaxEntries(-1);
188         store.initialise();
189     }
190 
191     private void createNonexpiringObjectStore()
192     {
193         store = new NonExpiringInMemoryObjectStore();
194     }
195     
196     /**
197      * Special subclass that coordinates with the expire thread. Upon calling <code>initialize</code>
198      * the scheduler in {@link AbstractMonitoredObjectStore} runs once. The tests in this test case
199      * rely on the fact that no expiry happens during their execution. This implementation waits for
200      * the first run of the expire method in initialize and only then continues with the execution 
201      * of the current thread.
202      */
203     private static class NonExpiringInMemoryObjectStore extends InMemoryObjectStore<String>
204     {
205         private CountDownLatch expireLatch;
206 
207         public NonExpiringInMemoryObjectStore()
208         {
209             super();
210             // entryTTL=-1 means we will have to expire manually
211             setEntryTTL(-1);
212             // run the expire thread in very, very large intervals (irreleavent to this test)
213             setExpirationInterval(Integer.MAX_VALUE);
214             
215             expireLatch = new CountDownLatch(1);
216         }
217         
218         @Override
219         public void initialise() throws InitialisationException
220         {
221             super.initialise();
222             
223             // now wait for the first expire to happen
224             try
225             {
226                 expireLatch.await(30, TimeUnit.SECONDS);
227             }
228             catch (InterruptedException ie)
229             {
230                 throw new RuntimeException("Interrupted while waiting for the first expire", ie);
231             }
232         }
233 
234         @Override
235         public void expire()
236         {
237             super.expire();
238             // expire successful ... signal initialize that it can continue
239             expireLatch.countDown();
240         }
241     }
242 }