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