You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by jo...@apache.org on 2008/09/09 02:26:28 UTC

svn commit: r693325 - in /incubator/shindig/trunk/java/common/src: main/java/org/apache/shindig/common/cache/TtlCache.java test/java/org/apache/shindig/common/cache/TtlCacheTest.java

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));
   }
 }