Author: johnh
Date: Mon Sep  8 17:26:28 2008
New Revision: 693325

URL: http://svn.apache.org/viewvc?rev=693325&view=rev
Log:
Improving TtlCache in preparation for using it throughout code where necessary 
to help clean up various APIs (eg. AbstractHttpCache, SHINDIG-579).

* TtlCache implements the Cache interface, with default behaviors for 
getElement and addElement. Because why not.
* New method getElementWithExpiration returns whether or not the returned 
element has expired along with the element itself. Callers can choose what to 
do with the results at that point.


Modified:
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java?rev=693325&r1=693324&r2=693325&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java
 Mon Sep  8 17:26:28 2008
@@ -24,18 +24,32 @@
  * Cache enforcing a Time-To-Live value atop whatever other base
  * caching characteristics are provided. A minimum and maximum
  * TTL is provided to ensure that TTL values are within reasonable
- * limits. This class contains a Cache but doesn't implement the
- * Cache interface due to the necessity of changing its addElement()
- * signature. If needed, however, it could implement addElement without
- * an explicitly provided object lifetime by using minTtl in that case.
- * @param <K> Key for the cache.
- * @param <V> Value the cache stores by Key.
+ * limits. Objects are not forced out of the cache after their TTL;
+ * they're simply treated as invalid, unless "stale" entries are
+ * explicitly requested.
+ * 
+ * Two sets of APIs are supported:
+ * 1. The standard Cache interface. addElement() adds to the cache
+ * with a particular default lifetime, which may be set separately. Without
+ * being overridden, its default value is 0 (no TTL), which applies subject
+ * to minTtl and maxTtl configured for the cache. getElement() retrieves
+ * an element from the cache subject to configured expiration.
+ * 2. Extended TTL and expired-object caching interfaces. An addElement
+ * method is provided with a requested expiration for the object, which is
+ * in turn subject to minTtl and maxTtl restrictions as configured for the
+ * cache. Method getElementWithExpiration is provided which returns the cached 
object
+ * along with whether or not it is expired.
+ * @param <K> Type of key for the cache.
+ * @param <V> Type of value the cache stores by Key.
  */
-public class TtlCache<K, V> {
+public class TtlCache<K, V> implements Cache<K, V> {
   private final Cache<K, TimeoutPair<V>> baseCache;
   private final long minTtl;
   private final long maxTtl;
   private TimeSource timeSource;
+  private long defaultLifetime;
+  
+  private static final long DEFAULT_LIFETIME_MILLIS = 0;
   
   /**
    * Create a new TtlCache with the given capacity and TTL values.
@@ -46,10 +60,20 @@
    * @param maxTtl Maximum amount of time a given entry can stay in the cache, 
in millis.
    */
   public TtlCache(CacheProvider cacheProvider, int capacity, long minTtl, long 
maxTtl) {
-       this.baseCache = cacheProvider.createCache(capacity);
-       this.minTtl = minTtl;
-       this.maxTtl = maxTtl;
-       this.timeSource = new TimeSource();
+    this.baseCache = cacheProvider.createCache(capacity);
+    this.minTtl = minTtl;
+    this.maxTtl = maxTtl;
+    this.timeSource = new TimeSource();
+    this.defaultLifetime = DEFAULT_LIFETIME_MILLIS;
+  }
+  
+  /**
+   * Sets the default lifetime of a given element added to the cache (using 
the standard
+   * addElement method), in milliseconds.
+   * @param defaultLifetime
+   */
+  public void setDefaultLifetimeMillis(long defaultLifetime) {
+    this.defaultLifetime = defaultLifetime;
   }
   
   /**
@@ -59,7 +83,24 @@
    * @return Element in the cache, if present and not timed out.
    */
   public V getElement(K key) {
-       return getElementMaybeRemove(key, false);
+    CachedObject<V> cached = getElementMaybeRemove(key, false);
+    
+    if (!cached.isExpired) {
+      return cached.obj;
+    }
+    
+    return null;
+  }
+  
+  /**
+   * Retrieve an element from the cache along with whether or not it is
+   * expired. A "stale" element may be used by calling code if it so
+   * chooses, ie. if it's unable to pull a "fresh" version of content.
+   * @param key Key whose element to look up.
+   * @return Pair of cached element and whether or not it is expired.
+   */
+  public CachedObject<V> getElementWithExpiration(K key) {
+    return getElementMaybeRemove(key, false);
   }
   
   /**
@@ -67,17 +108,27 @@
    * it should live in the cache provided in milliseconds. If below
    * minTtl, minTtl is used. If above maxTtl, maxTtl is used.
    * @param key Element key.
-   * @param val Cached element value.
+   * @param val Element value to cache.
    * @param lifetime Intended lifetime, in millis, of the element's entry.
    */
   public void addElement(K key, V val, long lifetime) {
-       long now = timeSource.currentTimeMillis();
+    long now = timeSource.currentTimeMillis();
     long expiration = lifetime;
     expiration = Math.max(now + minTtl, Math.min(now + maxTtl, expiration));
-       TimeoutPair<V> entry = new TimeoutPair<V>(val, expiration);
-       synchronized(baseCache) {
-         baseCache.addElement(key, entry);
-       }
+    TimeoutPair<V> entry = new TimeoutPair<V>(val, expiration);
+    synchronized(baseCache) {
+      baseCache.addElement(key, entry);
+    }
+  }
+  
+  /**
+   * Add an element to the cache, with lifetime set to the default configured
+   * for this cache object.
+   * @param key Element key.
+   * @param val Element value to cache.
+   */
+  public void addElement(K key, V val) {
+    addElement(key, val, defaultLifetime);
   }
   
   /**
@@ -87,36 +138,38 @@
    * @return Element value.
    */
   public V removeElement(K key) {
-       return getElementMaybeRemove(key, true);
-  }
+    CachedObject<V> cached = getElementMaybeRemove(key, true);
+    
+    if (!cached.isExpired) {
+      return cached.obj;
+    }
+    
+    return null;
+  } 
   
   /**
    * Set a new time source. Used for testing, so package-private.
    * @param timeSource New time source to use.
    */
   void setTimeSource(TimeSource timeSource) {
-       this.timeSource = timeSource;
+    this.timeSource = timeSource;
   }
   
-  private V getElementMaybeRemove(K key, boolean remove) {
-       TimeoutPair<V> entry = null;
-       if (remove) {
-         entry = baseCache.removeElement(key);
-       } else {
-         entry = baseCache.getElement(key);
-       }
-       if (entry == null) {
-         return null;
-       }
+  private CachedObject<V> getElementMaybeRemove(K key, boolean remove) {
+    TimeoutPair<V> entry = null;
+    
+    if (remove) {
+      entry = baseCache.removeElement(key);
+    } else {
+      entry = baseCache.getElement(key);
+         }
+         if (entry == null) {
+           return new CachedObject<V>((V)null, true);
+         }
 
-       long now = timeSource.currentTimeMillis();
-       if (now < entry.expiration) {
-      // Not yet timed out. Still valid, so return.
-         return entry.cachedObj;
-       }
-               
-       // No need to clean up the cache - that happens under the covers.
-       return null;
+         long now = timeSource.currentTimeMillis();
+         
+         return new CachedObject<V>(entry.cachedObj, now >= entry.expiration);
   }
   
   /**
@@ -125,12 +178,22 @@
    * @param <V> Type of stored object.
    */
   private static final class TimeoutPair<V> {
-       private V cachedObj;
-       private long expiration;
+    private V cachedObj;
+    private long expiration;
 
-       private TimeoutPair(V cachedObj, long expiration) {
-         this.cachedObj = cachedObj;
-         this.expiration = expiration;
-       }
+    private TimeoutPair(V cachedObj, long expiration) {
+      this.cachedObj = cachedObj;
+      this.expiration = expiration;
+    }
+  }
+  
+  public static class CachedObject<V> {
+    public V obj;
+    public boolean isExpired;
+    
+    private CachedObject(V obj, boolean isExpired) {
+      this.obj = obj;
+      this.isExpired = isExpired;
+    }
   }
 }

Modified: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java?rev=693325&r1=693324&r2=693325&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java
 Mon Sep  8 17:26:28 2008
@@ -28,91 +28,99 @@
   
   @Override
   public void setUp() throws Exception {
-       timeSource = new FakeTimeSource(0);
-       cacheProvider = new DefaultCacheProvider();
+    timeSource = new FakeTimeSource(0);
+    cacheProvider = new DefaultCacheProvider();
   }
   
   // Capacity just needs to be big enough to retain elements.
   private static final int CAPACITY = 100;
   private TtlCache<String, String> makeTtlCache(long minTtl, long maxTtl) {
-       TtlCache<String, String> ttlCache =
-               new TtlCache<String, String>(cacheProvider, CAPACITY, minTtl, 
maxTtl);
-       ttlCache.setTimeSource(timeSource);
-       return ttlCache;
+    TtlCache<String, String> ttlCache =
+        new TtlCache<String, String>(cacheProvider, CAPACITY, minTtl, maxTtl);
+    ttlCache.setTimeSource(timeSource);
+    return ttlCache;
   }
   
   public void testGeneralCacheExpiration() {
-       TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 
1000);
-       String key = "key1", val = "val1";
-       ttlCache.addElement(key, val, 240 * 1000);
-       
-       // Time is still 0: should be in the cache.
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time = 120 seconds: still in cache.
-       timeSource.setCurrentTimeMillis(120 * 1000);
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time = 240 seconds - 1 ms: still in cache.
-       timeSource.setCurrentTimeMillis(240 * 1000 - 1);
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time = 300 seconds: out of cache.
-       timeSource.setCurrentTimeMillis(300 * 1000);
-       assertNull(ttlCache.getElement(key));
+    TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 1000);
+    String key = "key1", val = "val1";
+    ttlCache.addElement(key, val, 240 * 1000);
+       
+    // Time is still 0: should be in the cache.
+    assertEquals(val, ttlCache.getElement(key));
+    assertEquals(val, ttlCache.getElementWithExpiration(key).obj);
+    assertFalse(ttlCache.getElementWithExpiration(key).isExpired);
+       
+    // Time = 120 seconds: still in cache.
+    timeSource.setCurrentTimeMillis(120 * 1000);
+    assertEquals(val, ttlCache.getElement(key));
+    assertEquals(val, ttlCache.getElementWithExpiration(key).obj);
+    assertFalse(ttlCache.getElementWithExpiration(key).isExpired);
+       
+    // Time = 240 seconds - 1 ms: still in cache.
+    timeSource.setCurrentTimeMillis(240 * 1000 - 1);
+    assertEquals(val, ttlCache.getElement(key));
+    assertEquals(val, ttlCache.getElementWithExpiration(key).obj);
+    assertFalse(ttlCache.getElementWithExpiration(key).isExpired);
+       
+    // Time = 300 seconds: out of cache.
+    timeSource.setCurrentTimeMillis(300 * 1000);
+    assertNull(ttlCache.getElement(key));
+    assertEquals(val, ttlCache.getElementWithExpiration(key).obj);
+    assertTrue(ttlCache.getElementWithExpiration(key).isExpired);
   }
   
   public void testRemoveFromCache() {
-       TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 
1000);
-       String key = "key1", val = "val1";
-       ttlCache.addElement(key, val, 240 * 1000);
-       
-       // Time at 0: still in cache
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time at 120: still in cache, but removing it.
-       timeSource.setCurrentTimeMillis(120 * 1000);
-       assertEquals(val, ttlCache.removeElement(key));
-       
-       // Still at 120: should be gone.
-       assertNull(ttlCache.removeElement(key));
+    TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 1000);
+    String key = "key1", val = "val1";
+    ttlCache.addElement(key, val, 240 * 1000);
+    
+    // Time at 0: still in cache
+    assertEquals(val, ttlCache.getElement(key));
+    
+    // Time at 120: still in cache, but removing it.
+    timeSource.setCurrentTimeMillis(120 * 1000);
+    assertEquals(val, ttlCache.removeElement(key));
+    
+    // Still at 120: should be gone.
+    assertNull(ttlCache.removeElement(key));
   }
   
   public void testCacheMinTtl() {
-       TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 
1000);
-       String key = "key1", val = "val1";
-       
-       // Add with a value below minTtl
-       ttlCache.addElement(key, val, 60 * 1000);
-       
-       // Time 0: still in cache.
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time 65: still in cache - not expired! minTtl takes precedence.
-       timeSource.setCurrentTimeMillis(65 * 1000);
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time 121: out of cache.
-       timeSource.setCurrentTimeMillis(121 * 1000);
-       assertNull(ttlCache.getElement(key));
+    TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 1000);
+    String key = "key1", val = "val1";
+    
+    // Add with a value below minTtl
+    ttlCache.addElement(key, val, 60 * 1000);
+    
+    // Time 0: still in cache.
+    assertEquals(val, ttlCache.getElement(key));
+    
+    // Time 65: still in cache - not expired! minTtl takes precedence.
+    timeSource.setCurrentTimeMillis(65 * 1000);
+    assertEquals(val, ttlCache.getElement(key));
+    
+    // Time 121: out of cache.
+    timeSource.setCurrentTimeMillis(121 * 1000);
+    assertNull(ttlCache.getElement(key));
   }
   
   public void testCacheMaxTtl() {
-       TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 
1000);
-       String key = "key1", val = "val1";
-       
-       // Add with a value above maxTtl
-       ttlCache.addElement(key, val, 400 * 1000);
-       
-       // Time 0: still in cache.
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time 360 - 1ms: still in cache.
-       timeSource.setCurrentTimeMillis(360 * 1000 - 1);
-       assertEquals(val, ttlCache.getElement(key));
-       
-       // Time 361: out of cache. Expired despite "desired" ttl of 400 secs.
-       timeSource.setCurrentTimeMillis(361 * 1000);
-       assertNull(ttlCache.getElement(key));
+    TtlCache<String, String> ttlCache = makeTtlCache(120 * 1000, 360 * 1000);
+    String key = "key1", val = "val1";
+    
+    // Add with a value above maxTtl
+    ttlCache.addElement(key, val, 400 * 1000);
+    
+    // Time 0: still in cache.
+    assertEquals(val, ttlCache.getElement(key));
+    
+    // Time 360 - 1ms: still in cache.
+    timeSource.setCurrentTimeMillis(360 * 1000 - 1);
+    assertEquals(val, ttlCache.getElement(key));
+    
+    // Time 361: out of cache. Expired despite "desired" ttl of 400 secs.
+    timeSource.setCurrentTimeMillis(361 * 1000);
+    assertNull(ttlCache.getElement(key));
   }
 }


Reply via email to