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