Author: johnh
Date: Tue Sep 23 18:12:28 2008
New Revision: 698411
URL: http://svn.apache.org/viewvc?rev=698411&view=rev
Log:
CachingContentRewriterRegistry uses TTL values provided by rewriters. At
present, the implementation is simply that the TTL for a given entry is th e
minimum TTL of all rewriters in a given pass.
Also includes a fix to TtlCache accommodating the case where now + ttl overruns
Long type limits.
This CL completes SHINDIG-616.
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/cache/TtlCache.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CaptureRewriter.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/AppendingRewriter.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.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=698411&r1=698410&r2=698411&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
Tue Sep 23 18:12:28 2008
@@ -104,8 +104,8 @@
}
/**
- * Add an element to the cache, with the intended amount of time
- * it should live in the cache provided in milliseconds. If below
+ * Add an element to the cache, with the intended expiration time
+ * for its cache entry provided in milliseconds. If below
* minTtl, minTtl is used. If above maxTtl, maxTtl is used.
* @param key Element key.
* @param val Element value to cache.
@@ -113,7 +113,12 @@
*/
public void addElement(K key, V val, long expiration) {
long now = timeSource.currentTimeMillis();
- expiration = Math.max(now + minTtl, Math.min(now + maxTtl, expiration));
+ long minAllowed = Math.min(now + maxTtl, expiration);
+ if (now > 0 && maxTtl > 0 && minAllowed < 0) {
+ // Long-value overflow.
+ minAllowed = maxTtl;
+ }
+ expiration = Math.max(now + minTtl, minAllowed);
TimeoutPair<V> entry = new TimeoutPair<V>(val, expiration);
synchronized(baseCache) {
baseCache.addElement(key, entry);
@@ -121,6 +126,21 @@
}
/**
+ * Add an element to the cache with the intended number of milliseconds,
+ * from the time of add, it is expected to live in the cache. This method
+ * is less precise than standard [EMAIL PROTECTED] addElement}, to which is
specified
+ * the exact expiration time (also in ms) of the entry, due to calculation
+ * of the current time as relative to the add call. In many scenarios,
+ * however, this behavior is perfectly acceptable. It is provided as a
convenience.
+ * @param key Element key.
+ * @param val Element value to cache.
+ * @param ttl Amount of time, in millis, for the cache entry to be valid.
+ */
+ public void addElementWithTtl(K key, V val, long ttl) {
+ addElement(key, val, System.currentTimeMillis() + ttl);
+ }
+
+ /**
* Add an element to the cache, with lifetime set to the default configured
* for this cache object.
* @param key Element key.
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java?rev=698411&r1=698410&r2=698411&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
Tue Sep 23 18:12:28 2008
@@ -17,11 +17,11 @@
*/
package org.apache.shindig.gadgets.rewrite;
-import org.apache.shindig.common.cache.Cache;
import org.apache.shindig.common.cache.CacheProvider;
+import org.apache.shindig.common.cache.TtlCache;
import org.apache.shindig.common.util.HashUtil;
import org.apache.shindig.gadgets.Gadget;
-import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.MutableContent;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
@@ -41,17 +41,30 @@
*/
public class CachingContentRewriterRegistry extends
DefaultContentRewriterRegistry {
- private final Cache<String, String> rewrittenCache;
+ private final TtlCache<String, String> rewrittenCache;
+ private long minCacheTtl;
private String rewritersKey;
+ /**
+ * Creates a registry with underlying cache configured by the provided
params.
+ * @param htmlParser Parser used to generate parse tree versions of content.
+ * @param cacheProvider Used to generate a cache instance.
+ * @param capacity Maximum number of rewritten content entries to store in
the cache.
+ * @param minCacheTtl Minimum TTL value, in milliseconds, that it makes
sense to cache an entry.
+ */
@Inject
public CachingContentRewriterRegistry(List<ContentRewriter> rewriters,
GadgetHtmlParser htmlParser,
CacheProvider cacheProvider,
- @Named("shindig.rewritten-content.cache.capacity")int capacity) {
+ @Named("shindig.rewritten-content.cache.capacity")int capacity,
+ @Named("shindig.rewritten-content.cache.minTTL")long minCacheTtl) {
super(rewriters, htmlParser);
-
- rewrittenCache = cacheProvider.createCache(capacity);
+ // minTtl = 0 and maxTtl = MAX_VALUE because the underlying cache is
willing to store data
+ // with any TTL value specified. Entries are added with a given TTL value
per slightly
+ // different logic by this class: if a rewrite pass has a cacheTtl lower
than minCacheTtl,
+ // it's simply not added.
+ rewrittenCache = new TtlCache<String, String>(cacheProvider, capacity, 0,
Long.MAX_VALUE);
+ this.minCacheTtl = minCacheTtl;
}
protected String getGadgetCacheKey(Gadget gadget) {
@@ -69,7 +82,7 @@
StringBuilder keyBuilder = new StringBuilder();
for (ContentRewriter rewriter : rewriters) {
keyBuilder.append(rewriter.getClass().getCanonicalName())
- .append('-').append(rewriter.getClass().hashCode());
+ .append("-").append(rewriter.getClass().hashCode()).append(":");
}
rewritersKey = keyBuilder.toString();
}
@@ -78,7 +91,7 @@
/** [EMAIL PROTECTED] */
@Override
- public boolean rewriteGadget(Gadget gadget) throws GadgetException {
+ public boolean rewriteGadget(Gadget gadget) {
if (gadget.getContext().getIgnoreCache()) {
return super.rewriteGadget(gadget);
}
@@ -91,14 +104,25 @@
return true;
}
- // Do a fresh rewrite and cache the results.
- boolean rewritten = super.rewriteGadget(gadget);
- if (rewritten) {
- // Only cache if the rewriters did something.
- rewrittenCache.addElement(cacheKey, gadget.getContent());
+ long cacheTtl = Long.MAX_VALUE;
+ String original = gadget.getContent();
+ for (ContentRewriter rewriter : getRewriters()) {
+ RewriterResults rr = rewriter.rewrite(gadget);
+ if (rr == null) {
+ cacheTtl = 0;
+ } else {
+ cacheTtl = Math.min(cacheTtl, rr.getCacheTtl());
+ }
+ }
+
+ if (cacheTtl >= minCacheTtl) {
+ // Only cache if the cacheTtl is greater than the minimum time
configured for doing so.
+ // This prevents cache churn and may be more efficient when rewriting is
cheaper
+ // than a cache lookup.
+ rewrittenCache.addElementWithTtl(cacheKey, gadget.getContent(),
cacheTtl);
}
- return rewritten;
+ return !original.equals(gadget.getContent());
}
/** [EMAIL PROTECTED] */
@@ -115,12 +139,32 @@
return new HttpResponseBuilder(resp).setResponseString(cached).create();
}
- HttpResponse rewritten = super.rewriteHttpResponse(req, resp);
- if (rewritten != null) {
- // All these are bounded by the provided TTLs
- rewrittenCache.addElement(cacheKey, rewritten.getResponseAsString());
+ String original = resp.getResponseAsString();
+ MutableContent mc = getMutableContent(original);
+ long cacheTtl = Long.MAX_VALUE;
+ for (ContentRewriter rewriter : getRewriters()) {
+ RewriterResults rr = rewriter.rewrite(req, resp, mc);
+ if (rr == null) {
+ cacheTtl = 0;
+ } else {
+ cacheTtl = Math.min(cacheTtl, rr.getCacheTtl());
+ }
}
- return rewritten;
+ if (cacheTtl >= minCacheTtl) {
+ rewrittenCache.addElementWithTtl(cacheKey, mc.getContent(), cacheTtl);
+ }
+
+ if (!original.equals(mc.getContent())) {
+ return new
HttpResponseBuilder(resp).setResponseString(mc.getContent()).create();
+ }
+
+ // Not rewritten, just return original.
+ return resp;
+ }
+
+ // Methods for testing purposes
+ void setMinCacheTtl(long minCacheTtl) {
+ this.minCacheTtl = minCacheTtl;
}
}
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CaptureRewriter.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CaptureRewriter.java?rev=698411&r1=698410&r2=698411&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CaptureRewriter.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CaptureRewriter.java
Tue Sep 23 18:12:28 2008
@@ -29,11 +29,12 @@
public class CaptureRewriter implements ContentRewriter {
private boolean rewroteView = false;
private boolean rewroteResponse = false;
+ private long cacheTtl = -1;
public RewriterResults rewrite(HttpRequest request, HttpResponse original,
MutableContent content) {
rewroteResponse = true;
- return null;
+ return results();
}
public boolean responseWasRewritten() {
@@ -42,10 +43,25 @@
public RewriterResults rewrite(Gadget gadget) {
rewroteView = true;
- return null;
+ return results();
}
public boolean viewWasRewritten() {
return rewroteView;
}
+
+ private RewriterResults results() {
+ if (cacheTtl == -1) {
+ return RewriterResults.cacheableIndefinitely();
+ }
+ return RewriterResults.cacheable(cacheTtl);
+ }
+
+ /**
+ * Sets cache TTL. -1 means cacheable indefinitely.
+ * @param cacheTtl
+ */
+ public void setCacheTtl(long cacheTtl) {
+ this.cacheTtl = cacheTtl;
+ }
}
\ No newline at end of file
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java?rev=698411&r1=698410&r2=698411&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
Tue Sep 23 18:12:28 2008
@@ -18,7 +18,6 @@
package org.apache.shindig.gadgets.rewrite;
import org.apache.shindig.gadgets.Gadget;
-import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.MutableContent;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
@@ -51,7 +50,7 @@
}
/** [EMAIL PROTECTED] */
- public boolean rewriteGadget(Gadget gadget) throws GadgetException {
+ public boolean rewriteGadget(Gadget gadget) {
String originalContent = gadget.getContent();
if (originalContent == null) {
@@ -68,9 +67,8 @@
/** [EMAIL PROTECTED] */
public HttpResponse rewriteHttpResponse(HttpRequest req, HttpResponse resp) {
- MutableContent mc = new MutableContent(htmlParser);
String originalContent = resp.getResponseAsString();
- mc.setContent(originalContent);
+ MutableContent mc = getMutableContent(originalContent);
for (ContentRewriter rewriter : rewriters) {
rewriter.rewrite(req, resp, mc);
@@ -84,4 +82,13 @@
return new
HttpResponseBuilder(resp).setResponseString(rewrittenContent).create();
}
+ protected MutableContent getMutableContent(String content) {
+ MutableContent mc = new MutableContent(htmlParser);
+ mc.setContent(content);
+ return mc;
+ }
+
+ protected List<ContentRewriter> getRewriters() {
+ return rewriters;
+ }
}
Modified:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/AppendingRewriter.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/AppendingRewriter.java?rev=698411&r1=698410&r2=698411&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/AppendingRewriter.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/AppendingRewriter.java
Tue Sep 23 18:12:28 2008
@@ -28,22 +28,35 @@
* Used for testing.
*/
class AppendingRewriter implements ContentRewriter {
- private final String appender;
+ private String appender;
+ private final long cacheTtl;
AppendingRewriter(String appender) {
this.appender = appender;
+ this.cacheTtl = 0;
+ }
+
+ AppendingRewriter(String appender, long cacheTtl) {
+ this.appender = appender;
+ this.cacheTtl = cacheTtl;
}
public RewriterResults rewrite(HttpRequest request, HttpResponse original,
MutableContent c) {
// Appends appender to the end of the content string.
c.setContent(c.getContent() + appender);
- return RewriterResults.cacheableIndefinitely();
+ return RewriterResults.cacheable(cacheTtl);
}
public RewriterResults rewrite(Gadget gadget) {
// Appends appender to the end of the input string.
gadget.setContent(gadget.getContent() + appender);
- return RewriterResults.cacheableIndefinitely();
+ return RewriterResults.cacheable(cacheTtl);
+ }
+
+ void setAppender(String newAppender) {
+ // This can be used to simulate a rewriter that returns different
+ // results per run for the same input content.
+ this.appender = newAppender;
}
}
\ No newline at end of file
Modified:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java?rev=698411&r1=698410&r2=698411&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
Tue Sep 23 18:12:28 2008
@@ -46,13 +46,14 @@
import java.util.Map;
public class CachingContentRewriterRegistryTest {
+ private final CaptureRewriter captureRewriter = new CaptureRewriter();
private final List<CaptureRewriter> rewriters
- = Lists.newArrayList(new CaptureRewriter(), new
ModifyingCaptureContentRewriter());
+ = Lists.newArrayList(captureRewriter, new
ModifyingCaptureContentRewriter());
private final List<ContentRewriter> contentRewriters
= Lists.<ContentRewriter>newArrayList(rewriters);
private final FakeCacheProvider provider = new FakeCacheProvider();
- private final ContentRewriterRegistry registry
- = new CachingContentRewriterRegistry(contentRewriters, null, provider,
100);
+ private final CachingContentRewriterRegistry registry
+ = new CachingContentRewriterRegistry(contentRewriters, null, provider,
100, 0);
private final IMocksControl control = EasyMock.createNiceControl();
private final ContainerConfig config =
control.createMock(ContainerConfig.class);
@@ -92,7 +93,6 @@
gadget = new Gadget(context, spec, new ArrayList<JsLibrary>(), config,
null);
registry.rewriteGadget(gadget);
- // TODO: We're not actually testing the TTL of the entries here.
assertEquals(3, provider.readCount);
assertEquals(1, provider.writeCount);
}
@@ -181,7 +181,7 @@
// The new registry is created using one additional rewriter, but the same
cache.
contentRewriters.add(new CaptureRewriter());
ContentRewriterRegistry newRegistry
- = new CachingContentRewriterRegistry(contentRewriters, null, provider,
100);
+ = new CachingContentRewriterRegistry(contentRewriters, null, provider,
100, 0);
newRegistry.rewriteHttpResponse(request, response);
@@ -190,6 +190,27 @@
assertFalse("Cache was written using identical keys.",
provider.keys.get(0).equals(provider.keys.get(1)));
}
+
+ @Test
+ public void rewriteBelowMinCacheDoesntWriteToCache() throws Exception {
+ registry.setMinCacheTtl(1000);
+ captureRewriter.setCacheTtl(500);
+
+ String body = "Hello, world";
+ String xml = "<Module><ModulePrefs title=''/><Content>" + body +
"</Content></Module>";
+ GadgetSpec spec = new GadgetSpec(URI.create("#"), xml);
+ GadgetContext context = new GadgetContext();
+
+ control.replay();
+
+ // We have to re-create Gadget objects because they get mutated directly,
which is really
+ // inconsistent with the behavior of rewriteHttpResponse.
+ Gadget gadget = new Gadget(context, spec, new ArrayList<JsLibrary>(),
config, null);
+ registry.rewriteGadget(gadget);
+
+ assertEquals(1, provider.readCount);
+ assertEquals(0, provider.writeCount);
+ }
private static class FakeCacheProvider implements CacheProvider {
private final Map<String, Object> entries = Maps.newHashMap();
@@ -239,5 +260,7 @@
gadget.setContent(gadget.getContent() + "-modified");
return RewriterResults.cacheableIndefinitely();
}
+
}
+
}