You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by ga...@apache.org on 2011/01/19 12:16:40 UTC
svn commit: r1060766 - in /shindig/trunk/java/gadgets/src:
main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java
test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java
Author: gagan
Date: Wed Jan 19 11:16:40 2011
New Revision: 1060766
URL: http://svn.apache.org/viewvc?rev=1060766&view=rev
Log:
Patch by nikhilmadan23 | Issue 3780045: Add a Resource Mutate Visitor to enforce that a resource is in cache | http://codereview.appspot.com/3780045/
Added:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java (with props)
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java (with props)
Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java?rev=1060766&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java Wed Jan 19 11:16:40 2011
@@ -0,0 +1,149 @@
+/*
+ * 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.gadgets.rewrite;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.shindig.common.Pair;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpCache;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.http.RequestPipeline;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Visitor that walks over html tags as specified by {@code resourceTags} and
+ * reserves html tag nodes whose uri attributes are either not in cache, or are
+ * in cache, but the response in cache is either stale or an error response. In
+ * all the above mentioned cases, we trigger a background fetch for the
+ * resource. This visitor should be used by a rewriter in conjuction with other
+ * visitors which depend on the uri of the html node being in cache.
+ *
+ */
+public class CacheEnforcementVisitor extends ResourceMutateVisitor {
+
+ private static final Logger logger = Logger.getLogger(
+ CacheEnforcementVisitor.class.getName());
+ private final HttpCache cache;
+ private final RequestPipeline requestPipeline;
+ private final Executor executor;
+
+
+ /**
+ * Constructs the cache enforcement visitor.
+ */
+ public CacheEnforcementVisitor(ContentRewriterFeature.Config featureConfig,
+ Executor executor,
+ HttpCache cache,
+ RequestPipeline requestPipeline,
+ Tags... resourceTags) {
+ super(featureConfig, resourceTags);
+ this.executor = executor;
+ this.cache = cache;
+ this.requestPipeline = requestPipeline;
+ }
+
+ @Override
+ public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
+ if (super.visit(gadget, node).equals(VisitStatus.RESERVE_NODE)) {
+ Element element = (Element) node;
+ String nodeName = node.getNodeName().toLowerCase();
+ String uriStr = element.getAttribute(resourceTags.get(nodeName)).trim();
+ // TODO: Construct other attributes of the HttpRequest using the passed in
+ // gadget.
+ HttpResponse response = cache.getResponse(new HttpRequest(Uri.parse(uriStr)));
+ if (response == null) {
+ return handleResponseNotInCache(uriStr);
+ } else {
+ return handleResponseInCache(uriStr, response);
+ }
+ }
+ return VisitStatus.BYPASS;
+ }
+
+ /**
+ * The action to be performed if the response is in cache.
+ *
+ * @param uri The uri of the node.
+ * @param response The HttpResponse retrieved from cache.
+ * @return The visit status of the node.
+ */
+ protected VisitStatus handleResponseInCache(String uri, HttpResponse response) {
+ // TODO: If the response is strict no cache, then we should reserve the node.
+ // Currently, we will keep triggering fetches for these resources.
+ // Also, investigate the Cache-Control=no-cache case. Should we proxy resources in this case?
+ // Also, investigate when Cache-Control is max-age=0. We are currently triggering a fetch in
+ // this case. We should look at the TTL of the original response and if that is zero, we
+ // shouldn't trigger a fetch for the resource.
+ if (response.isStale() || response.isError()) {
+ // Trigger a fetch if the response is stale or an error, and reserve the node.
+ triggerFetch(uri);
+ return VisitStatus.RESERVE_NODE;
+ } else {
+ // Otherwise, we assume the cached response is valid and bypass the node.
+ return VisitStatus.BYPASS;
+ }
+ }
+
+ /**
+ * The action to be performed if the response is not in cache.
+ *
+ * @param uri The uri of the node.
+ * @return The visit status of the node.
+ */
+ protected VisitStatus handleResponseNotInCache(String uri) {
+ triggerFetch(uri);
+ return VisitStatus.RESERVE_NODE;
+ }
+
+ /**
+ * Triggers a background fetch for a resource.
+ *
+ * @param uri The uri
+ */
+ protected void triggerFetch(final String uri) {
+
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ requestPipeline.execute(new HttpRequest(Uri.parse(uri)));
+ } catch (GadgetException e) {
+ logger.log(Level.WARNING, "Triggered fetch failed for " + uri, e);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected Collection<Pair<Node, Uri>> mutateUris(Gadget gadget, Collection<Node> nodes) {
+ return ImmutableList.of();
+ }
+}
Propchange: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitor.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java?rev=1060766&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java Wed Jan 19 11:16:40 2011
@@ -0,0 +1,161 @@
+/*
+ * 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.gadgets.rewrite;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.cache.LruCacheProvider;
+import org.apache.shindig.common.cache.CacheProvider;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponseBuilder;
+import org.apache.shindig.gadgets.http.RequestPipeline;
+import org.apache.shindig.gadgets.http.HttpCache;
+import org.apache.shindig.gadgets.http.DefaultHttpCache;
+import org.apache.shindig.gadgets.parse.ParseModule.DOMImplementationProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for CacheEnforcementVisitor.
+ */
+public class CacheEnforcementVisitorTest extends DomWalkerTestBase {
+ private ExecutorService executor;
+ private HttpCache cache;
+ protected Document doc;
+ private static final Map<String, String> ALL_RESOURCES =
+ CacheEnforcementVisitor.Tags.ALL_RESOURCES.getResourceTags();
+ private static final String IMG_URL = "http://www.example.org/1.gif";
+
+ @Before
+ public void setUp() {
+ executor = Executors.newSingleThreadExecutor();
+ DOMImplementationProvider domImpl = new DOMImplementationProvider();
+ doc = domImpl.get().createDocument(null, null, null);
+ CacheProvider cacheProvider = new LruCacheProvider(10);
+ cache = new DefaultHttpCache(cacheProvider);
+ }
+
+ @Test
+ public void testStaleImgWithNegativeTtlReservedAndFetchTriggered() throws Exception {
+ cache.addResponse(new HttpRequest(Uri.parse(IMG_URL)),
+ new HttpResponseBuilder().setResponseString("test")
+ .setCacheTtl(-1).create());
+ checkVisitBypassedAndFetchTriggered("img", IMG_URL, false, true);
+ }
+
+ @Test
+ public void testStaleImgWithZeroMaxAgeReservedAndFetchTriggered() throws Exception {
+ cache.addResponse(new HttpRequest(Uri.parse(IMG_URL)),
+ new HttpResponseBuilder().setResponseString("test")
+ .addHeader("Cache-Control", "max-age=0").create());
+ checkVisitBypassedAndFetchTriggered("img", IMG_URL, false, true);
+ }
+
+ @Test
+ public void testImgWithErrorResponseReservedAndFetchTriggered() throws Exception {
+ cache.addResponse(new HttpRequest(Uri.parse(IMG_URL)),
+ new HttpResponseBuilder().setResponseString("test")
+ .setHttpStatusCode(404).create());
+ checkVisitBypassedAndFetchTriggered("img", IMG_URL, false, true);
+ }
+
+ @Test
+ public void testImgBypassedAndFetchNotTriggered() throws Exception {
+ cache.addResponse(new HttpRequest(Uri.parse(IMG_URL)),
+ new HttpResponseBuilder().setResponseString("test").create());
+ checkVisitBypassedAndFetchTriggered("img", IMG_URL, true, false);
+ }
+
+ @Test
+ public void testEmbedImgBypassedAndFetchNotTriggered() throws Exception {
+ checkVisitBypassedAndFetchTriggered("embed", IMG_URL, true, false);
+ cache.addResponse(new HttpRequest(Uri.parse(IMG_URL)),
+ new HttpResponseBuilder().setResponseString("test").create());
+ checkVisitBypassedAndFetchTriggered("embed", IMG_URL, true, false);
+ }
+
+ @Test
+ public void testImgNotInCacheReservedAndFetchTriggered() throws Exception {
+ checkVisitBypassedAndFetchTriggered("img", IMG_URL, false, true);
+ }
+
+ /**
+ * Checks whether a node with the specified tag and url is bypassed by the
+ * CacheAwareResourceMutateVisitor, and also whether a fetch is triggered for
+ * the resource.
+ *
+ * @param tag The name of the tag for the node.
+ * @param url The url of the node.
+ * @param expectBypass Boolean to check if the node will be bypassed by the
+ * visitor.
+ * @param expectFetch Boolean to check if a fetch will be triggered for the
+ * resource.
+ * @throws Exception
+ */
+ private void checkVisitBypassedAndFetchTriggered(String tag, String url, boolean expectBypass,
+ boolean expectFetch) throws Exception {
+ // Try to get the attribute name for the specified tag, or otherwise use src.
+ String attrName = ALL_RESOURCES.get(tag.toLowerCase());
+ attrName = attrName != null ? attrName : "src";
+
+ // Create a node with the specified tag name and attribute.
+ Element node = doc.createElement(tag);
+ Attr attr = doc.createAttribute(attrName);
+ attr.setValue(url);
+ node.setAttributeNode(attr);
+
+ // Mock the RequestPipeline.
+ RequestPipeline requestPipeline = createMock(RequestPipeline.class);
+ if (expectFetch) {
+ expect(requestPipeline.execute(new HttpRequest(Uri.parse(url))))
+ .andReturn(new HttpResponseBuilder().setResponseString("test").create());
+ }
+ replay(requestPipeline);
+
+ ContentRewriterFeature.Config config = createMock(ContentRewriterFeature.Config.class);
+ expect(config.shouldRewriteURL(IMG_URL)).andReturn(true).anyTimes();
+ expect(config.shouldRewriteTag("img")).andReturn(true).anyTimes();
+ replay(config);
+ CacheEnforcementVisitor visitor = new CacheEnforcementVisitor(
+ config, executor, cache, requestPipeline,
+ ProxyingVisitor.Tags.SCRIPT, ProxyingVisitor.Tags.STYLESHEET,
+ ProxyingVisitor.Tags.EMBEDDED_IMAGES);
+
+ DomWalker.Visitor.VisitStatus status = visitor.visit(null, node);
+
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ verify(requestPipeline);
+ verify(config);
+
+ assertEquals(expectBypass, status == DomWalker.Visitor.VisitStatus.BYPASS);
+ }
+}
Propchange: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CacheEnforcementVisitorTest.java
------------------------------------------------------------------------------
svn:eol-style = native