You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by jo...@apache.org on 2010/03/09 02:03:50 UTC
svn commit: r920603 - in /shindig/trunk/java/gadgets/src:
main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
Author: johnh
Date: Tue Mar 9 01:03:50 2010
New Revision: 920603
URL: http://svn.apache.org/viewvc?rev=920603&view=rev
Log:
Implement Concatenation visitor, supporting batched version lookups and split-JavaScript inclusion.
Added:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java?rev=920603&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ConcatVisitor.java Tue Mar 9 01:03:50 2010
@@ -0,0 +1,202 @@
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.uri.ConcatUriManager;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.google.common.collect.Lists;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class ConcatVisitor implements DomWalker.Visitor {
+ public static class Js extends ConcatVisitor {
+ public Js(ContentRewriterFeature.Config config,
+ ConcatUriManager uriManager) {
+ super(config, uriManager, ConcatUriManager.Type.JS);
+ }
+ }
+
+ public static class Css extends ConcatVisitor {
+ public Css(ContentRewriterFeature.Config config,
+ ConcatUriManager uriManager) {
+ super(config, uriManager, ConcatUriManager.Type.CSS);
+ }
+ }
+
+ private final ConcatUriManager uriManager;
+ private final ConcatUriManager.Type type;
+ private final ContentRewriterFeature.Config config;
+ private final boolean split;
+
+ private ConcatVisitor(ContentRewriterFeature.Config config,
+ ConcatUriManager uriManager, ConcatUriManager.Type type) {
+ this.uriManager = uriManager;
+ this.type = type;
+ this.config = config;
+ this.split = (type == ConcatUriManager.Type.JS && config.isSplitJsEnabled());
+ }
+
+ public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
+ // Reserve JS nodes; always if there's an adjacent rewritable JS node and also when
+ // directed to support split-resource concatenation
+ if (node.getNodeType() != Node.ELEMENT_NODE ||
+ !node.getNodeName().equalsIgnoreCase(type.getTagName())) {
+ return VisitStatus.BYPASS;
+ }
+
+ Element element = (Element)node;
+ if (isRewritableExternData(element)) {
+ if (split ||
+ isRewritableExternData(getSibling(element, true)) ||
+ isRewritableExternData(getSibling(element, false))) {
+ return VisitStatus.RESERVE_NODE;
+ }
+ }
+
+ return VisitStatus.BYPASS;
+ }
+
+ public boolean revisit(Gadget gadget, List<Node> nodes) throws RewritingException {
+ // Collate Elements into batches to be concatenated.
+ List<List<Element>> concatBatches = Lists.newLinkedList();
+ List<Element> curBatch = Lists.newLinkedList();
+ Iterator<Node> nodeIter = nodes.iterator();
+ Element cur = (Element)nodeIter.next();
+ curBatch.add(cur);
+ while (nodeIter.hasNext()) {
+ Element next = (Element)nodeIter.next();
+ if (!split && cur != getSibling(next, true)) {
+ // Break off current batch and add to list of all.
+ concatBatches.add(curBatch);
+ curBatch = Lists.newLinkedList();
+ }
+ curBatch.add(next);
+ cur = next;
+ }
+
+ // Add leftovers.
+ concatBatches.add(curBatch);
+
+ // Prepare batches of Uris to send to generate concat Uris
+ List<List<Uri>> uriBatches = Lists.newLinkedList();
+ Iterator<List<Element>> batchesIter = concatBatches.iterator();
+ while (batchesIter.hasNext()) {
+ List<Element> batch = batchesIter.next();
+ List<Uri> uris = Lists.newLinkedList();
+ if (batch.isEmpty() || !getUris(type, batch, uris)) {
+ batchesIter.remove();
+ continue;
+ }
+ uriBatches.add(uris);
+ }
+
+ if (uriBatches.isEmpty()) {
+ return false;
+ }
+
+ // Generate the ConcatUris, then correlate with original elements.
+ List<ConcatUriManager.ConcatData> concatUris =
+ uriManager.make(
+ ConcatUriManager.ConcatUri.fromList(gadget, uriBatches, type), !split);
+
+ Iterator<List<Element>> elemBatchIt = concatBatches.iterator();
+ Iterator<List<Uri>> uriBatchIt = uriBatches.iterator();
+ for (ConcatUriManager.ConcatData concatUri : concatUris) {
+ List<Element> sourceBatch = elemBatchIt.next();
+ List<Uri> sourceUris = uriBatchIt.next();
+
+ // Regardless what happens, inject a copy of the first node,
+ // with new (concat) URI, immediately ahead of the first elem.
+ Element firstElem = sourceBatch.get(0);
+ Element elemConcat = (Element)firstElem.cloneNode(true);
+ elemConcat.setAttribute(type.getSrcAttrib(), concatUri.getUri().toString());
+ firstElem.getParentNode().insertBefore(elemConcat, firstElem);
+
+ // Now for all Elements, either A) remove them or B) replace each
+ // with a <script> node with snippet of code configuring/evaluating
+ // the resultant inserted code. This is useful for split-JS in particular,
+ // and might also be used in spriting later.
+ Iterator<Uri> uriIt = sourceUris.iterator();
+ for (Element elem : sourceBatch) {
+ Uri elemOrigUri = uriIt.next();
+ String snippet = concatUri.getSnippet(elemOrigUri);
+ if (!StringUtils.isEmpty(snippet)) {
+ Node scriptNode = elem.getOwnerDocument().createElement("script");
+ scriptNode.setTextContent(snippet);
+ elem.getParentNode().insertBefore(scriptNode, elem);
+ }
+ elem.getParentNode().removeChild(elem);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isRewritableExternData(Element elem) {
+ String uriStr = elem != null ? elem.getAttribute(type.getSrcAttrib()) : null;
+ if (StringUtils.isEmpty(uriStr) ||
+ !config.shouldRewriteURL(uriStr)) {
+ return false;
+ }
+ if (type == ConcatUriManager.Type.CSS) {
+ // rel="stylesheet" and type="css" also required.
+ return ("stylesheet".equalsIgnoreCase(elem.getAttribute("rel")) ||
+ elem.getAttribute("type").contains("css"));
+ }
+ return true;
+ }
+
+ private Element getSibling(Element root, boolean isPrev) {
+ Node cur = root;
+ while ((cur = getNext(cur, isPrev)) != null) {
+ if (cur.getNodeType() == Node.TEXT_NODE && StringUtils.isEmpty(cur.getTextContent())) {
+ continue;
+ }
+ break;
+ }
+ if (cur != null && cur.getNodeType() == Node.ELEMENT_NODE) {
+ return (Element)cur;
+ }
+ return null;
+ }
+
+ private Node getNext(Node node, boolean isPrev) {
+ return isPrev ? node.getPreviousSibling() : node.getNextSibling();
+ }
+
+ private boolean getUris(ConcatUriManager.Type type, List<Element> elems, List<Uri> uris) {
+ for (Element elem : elems) {
+ String uriStr = elem.getAttribute(type.getSrcAttrib());
+ try {
+ uris.add(Uri.parse(uriStr));
+ } catch (Uri.UriException e) {
+ // Invalid formatted Uri, batch failed.
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java?rev=920603&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/ConcatVisitorTest.java Tue Mar 9 01:03:50 2010
@@ -0,0 +1,519 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.gadgets.rewrite.DomWalker.Visitor.VisitStatus;
+import org.apache.shindig.gadgets.uri.ConcatUriManager;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Map;
+
+public class ConcatVisitorTest extends DomWalkerTestBase {
+ private static final String JS1_URL_STR = "http://one.com/foo.js";
+ private Node js1;
+
+ private static final String JS2_URL_STR = "http://two.com/foo.js";
+ private Node js2;
+
+ private static final String JS3_URL_STR = "http://three.com/foo.js";
+ private Node js3;
+
+ private static final String JS4_URL_STR = "http://four.com/foo.js";
+ private Node js4;
+
+ private static final String JS5_URL_STR = "http://~^|BAD |^/foo.js";
+ private Node js5;
+
+ private static final String JS6_URL_STR = "http://six.com/foo.js";
+ private Node js6;
+
+ private static final String CSS1_URL_STR = "http://one.com/foo.js";
+ private Node css1;
+
+ private static final String CSS2_URL_STR = "http://two.com/foo.js";
+ private Node css2;
+
+ private static final String CSS3_URL_STR = "http://three.com/foo.js";
+ private Node css3;
+
+ private static final String CSS4_URL_STR = "http://four.com/foo.js";
+ private Node css4;
+
+ private static final Uri CONCAT_BASE_URI = Uri.parse("http://test.com/proxy");
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ js1 = elem("script", "src", JS1_URL_STR);
+ js2 = elem("script", "src", JS2_URL_STR);
+ js3 = elem("script", "src", JS3_URL_STR);
+ js4 = elem("script", "src", JS4_URL_STR);
+ js5 = elem("script", "src", JS5_URL_STR);
+ js6 = elem("script", "src", JS6_URL_STR);
+ css1 = elem("link", "rel", "stylesheet", "type", "text/css", "href", CSS1_URL_STR);
+ css2 = elem("link", "rel", "stylesheet", "type", "text/css", "href", CSS2_URL_STR);
+ css3 = elem("link", "rel", "stylesheet", "type", "text/css", "href", CSS3_URL_STR);
+ css4 = elem("link", "rel", "stylesheet", "type", "text/css", "href", CSS4_URL_STR);
+ }
+
+ @Test
+ public void dontVisitSingleJs() throws Exception {
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(js1, null, false));
+ }
+
+ @Test
+ public void dontVisitSingleCss() throws Exception {
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(css1, null));
+ }
+
+ @Test
+ public void dontVisitJsWithoutSrc() throws Exception {
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(elem("script"), null, false));
+ }
+
+ @Test
+ public void dontVisitUnknown() throws Exception {
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(elem("div"), null, true));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(elem("div"), null));
+ }
+
+ @Test
+ public void dontVisitContigJsMiddleNotRewritable() throws Exception {
+ ContentRewriterFeature.Config config = config(".*two.*", false);
+ seqNodes(js1, js2, js3);
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js1));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js2));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js3));
+ }
+
+ @Test
+ public void dontVisitContigCssMiddleNotRewritable() throws Exception {
+ ContentRewriterFeature.Config config = config(".*two.*", true);
+ seqNodes(css1, css2, css3);
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(config, css1));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(config, css2));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(config, css3));
+ }
+
+ @Test
+ public void dontVisitSeparatedJsNotSplit() throws Exception {
+ ContentRewriterFeature.Config config = config(null, false);
+ Node sep1 = elem("div");
+ Node sep2 = elem("span");
+ seqNodes(js1, sep1, js2, sep2, js3);
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js1));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, sep1));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js2));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, sep2));
+ assertEquals(VisitStatus.BYPASS, getVisitStatusJs(config, js3));
+ }
+
+ @Test
+ public void visitRelFreeCss() throws Exception {
+ Node node = elem("link", "type", "text/css", "href", CSS1_URL_STR);
+ seqNodes(node, css1);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(node, null));
+ }
+
+ @Test
+ public void visitTypeCssFreeCss() throws Exception {
+ Node node = elem("link", "rel", "stylesheet", "href", CSS1_URL_STR);
+ seqNodes(node, css1);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(node, null));
+ }
+
+ @Test
+ public void dontVisitCssWithoutAttribs() throws Exception {
+ Node node = elem("link", "href", CSS1_URL_STR);
+ seqNodes(node, css1);
+ assertEquals(VisitStatus.BYPASS, getVisitStatusCss(node, null));
+ }
+
+ @Test
+ public void visitContigJs() throws Exception {
+ seqNodes(js1, js2, js3);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js1, null, false));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js2, null, false));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js3, null, false));
+ }
+
+ @Test
+ public void visitContigCss() throws Exception {
+ seqNodes(css1, css2, css3);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(css1, null));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(css2, null));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusCss(css3, null));
+ }
+
+ @Test
+ public void visitSplitJsSingle() throws Exception {
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js1, null, true));
+ }
+
+ @Test
+ public void visitSplitJsSeparated() throws Exception {
+ seqNodes(js1, elem("span"), js2, elem("div"), js3);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js1, null, true));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js2, null, true));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js3, null, true));
+ }
+
+ @Test
+ public void visitSplitJsContiguous() throws Exception {
+ seqNodes(js1, js2, js3);
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js1, null, true));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js2, null, true));
+ assertEquals(VisitStatus.RESERVE_NODE, getVisitStatusJs(js3, null, true));
+ }
+
+ @Test
+ public void concatSingleBatchJs() throws Exception {
+ List<Node> nodes = seqNodes(js1, js2, js3);
+ Node parent = js1.getParentNode();
+
+ // Sanity check.
+ assertEquals(3, parent.getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Js rewriter = new ConcatVisitor.Js(config(null, false), mgr);
+ assertTrue(rewriter.revisit(gadget(), nodes));
+
+ // Should be left with a single JS node child to parent.
+ assertEquals(1, parent.getChildNodes().getLength());
+ Element concatNode = (Element)parent.getChildNodes().item(0);
+ Uri concatUri = Uri.parse(concatNode.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri.getPath());
+ assertEquals(JS1_URL_STR, concatUri.getQueryParameter("1"));
+ assertEquals(JS2_URL_STR, concatUri.getQueryParameter("2"));
+ assertEquals(JS3_URL_STR, concatUri.getQueryParameter("3"));
+ }
+
+ @Test
+ public void concatSingleBatchCss() throws Exception {
+ List<Node> nodes = seqNodes(css1, css2, css3);
+ Node parent = css1.getParentNode();
+
+ // Sanity check.
+ assertEquals(3, parent.getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Css rewriter = new ConcatVisitor.Css(config(null, false), mgr);
+ assertTrue(rewriter.revisit(gadget(), nodes));
+
+ // Should be left with a single JS node child to parent.
+ assertEquals(1, parent.getChildNodes().getLength());
+ Element concatNode = (Element)parent.getChildNodes().item(0);
+ Uri concatUri = Uri.parse(concatNode.getAttribute("href"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri.getPath());
+ assertEquals(CSS1_URL_STR, concatUri.getQueryParameter("1"));
+ assertEquals(CSS2_URL_STR, concatUri.getQueryParameter("2"));
+ assertEquals(CSS3_URL_STR, concatUri.getQueryParameter("3"));
+ }
+
+ @Test
+ public void concatMultiBatchJs() throws Exception {
+ List<Node> fullListJs = Lists.newArrayList();
+ fullListJs.addAll(seqNodes(js1, js2));
+ Node parent1 = js1.getParentNode();
+ assertEquals(2, parent1.getChildNodes().getLength());
+
+ fullListJs.addAll(seqNodes(js3, js4));
+ Node parent2 = js3.getParentNode();
+ assertEquals(2, js3.getParentNode().getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Js rewriter = new ConcatVisitor.Js(config(null, false), mgr);
+ assertTrue(rewriter.revisit(gadget(), fullListJs));
+
+ // Should have been independently concatenated.
+ assertEquals(1, parent1.getChildNodes().getLength());
+ Element cn1 = (Element)parent1.getChildNodes().item(0);
+ Uri concatUri1 = Uri.parse(cn1.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri1.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri1.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri1.getPath());
+ assertEquals(JS1_URL_STR, concatUri1.getQueryParameter("1"));
+ assertEquals(JS2_URL_STR, concatUri1.getQueryParameter("2"));
+ assertNull(concatUri1.getQueryParameter("3"));
+
+ assertEquals(1, parent2.getChildNodes().getLength());
+ Element cn2 = (Element)parent2.getChildNodes().item(0);
+ Uri concatUri2 = Uri.parse(cn2.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri2.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri2.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri2.getPath());
+ assertEquals(JS3_URL_STR, concatUri2.getQueryParameter("1"));
+ assertEquals(JS4_URL_STR, concatUri2.getQueryParameter("2"));
+ assertNull(concatUri2.getQueryParameter("3"));
+ }
+
+ @Test
+ public void concatMultiBatchCss() throws Exception {
+ List<Node> fullListCss = Lists.newArrayList();
+ fullListCss.addAll(seqNodes(css1, css2));
+ Node parent1 = css1.getParentNode();
+ assertEquals(2, parent1.getChildNodes().getLength());
+
+ fullListCss.addAll(seqNodes(css3, css4));
+ Node parent2 = css3.getParentNode();
+ assertEquals(2, css3.getParentNode().getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Css rewriter = new ConcatVisitor.Css(config(null, false), mgr);
+ assertTrue(rewriter.revisit(gadget(), fullListCss));
+
+ // Should have been independently concatenated.
+ assertEquals(1, parent1.getChildNodes().getLength());
+ Element cn1 = (Element)parent1.getChildNodes().item(0);
+ Uri concatUri1 = Uri.parse(cn1.getAttribute("href"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri1.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri1.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri1.getPath());
+ assertEquals(CSS1_URL_STR, concatUri1.getQueryParameter("1"));
+ assertEquals(CSS2_URL_STR, concatUri1.getQueryParameter("2"));
+ assertNull(concatUri1.getQueryParameter("3"));
+
+ assertEquals(1, parent2.getChildNodes().getLength());
+ Element cn2 = (Element)parent2.getChildNodes().item(0);
+ Uri concatUri2 = Uri.parse(cn2.getAttribute("href"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri2.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri2.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri2.getPath());
+ assertEquals(CSS3_URL_STR, concatUri2.getQueryParameter("1"));
+ assertEquals(CSS4_URL_STR, concatUri2.getQueryParameter("2"));
+ assertNull(concatUri2.getQueryParameter("3"));
+ }
+
+ @Test
+ public void concatMultiBatchJsBadBatch() throws Exception {
+ List<Node> fullListJs = Lists.newArrayList();
+ fullListJs.addAll(seqNodes(js1, js2));
+ Node parent1 = js1.getParentNode();
+ assertEquals(2, parent1.getChildNodes().getLength());
+
+ fullListJs.addAll(seqNodes(js5, js6));
+ Node parent3 = js5.getParentNode();
+ assertEquals(2, parent3.getChildNodes().getLength());
+
+ fullListJs.addAll(seqNodes(js3, js4));
+ Node parent2 = js3.getParentNode();
+ assertEquals(2, js3.getParentNode().getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Js rewriter = new ConcatVisitor.Js(config(null, false), mgr);
+ assertTrue(rewriter.revisit(gadget(), fullListJs));
+
+ // Should have been independently concatenated. Batches #1 and #2 are OK. Middle skipped.
+ assertEquals(1, parent1.getChildNodes().getLength());
+ Element cn1 = (Element)parent1.getChildNodes().item(0);
+ Uri concatUri1 = Uri.parse(cn1.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri1.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri1.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri1.getPath());
+ assertEquals(JS1_URL_STR, concatUri1.getQueryParameter("1"));
+ assertEquals(JS2_URL_STR, concatUri1.getQueryParameter("2"));
+
+ assertEquals(2, parent3.getChildNodes().getLength());
+ assertSame(js5, parent3.getChildNodes().item(0));
+ assertSame(js6, parent3.getChildNodes().item(1));
+
+ assertEquals(1, parent2.getChildNodes().getLength());
+ Element cn2 = (Element)parent2.getChildNodes().item(0);
+ Uri concatUri2 = Uri.parse(cn2.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri2.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri2.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri2.getPath());
+ assertEquals(JS3_URL_STR, concatUri2.getQueryParameter("1"));
+ assertEquals(JS4_URL_STR, concatUri2.getQueryParameter("2"));
+ }
+
+ @Test
+ public void concatSplitJsSingleBatch() throws Exception {
+ List<Node> nodes = seqNodes(js1, js2);
+ Node parent = js1.getParentNode();
+ assertEquals(2, parent.getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Js rewriter = new ConcatVisitor.Js(config(null, true), mgr);
+ assertTrue(rewriter.revisit(gadget(), nodes));
+
+ // Same number of nodes. Now the second JS node is a new script node eval'ing JS.
+ // For test purposes the code is just the Uri.
+ assertEquals(3, parent.getChildNodes().getLength());
+ Element jsConcat = (Element)parent.getChildNodes().item(0);
+ assertEquals("script", jsConcat.getTagName());
+ Uri concatUri = Uri.parse(jsConcat.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri.getPath());
+ assertEquals(JS1_URL_STR, concatUri.getQueryParameter("1"));
+ assertEquals(JS2_URL_STR, concatUri.getQueryParameter("2"));
+ assertNull(concatUri.getQueryParameter("3"));
+ assertEquals("1", concatUri.getQueryParameter("SPLIT"));
+
+ // Split-eval nodes 1 and 2
+ Element splitEval1 = (Element)parent.getChildNodes().item(1);
+ assertEquals("script", splitEval1.getTagName());
+ assertNull(splitEval1.getAttributeNode("src"));
+ assertEquals(JS1_URL_STR, splitEval1.getTextContent());
+
+ Element splitEval2 = (Element)parent.getChildNodes().item(2);
+ assertEquals("script", splitEval2.getTagName());
+ assertNull(splitEval2.getAttributeNode("src"));
+ assertEquals(JS2_URL_STR, splitEval2.getTextContent());
+ }
+
+ @Test
+ public void concatSplitJsSplitNodes() throws Exception {
+ Node parent = doc.createElement("container");
+ parent.appendChild(doc.createElement("div"));
+ parent.appendChild(js1);
+ parent.appendChild(doc.createTextNode("text"));
+ parent.appendChild(doc.createComment("comment"));
+ parent.appendChild(js2);
+ parent.appendChild(doc.createElement("span"));
+ List<Node> nodes = ImmutableList.of(js1, js2);
+ assertEquals(6, parent.getChildNodes().getLength());
+
+ SimpleConcatUriManager mgr = simpleMgr();
+ ConcatVisitor.Js rewriter = new ConcatVisitor.Js(config(null, true), mgr);
+ assertTrue(rewriter.revisit(gadget(), nodes));
+
+ // Same number of nodes. Now the second JS node is a new script node eval'ing JS.
+ // For test purposes the code is just the Uri.
+ assertEquals(7, parent.getChildNodes().getLength());
+ Element jsConcat = (Element)parent.getChildNodes().item(1);
+ assertEquals("script", jsConcat.getTagName());
+ Uri concatUri = Uri.parse(jsConcat.getAttribute("src"));
+ assertEquals(CONCAT_BASE_URI.getScheme(), concatUri.getScheme());
+ assertEquals(CONCAT_BASE_URI.getAuthority(), concatUri.getAuthority());
+ assertEquals(CONCAT_BASE_URI.getPath(), concatUri.getPath());
+ assertEquals(JS1_URL_STR, concatUri.getQueryParameter("1"));
+ assertEquals(JS2_URL_STR, concatUri.getQueryParameter("2"));
+ assertNull(concatUri.getQueryParameter("3"));
+ assertEquals("1", concatUri.getQueryParameter("SPLIT"));
+
+ // Split-eval nodes 1 and 2
+ Element splitEval1 = (Element)parent.getChildNodes().item(2);
+ assertEquals("script", splitEval1.getTagName());
+ assertNull(splitEval1.getAttributeNode("src"));
+ assertEquals(JS1_URL_STR, splitEval1.getTextContent());
+
+ Element splitEval2 = (Element)parent.getChildNodes().item(5);
+ assertEquals("script", splitEval2.getTagName());
+ assertNull(splitEval2.getAttributeNode("src"));
+ assertEquals(JS2_URL_STR, splitEval2.getTextContent());
+ }
+
+ private VisitStatus getVisitStatusJs(ContentRewriterFeature.Config config, Node node)
+ throws RewritingException {
+ return new ConcatVisitor.Js(config, null).visit(gadget(), node);
+ }
+
+ private VisitStatus getVisitStatusJs(Node node, String rewriteRegex, boolean splitJs)
+ throws Exception {
+ ContentRewriterFeature.Config config = config(rewriteRegex, splitJs);
+ return getVisitStatusJs(config, node);
+ }
+
+ private VisitStatus getVisitStatusCss(ContentRewriterFeature.Config config, Node node)
+ throws RewritingException {
+ return new ConcatVisitor.Css(config, null).visit(gadget(), node);
+ }
+
+ private VisitStatus getVisitStatusCss(Node node, String rewriteRegex)
+ throws Exception {
+ // True, but never used (splitJS support)
+ ContentRewriterFeature.Config config = config(rewriteRegex, true);
+ return getVisitStatusCss(config, node);
+ }
+
+ private ContentRewriterFeature.Config config(String exclude, boolean splitJs) {
+ return new ContentRewriterFeature.DefaultConfig(".*", exclude == null ? "" : exclude,
+ "0", "", "false", splitJs ? "true" : "false");
+ }
+
+ private List<Node> seqNodes(Node... nodes) {
+ Node container = doc.createElement("container");
+ List<Node> seq = Lists.newArrayListWithCapacity(nodes.length);
+ for (Node node : nodes) {
+ container.appendChild(node);
+ seq.add(node);
+ }
+ return seq;
+ }
+
+ private SimpleConcatUriManager simpleMgr() {
+ return new SimpleConcatUriManager(CONCAT_BASE_URI);
+ }
+
+ private static class SimpleConcatUriManager implements ConcatUriManager {
+ private final Uri base;
+
+ private SimpleConcatUriManager(Uri base) {
+ this.base = base;
+ }
+
+ public List<ConcatData> make(List<ConcatUri> batches, boolean isAdjacent) {
+ List<ConcatData> results = Lists.newArrayListWithCapacity(batches.size());
+ for (ConcatUri batch : batches) {
+ UriBuilder uriBuilder = new UriBuilder(base);
+ Integer i = 1;
+ for (Uri uri : batch.getBatch()) {
+ uriBuilder.addQueryParameter((i++).toString(), uri.toString());
+ }
+ Map<Uri, String> snippets = Maps.newHashMap();
+ if (!isAdjacent) {
+ for (Uri uri : batch.getBatch()) {
+ snippets.put(uri, uri.toString());
+ }
+ uriBuilder.addQueryParameter("SPLIT", "1");
+ }
+ results.add(new ConcatData(uriBuilder.toUri(), snippets));
+ }
+ return results;
+ }
+
+ public ConcatUri process(Uri uri) {
+ // Not used in test code.
+ throw new UnsupportedOperationException();
+ }
+
+ }
+}