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/05 09:23:50 UTC

svn commit: r692361 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/rewrite/ test/java/org/apache/shindig/gadgets/rewrite/

Author: johnh
Date: Fri Sep  5 00:23:49 2008
New Revision: 692361

URL: http://svn.apache.org/viewvc?rev=692361&view=rev
Log:
Step #1 in migrating ParseTreeDefaultContentRewriter to its constituent parts: three separate rewriters, all of which operate on a parse tree:
1. JsTagConcatCR: consolidates adjacent <script src> tags.
2. StyleLinkCR: rewrites image links in <style> blocks using a provided LinkRewriter.
3. LinkingTagCR: rewrites content links in various tags using a provided LinkRewriter.

With this change, each of these becomes independently configurable, and in fact usable multiple times in a given rewriting chain if desired. Each is constructed using either ContentRewriterFeature.Factory, LinkRewriter, or both. The former generates a ContentRewriterFeature for a given GadgetSpec using provided defaults. The Factory itself would be generated once, when configuring the ContentRewriter instances that apply to a given Shindig installation in its ContentRewriterRegistry.

All relevant code copied from what was previously in separate methods in ParseTreeHtmlRewriter, with bits of semantics (notably regarding when the rewriters actually apply) from [ParseTree]DefaultContentRewriter. Note that each Rewriter independently makes its own decision as to whether or not it applies for a given request.

As with ParseTreeDefaultContentRewriter, these changes presently do *not* affect the default installation of Shindig, as they are not configured as default Rewriters yet (soon).

These Rewriters are not yet complete due to missing implementations for the HttpResponse ContentRewriter interface, pending implementation of an approach for sharing a ParseTree to avoid multiple re-parsing (ContentRewriter interface change or HttpResponse[Builder] helpers). Lastly, there are some commonalities between these Rewriters which should soon be factored into helper base classes.


Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/FeatureBasedRewriterTestBase.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriterTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriterTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriterTest.java
Removed:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ParseTreeDefaultContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ParseTreeHtmlRewriter.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ParseTreeHtmlRewriterTest.java
Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java?rev=692361&r1=692360&r2=692361&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java Fri Sep  5 00:23:49 2008
@@ -190,4 +190,30 @@
     }
     return fingerprint;
   }
+  
+  public static class Factory {
+    private final String defaultIncludeUrls;
+    private final String defaultExcludeUrls;
+    private final String defaultExpires;
+    private final Set<String> defaultIncludeTags;
+    
+    public Factory(String includeUrls, String excludeUrls, String expires,
+        Set<String> includeTags) {
+      defaultIncludeUrls = includeUrls;
+      defaultExcludeUrls = excludeUrls;
+      defaultExpires = expires;
+      defaultIncludeTags = includeTags;
+    }
+    
+    public ContentRewriterFeature get(GadgetSpec spec) {
+      ContentRewriterFeature rewriterFeature =
+        (ContentRewriterFeature)spec.getAttribute("content-rewrite");
+      if (rewriterFeature == null) {
+        rewriterFeature = new ContentRewriterFeature(spec, defaultIncludeUrls,
+            defaultExcludeUrls, defaultExpires, defaultIncludeTags);
+        spec.setAttribute("content-rewrite", rewriterFeature);
+      }
+      return rewriterFeature;
+    }
+  }
 }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriter.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriter.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,187 @@
+/*
+ * 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 java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import org.apache.shindig.common.util.Utf8UrlCoder;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+
+public class JsTagConcatContentRewriter implements ContentRewriter {
+  private final static int MAX_URL_LENGTH = 1500;
+  
+  private final ContentRewriterFeature.Factory rewriterFeatureFactory;
+  private final String concatUrlBase;
+  
+  private static final String DEFAULT_CONCAT_URL_BASE = "/gadgets/concat?";
+  
+  public JsTagConcatContentRewriter(ContentRewriterFeature.Factory rewriterFeatureFactory,
+      String concatUrlBase) {
+    this.rewriterFeatureFactory = rewriterFeatureFactory;
+    if (concatUrlBase != null) {
+      this.concatUrlBase = concatUrlBase;
+    } else {
+      this.concatUrlBase = DEFAULT_CONCAT_URL_BASE;
+    }
+  }
+
+  public HttpResponse rewrite(HttpRequest request, HttpResponse original) {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  public void rewrite(Gadget gadget) {
+    ContentRewriterFeature rewriterFeature = rewriterFeatureFactory.get(gadget.getSpec());
+    if (!rewriterFeature.isRewriteEnabled() ||
+        !rewriterFeature.getIncludedTags().contains("script")) {
+      return;
+    }
+    
+    // Bootstrap queue of children over which to iterate,
+    // ie. lists of siblings to potentially combine
+    Queue<GadgetHtmlNode> nodesToProcess =
+        new LinkedList<GadgetHtmlNode>();
+    nodesToProcess.add(gadget.getParseTree());
+
+    
+    String concatBase = getJsConcatBase(gadget.getSpec(), rewriterFeature);
+    
+    while (!nodesToProcess.isEmpty()) {
+      GadgetHtmlNode parentNode = nodesToProcess.remove();
+      if (!parentNode.isText()) {
+        List<GadgetHtmlNode> childList = parentNode.getChildren();
+        
+        // Iterate over children next in depth-first fashion.
+        // Text nodes (such as <script src> processed here) will be ignored.
+        nodesToProcess.addAll(childList);
+        
+        List<GadgetHtmlNode> toRemove = new ArrayList<GadgetHtmlNode>();
+        List<URI> scripts = new ArrayList<URI>();
+        boolean processScripts = false;
+        for (int i = 0; i < childList.size(); ++i) {
+          GadgetHtmlNode cur = childList.get(i);
+        
+          // Find consecutive <script src=...> tags
+          if (!cur.isText() &&
+               cur.getTagName().equalsIgnoreCase("script") &&
+               cur.hasAttribute("src")) {
+            URI scriptUri = null;
+            try {
+              scriptUri =
+                  gadget.getSpec().getUrl().resolve(new URI(cur.getAttributeValue("src")));
+            } catch (URISyntaxException use) {
+              // Same behavior as JavascriptTagMerger
+              // Perhaps switch to ignoring script src instead?
+              throw new RuntimeException(use);
+            }
+            scripts.add(scriptUri);
+            toRemove.add(cur);
+          } else if (scripts.size() > 0 && cur.isText() && cur.getText().matches("\\s*")) {
+            // Whitespace after one or more scripts. Ignore and remove.
+            toRemove.add(cur);
+          } else if (scripts.size() > 0) {
+            processScripts = true;
+          }
+          
+          if (i == (childList.size() - 1)) {
+            processScripts = true;
+          }
+        
+          if (processScripts && scripts.size() > 0) {
+            // Tags found. Concatenate scripts together.
+            List<URI> concatUris = getConcatenatedUris(concatBase, scripts);
+            
+            // Insert concatenated nodes before first match
+            for (URI concatUri : concatUris) {
+              GadgetHtmlNode newScript = new GadgetHtmlNode("script", null);
+              newScript.setAttribute("src", concatUri.toString());
+              parentNode.insertBefore(newScript, toRemove.get(0));
+            }
+            
+            // Remove contributing match nodes
+            for (GadgetHtmlNode remove : toRemove) {
+              parentNode.removeChild(remove);
+            }
+            
+            processScripts = false;
+            scripts.clear();
+            toRemove.clear();
+          }
+        }
+      }
+    }
+  }
+  
+  private List<URI> getConcatenatedUris(String concatBase, List<URI> uris) {
+    List<URI> concatUris = new LinkedList<URI>();
+    int paramIndex = 1;
+    StringBuilder builder = null;
+    int maxUriLen = MAX_URL_LENGTH + concatBase.length();
+    try {
+      int uriIx = 0, lastUriIx = (uris.size() - 1);
+      for (URI uri : uris) {
+        if (paramIndex == 1) {
+          builder = new StringBuilder(concatBase);
+        } else {
+          builder.append("&");
+        }
+        builder.append(paramIndex).append("=")
+            .append(URLEncoder.encode(uri.toString(), "UTF-8"));
+        if (builder.length() > maxUriLen ||
+            uriIx == lastUriIx) {
+          // Went over URI length warning limit or on the last uri
+          concatUris.add(new URI(builder.toString()));
+          builder = null;
+          paramIndex = 0;
+        }
+        ++paramIndex;
+        ++uriIx;
+      }
+    } catch (UnsupportedEncodingException e) {
+      throw new RuntimeException(e);
+    } catch (URISyntaxException e) {
+      throw new RuntimeException(e);
+    }
+    return concatUris;
+  }
+  
+  String getJsConcatBase(GadgetSpec spec, ContentRewriterFeature rewriterFeature) {
+    return concatUrlBase +
+           ProxyBase.REWRITE_MIME_TYPE_PARAM +
+           "=text/javascript&" +
+           "gadget=" +
+           Utf8UrlCoder.encode(spec.getUrl().toString()) +
+           "&fp=" +
+           rewriterFeature.getFingerprint() +
+           '&';
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriter.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriter.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,99 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
+
+public class LinkingTagContentRewriter implements ContentRewriter {
+  private final LinkRewriter linkRewriter;
+  private final Map<String, Set<String>> tagAttributeTargets;
+  
+  public LinkingTagContentRewriter(LinkRewriter linkRewriter,
+      Map<String, Set<String>> attributeTargets) {
+    this.linkRewriter = linkRewriter;
+    if (attributeTargets != null) {
+      this.tagAttributeTargets = attributeTargets;
+    } else {
+      this.tagAttributeTargets = getDefaultTargets();
+    }
+  }
+
+  public HttpResponse rewrite(HttpRequest request, HttpResponse original) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public void rewrite(Gadget gadget) {
+	  if (linkRewriter == null) {
+	    // Sanity test.
+	    return;
+	  }
+	  
+    Queue<GadgetHtmlNode> nodesToProcess =
+      new LinkedList<GadgetHtmlNode>();
+    GadgetHtmlNode root = gadget.getParseTree();
+    if (root == null) {
+      return;
+    }
+    
+    nodesToProcess.addAll(root.getChildren());
+  
+    while (!nodesToProcess.isEmpty()) {
+      GadgetHtmlNode curNode = nodesToProcess.remove();
+      if (!curNode.isText()) {
+        // Depth-first iteration over children. Order doesn't matter anyway.
+        nodesToProcess.addAll(curNode.getChildren());
+        
+        Set<String> curTagAttrs =
+            tagAttributeTargets.get(curNode.getTagName().toLowerCase());
+        if (curTagAttrs != null) {
+          for (String attrKey : curNode.getAttributeKeys()) {
+            if (curTagAttrs.contains(attrKey.toLowerCase())) {
+              String attrValue = curNode.getAttributeValue(attrKey);
+            
+              // Attribute marked for rewriting: do it!
+              curNode.setAttribute(attrKey,
+                  linkRewriter.rewrite(attrValue, gadget.getSpec().getUrl()));
+            }
+          }
+        }
+      }
+    }
+	}
+
+  private static Map<String, Set<String>> getDefaultTargets() {
+    Map<String, Set<String>> targets  = new HashMap<String, Set<String>>();
+    targets.put("img", new HashSet<String>(Arrays.asList("src")));
+    targets.put("embed", new HashSet<String>(Arrays.asList("src")));
+    targets.put("link", new HashSet<String>(Arrays.asList("href")));
+    return targets;
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriter.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriter.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.LinkedList;
+import java.util.Queue;
+
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.parse.GadgetHtmlNode;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+
+public class StyleLinksContentRewriter implements ContentRewriter {
+  // TODO: consider providing helper base class for node-visitor content rewriters
+  private final ContentRewriterFeature.Factory rewriterFeatureFactory;
+  private final LinkRewriter linkRewriter;
+  
+  public StyleLinksContentRewriter(ContentRewriterFeature.Factory rewriterFeatureFactory,
+      LinkRewriter linkRewriter) {
+    this.rewriterFeatureFactory = rewriterFeatureFactory;
+    this.linkRewriter = linkRewriter;
+  }
+
+  public HttpResponse rewrite(HttpRequest request, HttpResponse original) {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  public void rewrite(Gadget gadget) {
+    ContentRewriterFeature rewriterFeature = rewriterFeatureFactory.get(gadget.getSpec());
+    if (linkRewriter == null || 
+        !rewriterFeature.isRewriteEnabled() ||
+        !rewriterFeature.getIncludedTags().contains("style")) {
+      return;
+    }
+    
+    Queue<GadgetHtmlNode> nodesToProcess =
+      new LinkedList<GadgetHtmlNode>();
+    GadgetHtmlNode root = gadget.getParseTree();
+    if (root == null) {
+      return;
+    }
+    
+    nodesToProcess.addAll(root.getChildren());
+  
+    while (!nodesToProcess.isEmpty()) {
+      GadgetHtmlNode curNode = nodesToProcess.remove();
+      if (!curNode.isText()) {
+        // Depth-first iteration over children. Order doesn't matter anyway.
+        nodesToProcess.addAll(curNode.getChildren());
+        
+        if (curNode.getTagName().equalsIgnoreCase("style")) {
+          String styleText = getNodeChildText(curNode);
+          curNode.clearChildren();
+          curNode.appendChild(new GadgetHtmlNode(
+              CssRewriter.rewrite(styleText, gadget.getSpec().getUrl(), linkRewriter)));
+        }
+      }
+    }
+  }
+  
+  private static String getNodeChildText(GadgetHtmlNode node) {
+    // TODO: move this to GadgetHtmlNode as a helper
+    StringBuilder builder = new StringBuilder();
+    for (GadgetHtmlNode child : node.getChildren()) {
+      if (child.isText()) {
+        builder.append(child.getText());
+      }
+    }
+    return builder.toString();
+  }
+  
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/FeatureBasedRewriterTestBase.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/FeatureBasedRewriterTestBase.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/FeatureBasedRewriterTestBase.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/FeatureBasedRewriterTestBase.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,97 @@
+/*
+ * 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 org.easymock.classextension.EasyMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.replay;
+
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.View;
+
+import junit.framework.TestCase;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class FeatureBasedRewriterTestBase extends TestCase {
+  protected URI baseUri;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    baseUri = new URI("http://gadget.org/dir/gadget.xml");
+  }
+  
+  protected ContentRewriterFeature.Factory mockContentRewriterFeatureFactory(
+      ContentRewriterFeature feature) {
+    return new MockRewriterFeatureFactory(feature);
+  }
+  
+  protected ContentRewriterFeature makeFeature(String... includedTags) {
+    ContentRewriterFeature rewriterFeature =
+        EasyMock.createNiceMock(ContentRewriterFeature.class);
+    Set<String> tags = new HashSet<String>();
+    for (String tag : includedTags) {
+      tags.add(tag);
+    }
+    expect(rewriterFeature.isRewriteEnabled()).andReturn(true).anyTimes();
+    expect(rewriterFeature.getIncludedTags()).andReturn(tags).anyTimes();
+    expect(rewriterFeature.getFingerprint()).andReturn(-840722081).anyTimes();
+    replay(rewriterFeature);
+    return rewriterFeature;
+  }
+  
+  protected String rewriteHelper(ContentRewriter rewriter, String s,
+      ParsedHtmlNode[] p) throws Exception {
+    GadgetHtmlParser parser = EasyMock.createNiceMock(GadgetHtmlParser.class);
+    List<ParsedHtmlNode> expected = p != null ? Arrays.asList(p) : null;
+    expect(parser.parse(s)).andReturn(expected).anyTimes();
+    View view = EasyMock.createNiceMock(View.class);
+    expect(view.getContent()).andReturn(s).anyTimes();
+    expect(view.getName()).andReturn(GadgetSpec.DEFAULT_VIEW).anyTimes();
+    GadgetSpec spec = EasyMock.createNiceMock(GadgetSpec.class);
+    expect(spec.getUrl()).andReturn(baseUri).anyTimes();
+    expect(spec.getView(GadgetSpec.DEFAULT_VIEW)).andReturn(view).anyTimes();
+    replay(parser, view, spec);
+    Gadget gadget = new Gadget(new GadgetContext(), spec, null, null, parser);
+    rewriter.rewrite(gadget);
+    return gadget.getContent();
+  }
+  
+  private static class MockRewriterFeatureFactory extends ContentRewriterFeature.Factory {
+    private final ContentRewriterFeature feature;
+    
+    public MockRewriterFeatureFactory(ContentRewriterFeature feature) {
+      super(".*", "", "HTTP", null);
+      this.feature = feature;
+    }
+    
+    @Override
+    public ContentRewriterFeature get(GadgetSpec spec) {
+      return feature;
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriterTest.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JsTagConcatContentRewriterTest.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,192 @@
+/*
+ * 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 org.easymock.classextension.EasyMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.replay;
+
+import org.apache.shindig.gadgets.parse.GadgetHtmlNodeTest;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+
+public class JsTagConcatContentRewriterTest extends FeatureBasedRewriterTestBase {
+  private ContentRewriterFeature jsFeature;
+  private JsTagConcatContentRewriter rewriter;
+  private String concatBase;
+  
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    jsFeature = makeFeature("script");
+    ContentRewriterFeature.Factory factory = mockContentRewriterFeatureFactory(jsFeature);
+    rewriter = new JsTagConcatContentRewriter(factory, null);
+    GadgetSpec spec = EasyMock.createNiceMock(GadgetSpec.class);
+    expect(spec.getUrl()).andReturn(baseUri).anyTimes();
+    replay(spec);
+    concatBase = rewriter.getJsConcatBase(spec, jsFeature);
+  }
+
+  public void testJSMergePreserveNoExternal() throws Exception {
+    String s = "<script>\n"
+        + "doSomething\n"
+        + "</script>";
+    ParsedHtmlNode[] scriptKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode("\ndoSomething\n")
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", null, scriptKids)
+    };
+    assertEquals(s, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergePreserveNoScript() throws Exception {
+    String s
+        = "<html><div id=\"test\">ceci ne pas une script</div></html>";
+    String[][] attribs = { { "id", "test" } };
+    ParsedHtmlNode[] divKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode("ceci ne pas une script")
+    };
+    ParsedHtmlNode[] htmlKids = {
+      GadgetHtmlNodeTest.makeParsedTagNode("div", attribs, divKids)
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("html", null, htmlKids)
+    };
+    assertEquals(s, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergePreserveWithComment() throws Exception {
+    String s = "<script>" +
+        "<!--\ndoSomething\n-->" +
+        "</script>";
+    ParsedHtmlNode[] scriptKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode("<!--\ndoSomething\n-->")
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", null, scriptKids)
+    };
+    assertEquals(s, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeSingleScriptReWrite() throws Exception {
+    String s = "<script src=\"http://a.b.com/1.js\"></script>";
+    String[][] attribs = { { "src", "http://a.b.com/1.js" } };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attribs, null)
+    };
+    String rewritten
+        = "<script src=\"" + concatBase + "1=http%3A%2F%2Fa.b.com%2F1.js\"></script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeTwoScriptReWriteWithWhitespace() throws Exception {
+    String s = "<script src=\"http://a.b.com/1.js\"></script>\n"
+        + "<script src=\"http://a.b.com/2.js\"></script>";
+    String[][] attr1 = { { "src", "http://a.b.com/1.js" } };
+    String[][] attr2 = { { "src", "http://a.b.com/2.js" } };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr1, null),
+      GadgetHtmlNodeTest.makeParsedTextNode("\n"),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr2, null)
+    };
+    String rewritten
+        = "<script src=\"" + concatBase + "1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\"></script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeLeadAndTrailingScriptReWrite() throws Exception {
+    String s = "<script>\n"
+        + "doSomething\n"
+        + "</script>"
+        + "<script src=\"http://a.b.com/1.js\"></script>"
+        + "<script src=\"http://a.b.com/2.js\"></script>"
+        + "<script>"
+        + "doSomething\n"
+        + "</script>";
+    String[][] attr1 = { { "src", "http://a.b.com/1.js" } };
+    String[][] attr2 = { { "src", "http://a.b.com/2.js" } };
+    ParsedHtmlNode[] scriptKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode("\ndoSomething\n")
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", null, scriptKids),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr1, null),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr2, null),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", null, scriptKids)
+    };
+    String rewritten = "<script>\n"
+        + "doSomething\n"
+        + "</script>"
+        + "<script src=\"" + concatBase + "1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\"></script>"
+        + "<script>\n"
+        + "doSomething\n"
+        + "</script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeInterspersed() throws Exception {
+    String s = "<script src=\"http://a.b.com/1.js\"></script>"
+        + "<script src=\"http://a.b.com/2.js\"></script>"
+        + "<script><!-- doSomething --></script>"
+        + "<script src=\"http://a.b.com/3.js\"></script>"
+        + "<script src=\"http://a.b.com/4.js\"></script>";
+    String[][] attr1 = { { "src", "http://a.b.com/1.js" } };
+    String[][] attr2 = { { "src", "http://a.b.com/2.js" } };
+    String[][] attr3 = { { "src", "http://a.b.com/3.js" } };
+    String[][] attr4 = { { "src", "http://a.b.com/4.js" } };
+    ParsedHtmlNode[] scriptKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode("<!-- doSomething -->")
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr1, null),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr2, null),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", null, scriptKids),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr3, null),
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr4, null)
+    };
+    String rewritten =
+        "<script src=\"" + concatBase + "1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\"></script>" +
+        "<script><!-- doSomething --></script>" +
+        "<script src=\"" + concatBase + "1=http%3A%2F%2Fa.b.com%2F3.js&2=http%3A%2F%2Fa.b.com%2F4.js\"></script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeDerelativizeHostRelative() throws Exception {
+    String s = "<script src=\"/1.js\"></script>";
+    String[][] attr1 = { { "src", "/1.js" } };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr1, null)  
+    };
+    String rewritten
+        = "<script src=\"" + concatBase + "1=http%3A%2F%2Fgadget.org%2F1.js\"></script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+
+  public void testJSMergeDerelativizePathRelative() throws Exception {
+    String s = "<script src=\"1.js\"></script>";
+    String[][] attr1 = { { "src", "1.js" } };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("script", attr1, null)  
+    };
+    String rewritten
+        = "<script src=\"" + concatBase + "1=http%3A%2F%2Fgadget.org%2Fdir%2F1.js\"></script>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriterTest.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagContentRewriterTest.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,82 @@
+/*
+ * 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 java.net.URI;
+
+import org.apache.shindig.gadgets.parse.GadgetHtmlNodeTest;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+
+public class LinkingTagContentRewriterTest extends FeatureBasedRewriterTestBase {
+  private LinkRewriter pfxLinkRewriter;
+  private LinkingTagContentRewriter rewriter;
+  
+  private static final String LINK_PREFIX = "px-";
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    pfxLinkRewriter = new LinkRewriter() {
+      public String rewrite(String uri, URI context) {
+        // Just prefixes with LINK_PREFIX
+        return LINK_PREFIX + uri;
+      }
+    };
+    rewriter = new LinkingTagContentRewriter(pfxLinkRewriter, null);
+  }
+  
+  public void testLinkingTagStandardRewrite() throws Exception {
+    String s = "<img src=\"http://a.b.com/img.gif\"></img>\n"
+        + "<IMG src=\"http://a.b.com/img2.gif\"/>\n"
+        + "<eMbeD src=\"http://a.b.com/some.mov\"/>\n"
+        + "<link href=\"http://a.b.com/link.html\"></link>";
+    String[][] img1attrib = { { "src", "http://a.b.com/img.gif" } };
+    String[][] img2attrib = { { "src", "http://a.b.com/img2.gif" } };
+    String[][] emb1attrib = { { "src", "http://a.b.com/some.mov" } };
+    String[][] href1attr = { { "href", "http://a.b.com/link.html" } };
+    ParsedHtmlNode[] p = {
+        GadgetHtmlNodeTest.makeParsedTagNode("img", img1attrib, null),
+        GadgetHtmlNodeTest.makeParsedTextNode("\n"),
+        GadgetHtmlNodeTest.makeParsedTagNode("IMG", img2attrib, null),
+        GadgetHtmlNodeTest.makeParsedTextNode("\n"),
+        GadgetHtmlNodeTest.makeParsedTagNode("eMbeD", emb1attrib, null),
+        GadgetHtmlNodeTest.makeParsedTextNode("\n"),
+        GadgetHtmlNodeTest.makeParsedTagNode("link", href1attr, null)
+    };
+    String rewritten = "<img src=\"" + LINK_PREFIX + "http://a.b.com/img.gif\"/>\n"
+        + "<IMG src=\"" + LINK_PREFIX + "http://a.b.com/img2.gif\"/>\n"
+        + "<eMbeD src=\"" + LINK_PREFIX + "http://a.b.com/some.mov\"/>\n"
+        + "<link href=\"" + LINK_PREFIX + "http://a.b.com/link.html\"/>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+  
+  public void testLinkingTagIgnoredWithNoRewriter() throws Exception {
+    String s = "<img src=\"http://a.b.com/img.gif\"></img>";
+    String[][] img1attrib = { { "src", "http://a.b.com/img.gif" } };
+    ParsedHtmlNode[] p = {
+        GadgetHtmlNodeTest.makeParsedTagNode("img", img1attrib, null),
+    };
+    assertEquals(s, rewriteHelper(new LinkingTagContentRewriter(null, null), s, p));
+  }
+  
+  public void testLinkingTagIgnoredWithBadParse() throws Exception {
+    String s = "<img src=\"http://a.b.com/img.gif></img>";
+    assertEquals(s, rewriteHelper(rewriter, s, null));  // null = couldn't parse
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriterTest.java?rev=692361&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/StyleLinksContentRewriterTest.java Fri Sep  5 00:23:49 2008
@@ -0,0 +1,104 @@
+/*
+ * 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 java.net.URI;
+
+import org.apache.shindig.gadgets.parse.GadgetHtmlNodeTest;
+import org.apache.shindig.gadgets.parse.ParsedHtmlNode;
+
+public class StyleLinksContentRewriterTest extends FeatureBasedRewriterTestBase {
+  private LinkRewriter pfxLinkRewriter;
+  private ContentRewriterFeature styleFeature;
+  private StyleLinksContentRewriter rewriter;
+  
+  private static final String LINK_PREFIX = "px-";
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    pfxLinkRewriter = new LinkRewriter() {
+      public String rewrite(String uri, URI context) {
+        // Just prefixes with LINK_PREFIX
+        return LINK_PREFIX + uri;
+      }
+    };
+    styleFeature = makeFeature("style");
+    ContentRewriterFeature.Factory factory = mockContentRewriterFeatureFactory(styleFeature);
+    rewriter = new StyleLinksContentRewriter(factory, pfxLinkRewriter);
+  }
+  
+  public void testStyleTagRewrites() throws Exception {
+    String css =
+      "div {list-style-image:url('http://a.b.com/bullet.gif');list-style-position:outside;margin:5px;padding:0}\n" +
+      ".someid {background-image:url(http://a.b.com/bigimg.png);float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    String s = "<style>" + css + "</style>";
+    ParsedHtmlNode[] styleKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode(css)
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("style", null, styleKids)
+    };
+    String rewritten =
+      "<style>div {list-style-image:url(\"" + LINK_PREFIX + "http://a.b.com/bullet.gif\");list-style-position:outside;margin:5px;padding:0}\n" +
+      ".someid {background-image:url(\"" + LINK_PREFIX + "http://a.b.com/bigimg.png\");float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}</style>";
+    assertEquals(rewritten, rewriteHelper(rewriter, s, p));
+  }
+  
+  public void testStyleTagRewritesIgnoredOnBadParse() throws Exception {
+    String css = 
+      "div {list-style-image:url('http://a.b.com/bullet.gif');list-style-position:outside;margin:5px;padding:0}\n" +
+      ".someid {background-image:url(http://a.b.com/bigimg.png);float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    String s = "<style>" + css + "</style";
+    assertEquals(s, rewriteHelper(rewriter, s, null));
+  }
+  
+  public void testStyleTagRewritesIgnoredOnNoFeatureKey() throws Exception {
+    ContentRewriterFeature overrideFeature = makeFeature("foo");  // doesn't include "style"
+    ContentRewriterFeature.Factory factory = mockContentRewriterFeatureFactory(overrideFeature);
+    StyleLinksContentRewriter overrideRewriter = new StyleLinksContentRewriter(factory, pfxLinkRewriter);
+    String css =
+      "div {list-style-image:url('http://a.b.com/bullet.gif');list-style-position:outside;margin:5px;padding:0}\n" +
+      ".someid {background-image:url(http://a.b.com/bigimg.png);float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    String s = "<style>" + css + "</style>";
+    ParsedHtmlNode[] styleKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode(css)
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("style", null, styleKids)
+    };
+    assertEquals(s, rewriteHelper(overrideRewriter, s, p));
+  }
+  
+  public void testStyleTagRewritesIgnoredOnNullLinkRewriter() throws Exception {
+    ContentRewriterFeature.Factory factory = mockContentRewriterFeatureFactory(styleFeature);
+    StyleLinksContentRewriter overrideRewriter = new StyleLinksContentRewriter(factory, null);
+    String css =
+      "div {list-style-image:url('http://a.b.com/bullet.gif');list-style-position:outside;margin:5px;padding:0}\n" +
+      ".someid {background-image:url(http://a.b.com/bigimg.png);float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    String s = "<style>" + css + "</style>";
+    ParsedHtmlNode[] styleKids = {
+      GadgetHtmlNodeTest.makeParsedTextNode(css)
+    };
+    ParsedHtmlNode[] p = {
+      GadgetHtmlNodeTest.makeParsedTagNode("style", null, styleKids)
+    };
+    assertEquals(s, rewriteHelper(overrideRewriter, s, p));
+  }
+}