1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.fulcrum.cache;
21  // Cactus and Junit imports
22  
23  import java.util.ConcurrentModificationException;
24  import java.util.Date;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.avalon.framework.component.ComponentException;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.fulcrum.testcontainer.BaseUnitTest;
32  /***
33   * CacheTest
34   *
35   * @author <a href="paulsp@apache.org">Paul Spencer</a>
36   * @author <a href="epugh@upstate.com">Eric Pugh</a>
37   * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
38   * @version $Id: CacheTest.java 535465 2007-05-05 06:58:06Z tv $
39   */
40  public class CacheTest extends BaseUnitTest
41  {
42  
43      private GlobalCacheService globalCache = null;
44      private static final String cacheKey = "CacheKey";
45      private static final String cacheKey_2 = "CacheKey_2";
46      public static final String SKIP_TESTS_KEY = "fulcrum.cache.skip.long.tests";
47      private static final Log LOG = LogFactory.getLog(CacheTest.class);
48  
49  
50      /***
51       * Defines the testcase name for JUnit.
52       *
53       * @param name the testcase's name.
54       */
55      public CacheTest(String name)
56      {
57          super(name);
58      }
59  
60      protected void setUp() throws Exception
61      {
62          super.setUp();
63          try
64          {
65              globalCache = (GlobalCacheService) this.lookup(GlobalCacheService.ROLE);
66          }
67          catch (ComponentException e)
68          {
69              e.printStackTrace();
70              fail(e.getMessage());
71          }
72      }
73      /***
74       * Simple test that verify an object can be created and deleted.
75       * @throws Exception
76       */
77      public void testSimpleAddGetCacheObject() throws Exception
78      {
79          String testString = new String("This is a test");
80          Object retrievedObject = null;
81          CachedObject cacheObject1 = null;
82          // Create object
83          cacheObject1 = new CachedObject(testString);
84          assertNotNull("Failed to create a cachable object 1", cacheObject1);
85          // Add object to cache
86          globalCache.addObject(cacheKey, cacheObject1);
87          // Get object from cache
88          retrievedObject = globalCache.getObject(cacheKey);
89          assertNotNull("Did not retrieved a cached object 1", retrievedObject);
90          assertTrue("Did not retrieved a correct, expected cached object 1", retrievedObject == cacheObject1);
91          // Remove object from cache
92          globalCache.removeObject(cacheKey);
93          // Verify object removed from cache
94          retrievedObject = null;
95          cacheObject1 = null;
96          try
97          {
98              retrievedObject = globalCache.getObject(cacheKey);
99              assertNull(
100                 "Retrieved the deleted cached object 1 and did not get expected ObjectExpiredException",
101                 retrievedObject);
102             assertNotNull("Did not get expected ObjectExpiredException retrieving a deleted object", retrievedObject);
103         }
104         catch (ObjectExpiredException e)
105         {
106             assertNull(
107                 "Retrieved the deleted cached object 1, but caught expected ObjectExpiredException exception",
108                 retrievedObject);
109         }
110         catch (Exception e)
111         {
112             throw e;
113         }
114         // Remove object from cache that does NOT exist in the cache
115         globalCache.removeObject(cacheKey);
116     }
117     /***
118      * Simple test that adds, retrieves, and deletes 2 object.
119      *
120      * @throws Exception
121      */
122     public void test2ObjectAddGetCachedObject() throws Exception
123     {
124         String testString = new String("This is a test");
125         Object retrievedObject = null;
126         CachedObject cacheObject1 = null;
127         CachedObject cacheObject2 = null;
128         // Create and add Object #1
129         cacheObject1 = new CachedObject(testString);
130         assertNotNull("Failed to create a cachable object 1", cacheObject1);
131         globalCache.addObject(cacheKey, cacheObject1);
132         retrievedObject = globalCache.getObject(cacheKey);
133         assertNotNull("Did not retrieved a cached object 1", retrievedObject);
134         assertEquals("Did not retrieved correct cached object", cacheObject1, retrievedObject);
135         // Create and add Object #2
136         cacheObject2 = new CachedObject(testString);
137         assertNotNull("Failed to create a cachable object 2", cacheObject2);
138         globalCache.addObject(cacheKey_2, cacheObject2);
139         retrievedObject = globalCache.getObject(cacheKey_2);
140         assertNotNull("Did not retrieved a cached object 2", retrievedObject);
141         assertEquals("Did not retrieved correct cached object 2", cacheObject2, retrievedObject);
142         // Get object #1
143         retrievedObject = globalCache.getObject(cacheKey);
144         assertNotNull("Did not retrieved a cached object 1. Attempt #2", retrievedObject);
145         assertEquals("Did not retrieved correct cached object 1. Attempt #2", cacheObject1, retrievedObject);
146         // Get object #1
147         retrievedObject = globalCache.getObject(cacheKey);
148         assertNotNull("Did not retrieved a cached object 1. Attempt #3", retrievedObject);
149         assertEquals("Did not retrieved correct cached object 1. Attempt #3", cacheObject1, retrievedObject);
150         // Get object #2
151         retrievedObject = globalCache.getObject(cacheKey_2);
152         assertNotNull("Did not retrieved a cached object 2. Attempt #2", retrievedObject);
153         assertEquals("Did not retrieved correct cached object 2 Attempt #2", cacheObject2, retrievedObject);
154         // Remove objects
155         globalCache.removeObject(cacheKey);
156         globalCache.removeObject(cacheKey_2);
157     }
158     /***
159      * Verify that an object will throw the ObjectExpiredException
160      * when it now longer exists in cache.
161      *
162      * @throws Exception
163      */
164     public void testObjectExpiration() throws Exception
165     {
166         String testString = new String("This is a test");
167         Object retrievedObject = null;
168         CachedObject cacheObject = null;
169         // Create and add Object that expires in 1000 millis (1 second)
170         cacheObject = new CachedObject(testString, 1000);
171         assertNotNull("Failed to create a cachable object", cacheObject);
172         long addTime = System.currentTimeMillis();
173         globalCache.addObject(cacheKey, cacheObject);
174         // Try to get un-expired object
175         try
176         {
177             retrievedObject = null;
178             retrievedObject = globalCache.getObject(cacheKey);
179             assertNotNull("Did not retrieved a cached object", retrievedObject);
180             assertEquals("Did not retrieved correct cached object", cacheObject, retrievedObject);
181         }
182         catch (ObjectExpiredException e)
183         {
184             assertTrue("Object expired early ( " + (System.currentTimeMillis() - addTime) + " millis)", false);
185         }
186         catch (Exception e)
187         {
188             throw e;
189         }
190         // Sleep 1500 Millis (1.5 seconds)
191         Thread.sleep(1500);
192         // Try to get expired object
193         try
194         {
195             retrievedObject = null;
196             retrievedObject = globalCache.getObject(cacheKey);
197             assertNull(
198                 "Retrieved the expired cached object  and did not get expected ObjectExpiredException",
199                 retrievedObject);
200             assertNotNull("Did not get expected ObjectExpiredException retrieving an expired object", retrievedObject);
201         }
202         catch (ObjectExpiredException e)
203         {
204             assertNull(
205                 "Retrieved the expired cached object, but caught expected ObjectExpiredException exception",
206                 retrievedObject);
207         }
208         catch (Exception e)
209         {
210             throw e;
211         }
212         // Remove objects
213         globalCache.removeObject(cacheKey);
214     }
215     /***
216      * Verify the all object will be flushed from the cache.
217      *
218      * This test can take server minutes.
219      *
220      * @throws Exception
221      */
222     public void testCacheFlush() throws Exception
223     {
224         String testString = new String("This is a test");
225         CachedObject cacheObject = null;
226         // Create and add Object that expires in 1 turbine Refresh + 1 millis
227         cacheObject = new CachedObject(testString, (getCacheRefresh() * 5) + 1);
228         assertNotNull("Failed to create a cachable object", cacheObject);
229         globalCache.addObject(cacheKey, cacheObject);
230         // 1 Refresh
231         Thread.sleep(getCacheRefresh() + 1);
232         assertTrue("No object in cache before flush", (0 < globalCache.getNumberOfObjects()));
233         // Flush Cache
234         globalCache.flushCache();
235         // Wait 15 seconds, 3 Refresh
236         Thread.sleep((getCacheRefresh() * 2) + 1);
237         assertEquals("After refresh", 0, globalCache.getNumberOfObjects());
238         // Remove objects
239         globalCache.removeObject(cacheKey);
240     }
241     /***
242      * Verify the Cache count is correct.
243      * @throws Exception
244      */
245     public void testObjectCount() throws Exception
246     {
247         assertNotNull("Could not retrive cache service.", globalCache);
248         // Create and add Object that expires in 1.5 turbine Refresh
249         long expireTime = getCacheRefresh() + getCacheRefresh() / 2;
250         CachedObject cacheObject = new CachedObject("This is a test", expireTime);
251         assertNotNull("Failed to create a cachable object", cacheObject);
252         globalCache.addObject(cacheKey, cacheObject);
253         assertEquals("After adding 1 Object", 1, globalCache.getNumberOfObjects());
254         // Wait until we're passed 1 refresh, but not half way.
255         Thread.sleep(getCacheRefresh() + getCacheRefresh() / 3);
256         assertEquals("After one refresh", 1, globalCache.getNumberOfObjects());
257         // Wait until we're passed 2 more refreshes
258         Thread.sleep((getCacheRefresh() * 2) + getCacheRefresh() / 3);
259         assertEquals("After three refreshes", 0, globalCache.getNumberOfObjects());
260     }
261     /***
262      * Verfy a refreshable object will refreshed in the following cases:
263      * o The object is retrieved via getObject an it is stale.
264      * o The object is determied to be stale during a cache
265      *   refresh
266      *
267      * This test can take serveral minutes.
268      *
269      * @throws Exception
270      */
271     public void testRefreshableObject() throws Exception
272     {
273         Object retrievedObject = null;
274         RefreshableCachedObject cacheObject = null;
275         // Create and add Object that expires in TEST_EXPIRETIME millis.
276         cacheObject = new RefreshableCachedObject(new RefreshableObject(), getTestExpireTime());
277         assertNotNull("Failed to create a cachable object", cacheObject);
278         long addTime = System.currentTimeMillis();
279         globalCache.addObject(cacheKey, cacheObject);
280         // Try to get un-expired object
281         try
282         {
283             retrievedObject = null;
284             retrievedObject = globalCache.getObject(cacheKey);
285             assertNotNull("Did not retrieved a cached object", retrievedObject);
286             assertEquals("Did not retrieved correct cached object", cacheObject, retrievedObject);
287         }
288         catch (ObjectExpiredException e)
289         {
290             assertTrue("Object expired early ( " + (System.currentTimeMillis() - addTime) + " millis)", false);
291         }
292         catch (Exception e)
293         {
294             throw e;
295         }
296         // Wait 1 Turbine cache refresh + 1 second.
297         Thread.sleep(getTestExpireTime() + 1000);
298         // Try to get expired object
299         try
300         {
301             retrievedObject = null;
302             retrievedObject = globalCache.getObject(cacheKey);
303             assertNotNull("Did not retrieved a cached object, after sleep", retrievedObject);
304             assertNotNull(
305                 "Cached object has no contents, after sleep.",
306                 ((RefreshableCachedObject) retrievedObject).getContents());
307             assertTrue(
308                 "Object did not refresh.",
309                 (((RefreshableObject) ((RefreshableCachedObject) retrievedObject).getContents()).getRefreshCount()
310                     > 0));
311         }
312         catch (ObjectExpiredException e)
313         {
314             assertTrue(
315                 "Received unexpected ObjectExpiredException exception "
316                     + "when retrieving refreshable object after ( "
317                     + (System.currentTimeMillis() - addTime)
318                     + " millis)",
319                 false);
320         }
321         catch (Exception e)
322         {
323             throw e;
324         }
325         // See if object will expires (testing every second for 100 seconds.  It sould not!
326         for (int i = 0; i < 100; i++)
327         {
328             Thread.sleep(1000); // Sleep 0.5 seconds
329             // Try to get expired object
330             try
331             {
332                 retrievedObject = null;
333                 retrievedObject = globalCache.getObject(cacheKey);
334                 assertNotNull("Did not retrieved a cached object, after sleep", retrievedObject);
335                 assertNotNull(
336                     "Cached object has no contents, after sleep.",
337                     ((RefreshableCachedObject) retrievedObject).getContents());
338                 assertTrue(
339                     "Object did not refresh.",
340                     (((RefreshableObject) ((RefreshableCachedObject) retrievedObject).getContents()).getRefreshCount()
341                         > 0));
342             }
343             catch (ObjectExpiredException e)
344             {
345                 assertTrue(
346                     "Received unexpected ObjectExpiredException exception "
347                         + "when retrieving refreshable object after ( "
348                         + (System.currentTimeMillis() - addTime)
349                         + " millis)",
350                     false);
351             }
352             catch (Exception e)
353             {
354                 throw e;
355             }
356         }
357         // Remove objects
358         globalCache.removeObject(cacheKey);
359     }
360     /***
361      * Verify a cached object will be delete after it has been
362      * untouched beyond it's TimeToLive.
363      *
364      * This test can take serveral minutes.
365      *
366      * @throws Exception
367      */
368     public void testRefreshableTimeToLive() throws Exception
369     {
370         String skipTestsProperty = System.getProperty(SKIP_TESTS_KEY,"false");
371         LOG.info("What is the value of the skipTestsProperty:" + skipTestsProperty);
372         if(Boolean.valueOf(skipTestsProperty).booleanValue()){
373             LOG.warn("Skipping testRefreshableTimeToLive test due to property " + SKIP_TESTS_KEY + " being true.");
374             return;
375         }
376         else {
377             LOG.warn("Running testRefreshableTimeToLive test due to property " + SKIP_TESTS_KEY + " being false.");
378         }
379 
380         Object retrievedObject = null;
381         RefreshableCachedObject cacheObject = null;
382         // Create and add Object that expires in TEST_EXPIRETIME millis.
383         cacheObject = new RefreshableCachedObject(new RefreshableObject(), getTestExpireTime());
384         assertNotNull("Failed to create a cachable object", cacheObject);
385         cacheObject.setTTL(getTestExpireTime());
386         // Verify TimeToLive was set
387         assertEquals("Returned TimeToLive", getTestExpireTime(), cacheObject.getTTL());
388         // Add object to Cache
389         long addTime = System.currentTimeMillis();
390         globalCache.addObject(cacheKey, cacheObject);
391         // Try to get un-expired object
392         try
393         {
394             retrievedObject = null;
395             retrievedObject = globalCache.getObject(cacheKey);
396             assertNotNull("Did not retrieved a cached object", retrievedObject);
397             assertEquals("Did not retrieved correct cached object", cacheObject, retrievedObject);
398         }
399         catch (ObjectExpiredException e)
400         {
401             fail("Object expired early ( " + (System.currentTimeMillis() - addTime) + " millis)");
402         }
403         catch (Exception e)
404         {
405             throw e;
406         }
407         // Wait long enough to allow object to expire, but do not exceed TTL
408         long timeout = getTestExpireTime() - 0000;
409         Thread.sleep(timeout);
410         // Try to get expired object
411         try
412         {
413             retrievedObject = null;
414             retrievedObject = globalCache.getObject(cacheKey);
415             assertNotNull("Did not retrieve a cached object, after sleep", retrievedObject);
416             assertNotNull(
417                 "Cached object has no contents, after sleep.",
418                 ((RefreshableCachedObject) retrievedObject).getContents());
419                 /*
420                  * @todo this is not working for some reason
421                  *
422                  *            assertTrue(
423                  *                "Object did not refresh.",
424                  *                (((RefreshableObject) ((RefreshableCachedObject) retrievedObject).getContents()).getRefreshCount()
425                  *                    > 0));
426                  */
427         }
428         catch (ObjectExpiredException e)
429         {
430             assertTrue(
431                 "Received unexpected ObjectExpiredException exception "
432                     + "when retrieving refreshable object after ( "
433                     + (System.currentTimeMillis() - addTime)
434                     + " millis)",
435                 false);
436         }
437         catch (Exception e)
438         {
439             throw e;
440         }
441         // Wait long enough to allow object to expire and exceed TTL
442         Thread.sleep(getTestExpireTime() + 5000);
443         // Try to get expired object
444         try
445         {
446             retrievedObject = null;
447             retrievedObject = globalCache.getObject(cacheKey);
448             assertNull("Retrieved a cached object, after exceeding TimeToLive", retrievedObject);
449         }
450         catch (ObjectExpiredException e)
451         {
452             assertNull(
453                 "Retrieved the expired cached object, but caught expected ObjectExpiredException exception",
454                 retrievedObject);
455         }
456         catch (Exception e)
457         {
458             throw e;
459         }
460     }
461     /***
462      * Test that we can get a list of the keys in the cache
463      *
464      * @return
465      */
466     public void testCacheGetKeyList() {
467         globalCache.flushCache();
468         globalCache.addObject("date1", new CachedObject(new Date()));
469         globalCache.addObject("date2", new CachedObject(new Date()));
470         globalCache.addObject("date3", new CachedObject(new Date()));
471         assertTrue("Did not get key list back.", (globalCache.getKeys() != null));
472         List keys = globalCache.getKeys();
473         for (Iterator itr = keys.iterator(); itr.hasNext();) {
474             Object key = itr.next();
475             assertTrue("Key was not an instance of String.", (key instanceof String));
476         }
477 
478     }
479 
480     /***
481      * Test that we can get a list of the keys in the cache
482      *
483      * @return
484      */
485     public void testCacheGetCachedObjects() {
486         globalCache.flushCache();
487         globalCache.addObject("date1", new CachedObject(new Date()));
488         globalCache.addObject("date2", new CachedObject(new Date()));
489         globalCache.addObject("date3", new CachedObject(new Date()));
490         assertTrue("Did not get object list back.", (globalCache.getCachedObjects() != null));
491         List objects = globalCache.getCachedObjects();
492         for (Iterator itr = objects.iterator(); itr.hasNext();) {
493             Object obj = itr.next();
494             assertNotNull("Object was null.", obj);
495             assertTrue("Object was not an instance of CachedObject", (obj instanceof CachedObject));
496         }
497 
498     }
499 
500     /***
501      * Test that the retrieved list is safe from
502      * ConcurrentModificationException's being thrown if the cache
503      * is updated while we are iterating over the List.
504      *
505      * @return
506      */
507     public void testCacheModification() {
508         globalCache.flushCache();
509         globalCache.addObject("date1", new CachedObject(new Date()));
510         globalCache.addObject("date2", new CachedObject(new Date()));
511         globalCache.addObject("date3", new CachedObject(new Date()));
512         assertTrue("Did not get key list back.", (globalCache.getKeys() != null));
513         List keys = globalCache.getKeys();
514         try {
515 	        for (Iterator itr = keys.iterator(); itr.hasNext();) {
516 	            Object key = itr.next();
517 	            globalCache.addObject("date4", new CachedObject(new Date()));
518 	        }
519         } catch (ConcurrentModificationException cme)
520         {
521             fail("Caught ConcurrentModificationException adding to cache.");
522         }
523         List objects = globalCache.getCachedObjects();
524         try {
525 	        for (Iterator itr = objects.iterator(); itr.hasNext();) {
526 	            Object obj = itr.next();
527 	            globalCache.addObject("date4", new CachedObject(new Date()));
528 	        }
529         } catch (ConcurrentModificationException cme)
530         {
531             fail("Caught ConcurrentModificationException adding to cache.");
532         }
533     }
534 
535 
536     /***
537      * Down cast the interface to the concreate object in order to grab the
538      * cache check frequency.
539      * @return the refresh requency in milliseconds
540      */
541     private long getCacheRefresh()
542     {
543         return ((DefaultGlobalCacheService) globalCache).getCacheCheckFrequency()*1000;
544     }
545 
546     /***
547      * How long until it expires
548      * @return the cache refresh plus 1000.
549      */
550     private long getTestExpireTime()
551     {
552         return getCacheRefresh() + 1000;
553     }
554 
555 
556 	private long getTestTimeToLive()
557 	{
558 		return getTestExpireTime()*5;
559 	}
560     /***
561      * Simple object that can be refreshed
562      */
563     class RefreshableObject implements Refreshable
564     {
565         private int refreshCount = 0;
566         /***
567          * Increment the refresh counter
568          */
569         public void refresh()
570         {
571             this.refreshCount++;
572         }
573         /***
574          * Reutrn the number of time this object has been refreshed
575          *
576          * @return Number of times refresh() has been called
577          */
578         public int getRefreshCount()
579         {
580             return this.refreshCount;
581         }
582     }
583 }