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/08/29 00:20:34 UTC

svn commit: r690027 - 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: Thu Aug 28 15:20:33 2008
New Revision: 690027

URL: http://svn.apache.org/viewvc?rev=690027&view=rev
Log:
Cache class that enforces a TTL for entries in it.


Added:
    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

Added: 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=690027&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java Thu Aug 28 15:20:33 2008
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.common.cache;
+
+import org.apache.shindig.common.util.TimeSource;
+
+/**
+ * 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.
+ */
+public class TtlCache<K, V> {
+  private final Cache<K, TimeoutPair<V>> baseCache;
+  private final long minTtl;
+  private final long maxTtl;
+  private TimeSource timeSource;
+  
+  /**
+   * Create a new TtlCache with the given capacity and TTL values.
+   * The cache provider provides an implementation of the actual storage.
+   * @param cacheProvider Creator of the actual cache.
+   * @param capacity Size of the underlying cache.
+   * @param minTtl Minimum amount of time a given entry must stay in the cache, in millis.
+   * @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();
+  }
+  
+  /**
+   * Retrieve an element from the cache by key. If there is no such element
+   * for that key in the cache, or if the element has timed out, null is returned.
+   * @param key Key whose element to look up.
+   * @return Element in the cache, if present and not timed out.
+   */
+  public V getElement(K key) {
+	return getElementMaybeRemove(key, false);
+  }
+  
+  /**
+   * Add an element to the cache, with the intended amount of time
+   * 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 lifetime Intended lifetime, in millis, of the element's entry.
+   */
+  public void addElement(K key, V val, long lifetime) {
+	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);
+	}
+  }
+  
+  /**
+   * Removes element for the given key from the cache. Returns it if
+   * it hasn't yet expired.
+   * @param key Element key.
+   * @return Element value.
+   */
+  public V removeElement(K key) {
+	return getElementMaybeRemove(key, true);
+  }
+  
+  /**
+   * 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;
+  }
+  
+  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;
+	}
+
+	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;
+  }
+  
+  /**
+   * Actual stored content in the cache. Saves the cached object along
+   * with its expiration date (in ms).
+   * @param <V> Type of stored object.
+   */
+  private static final class TimeoutPair<V> {
+	private V cachedObj;
+	private long expiration;
+
+	private TimeoutPair(V cachedObj, long expiration) {
+	  this.cachedObj = cachedObj;
+	  this.expiration = expiration;
+	}
+  }
+}

Added: 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=690027&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java (added)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/cache/TtlCacheTest.java Thu Aug 28 15:20:33 2008
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.common.cache;
+
+import org.apache.shindig.common.util.FakeTimeSource;
+
+import junit.framework.TestCase;
+
+public class TtlCacheTest extends TestCase {
+  private FakeTimeSource timeSource;
+  private CacheProvider cacheProvider;
+  
+  @Override
+  public void setUp() throws Exception {
+	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;
+  }
+  
+  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));
+  }
+  
+  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));
+  }
+  
+  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));
+  }
+  
+  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));
+  }
+}