001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.fulcrum.cache; 021 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertNotNull; 024import static org.junit.jupiter.api.Assertions.assertNull; 025import static org.junit.jupiter.api.Assertions.assertSame; 026import static org.junit.jupiter.api.Assertions.assertTrue; 027import static org.junit.jupiter.api.Assertions.fail; 028 029import java.util.ConcurrentModificationException; 030import java.util.Date; 031import java.util.List; 032 033import org.apache.avalon.framework.component.ComponentException; 034import org.apache.fulcrum.cache.impl.DefaultGlobalCacheService; 035import org.apache.fulcrum.testcontainer.BaseUnit5Test; 036import org.apache.logging.log4j.LogManager; 037import org.apache.logging.log4j.Logger; 038import org.junit.jupiter.api.AfterEach; 039import org.junit.jupiter.api.BeforeEach; 040import org.junit.jupiter.api.Tag; 041import org.junit.jupiter.api.Test; 042 043/** 044 * CacheTest 045 * 046 * @author <a href="paulsp@apache.org">Paul Spencer</a> 047 * @author <a href="epugh@upstate.com">Eric Pugh</a> 048 * @author <a href="mailto:peter@courefreshableCachedObjectux.biz">Peter CourefreshableCachedObjectux</a> 049 * @version $Id$ 050 */ 051public class CacheTest extends BaseUnit5Test 052{ 053 054 protected GlobalCacheService globalCache = null; 055 056 protected static final String cacheKey = "CacheKey"; 057 058 protected static final String cacheKey_2 = "CacheKey_2"; 059 060 protected static final Logger log = LogManager.getLogger( CacheTest.class ); 061 062 static { 063 String logSystem = System.getProperty("jcs.logSystem", null); 064 if (logSystem == null) { 065 System.setProperty("jcs.logSystem", "log4j2" ); 066 log.info( "Setting jcs.logSystem to: log4j2"); 067 logSystem = System.getProperty("jcs.logSystem", null); 068 } 069 log.warn( "What is the value of the jcs.logSystem: "+ logSystem); 070 071 } 072 073 /** 074 * Method to configure the role name of the service used 075 * 076 * @return the role name of the service to lookup 077 */ 078 protected String getCacheRoleName() 079 { 080 return GlobalCacheService.ROLE; 081 } 082 083 @BeforeEach 084 protected void setUp() throws Exception 085 { 086 System.out.println( "Testing service: "+ getClass().getName() + "for "+ getCacheRoleName()); 087 //if (globalCache == null) { 088 try 089 { 090 globalCache = (GlobalCacheService) this 091 .lookup(getCacheRoleName()); 092 } 093 catch (ComponentException e) 094 { 095 e.printStackTrace(); 096 fail(e.getMessage()); 097 } 098 //} 099 } 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}