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