Author: johnh
Date: Wed Jul 14 19:49:17 2010
New Revision: 964153
URL: http://svn.apache.org/viewvc?rev=964153&view=rev
Log:
Makes it possible to read and process client-side opensocial templates as JSON,
to avoid browser security issues potential in employing direct use of parser.
Added:
shindig/trunk/features/src/main/javascript/features/jsondom/
shindig/trunk/features/src/main/javascript/features/jsondom/feature.xml
shindig/trunk/features/src/main/javascript/features/jsondom/jsondom.js
shindig/trunk/features/src/test/javascript/features/jsondom/
shindig/trunk/features/src/test/javascript/features/jsondom/jsondom-test.js
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriter.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriterTest.java
Modified:
shindig/trunk/features/src/main/javascript/features/core.io/io.js
shindig/trunk/features/src/main/javascript/features/features.txt
shindig/trunk/features/src/main/javascript/features/opensocial-templates/base.js
shindig/trunk/features/src/main/javascript/features/opensocial-templates/feature.xml
shindig/trunk/features/src/main/javascript/features/opensocial-templates/jsTemplate/util.js
shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
Modified: shindig/trunk/features/src/main/javascript/features/core.io/io.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/core.io/io.js?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/core.io/io.js (original)
+++ shindig/trunk/features/src/main/javascript/features/core.io/io.js Wed Jul
14 19:49:17 2010
@@ -507,7 +507,7 @@ gadgets.io = function() {
replace("%rawurl%", url).
replace("%refresh%", encodeURIComponent(refresh)).
replace("%gadget%", encodeURIComponent(urlParams.url)).
- replace("%container%", encodeURIComponent(urlParams.container ||
urlParams.synd)).
+ replace("%container%", encodeURIComponent(urlParams.container ||
urlParams.synd || "default")).
replace("%rewriteMime%", rewriteMimeParam);
if (ret.indexOf('//') == 0) {
ret = window.location.protocol + ret;
Modified: shindig/trunk/features/src/main/javascript/features/features.txt
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/features.txt?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Wed Jul 14
19:49:17 2010
@@ -37,6 +37,7 @@ features/dynamic-height.util/feature.xml
features/dynamic-height/feature.xml
features/flash/feature.xml
features/i18n/feature.xml
+features/jsondom/feature.xml
features/locked-domain/feature.xml
features/minimessage/feature.xml
features/oauthpopup/feature.xml
Added: shindig/trunk/features/src/main/javascript/features/jsondom/feature.xml
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/jsondom/feature.xml?rev=964153&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/jsondom/feature.xml
(added)
+++ shindig/trunk/features/src/main/javascript/features/jsondom/feature.xml Wed
Jul 14 19:49:17 2010
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!--
+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.
+-->
+<feature>
+ <!--
+ jsondom (Read Only DOM) is implemented in both JavaScript
+ and server-side code. Its JavaScript-only implementation
+ provides a DOM parser that uses underlying browser objects.
+ This is, however, not Caja-compatible.
+
+ Therefore a server-side DOM-to-JSON parser mode is provided
+ to support this use case, where either explicitly required
+ (Caja context) or otherwise directly requested.
+ -->
+ <name>jsondom</name>
+ <dependency>globals</dependency>
+ <dependency>xmlutil</dependency>
+ <gadget>
+ <script src="jsondom.js"/>
+ </gadget>
+</feature>
Added: shindig/trunk/features/src/main/javascript/features/jsondom/jsondom.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/jsondom/jsondom.js?rev=964153&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/jsondom/jsondom.js
(added)
+++ shindig/trunk/features/src/main/javascript/features/jsondom/jsondom.js Wed
Jul 14 19:49:17 2010
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+// Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/
+// core.html#ID-1950641247>.
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+gadgets.jsondom = (function() {
+ var domCache = {};
+
+ function Node(data, opt_nextSibling) {
+ if (typeof data === "string") {
+ return Text(data, opt_nextSibling);
+ } else if (typeof data === "object") {
+ if (data.e) {
+ throw new Error(data.e);
+ }
+ return Element(data, opt_nextSibling);
+ }
+ return null;
+ }
+
+ function Element(json, opt_nextSibling) {
+ var nodeType = DOM_ELEMENT_NODE;
+ var tagName = json.n;
+ var attributes = [];
+ var children = [];
+ var nextSibling = opt_nextSibling;
+
+ // Set up attributes.
+ // They are passed as an array named "a", with
+ // each value having "n" = name and "v" = value.
+ for (var i = 0; i < json.a.length; ++i) {
+ attributes.push(Attr(json.a[i].n, json.a[i].v));
+ }
+
+ // Set up children. Do so from the back of the list to
+ // properly set up nextSibling references.
+ var reverseChildren = [];
+ var backChild = (json.c.length > 0 ? Node(json.c[json.c.length-1]) : null);
+ for (var i = json.c.length - 2; i >= 0; --i) {
+ var next = Node(json.c[i], backChild);
+ reverseChildren.push(next);
+ backChild = next;
+ }
+
+ // children is the reverse of reverseChildren
+ for (var i = reverseChildren.length - 1; i >= 0; --i) {
+ children.push(reverseChildren[i]);
+ }
+
+ return {
+ nodeType: nodeType,
+ tagName: tagName,
+ children: children,
+ attributes: attributes,
+ firstChild: children[0],
+ nextSibling: nextSibling,
+ getAttribute: function(key) {
+ for (var i = 0; i < attributes.length; ++i) {
+ if (attributes[i].nodeName == key) {
+ return attributes[i];
+ }
+ }
+ return null;
+ }
+ }
+ }
+
+ function Text(value, opt_nextSibling, opt_name, opt_type) {
+ var nodeType = opt_type || DOM_TEXT_NODE;
+ var nodeName = opt_name || "#text";
+ var nodeValue = value;
+ var nextSibling = opt_nextSibling;
+
+ return {
+ nodeType: nodeType,
+ nodeName: nodeName,
+ nodeValue: nodeValue,
+ data: nodeValue,
+ nextSibling: nextSibling,
+ cloneNode: function() {
+ return Text(nodeValue, nodeName);
+ }
+ }
+ }
+
+ function Attr(name, value) {
+ return Text(value, null, name, DOM_ATTR_NODE);
+ }
+
+ function preload(id, json) {
+ domCache[id] = Node(json);
+ }
+
+ function parse(str, opt_id) {
+ // Unique ID per parseable String.
+ if (opt_id && domCache[opt_id]) {
+ return domCache[opt_id];
+ }
+
+ // Parse using browser primitives.
+ var doc = opensocial.xmlutil.parseXML(str);
+
+ if (opt_id) {
+ domCache[opt_id] = doc;
+ }
+
+ return doc;
+ }
+
+ return {
+ parse: parse,
+ preload_: preload
+ };
+})();
Modified:
shindig/trunk/features/src/main/javascript/features/opensocial-templates/base.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensocial-templates/base.js?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
---
shindig/trunk/features/src/main/javascript/features/opensocial-templates/base.js
(original)
+++
shindig/trunk/features/src/main/javascript/features/opensocial-templates/base.js
Wed Jul 14 19:49:17 2010
@@ -140,7 +140,7 @@ os.compileTemplate = function(node, opt_
*/
os.compileTemplateString = function(src, opt_id, opt_container) {
src = opensocial.xmlutil.prepareXML(src, opt_container);
- var doc = opensocial.xmlutil.parseXML(src);
+ var doc = gadgets.jsondom.parse(src, opt_id);
return os.compileXMLDoc(doc, opt_id);
};
Modified:
shindig/trunk/features/src/main/javascript/features/opensocial-templates/feature.xml
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensocial-templates/feature.xml?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
---
shindig/trunk/features/src/main/javascript/features/opensocial-templates/feature.xml
(original)
+++
shindig/trunk/features/src/main/javascript/features/opensocial-templates/feature.xml
Wed Jul 14 19:49:17 2010
@@ -33,6 +33,7 @@ This should not be used in a production
<name>opensocial-templates</name>
<dependency>globals</dependency>
<dependency>opensocial-data-context</dependency>
+ <dependency>jsondom</dependency>
<dependency>security-token</dependency>
<dependency>xmlutil</dependency>
<gadget>
Modified:
shindig/trunk/features/src/main/javascript/features/opensocial-templates/jsTemplate/util.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensocial-templates/jsTemplate/util.js?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
---
shindig/trunk/features/src/main/javascript/features/opensocial-templates/jsTemplate/util.js
(original)
+++
shindig/trunk/features/src/main/javascript/features/opensocial-templates/jsTemplate/util.js
Wed Jul 14 19:49:17 2010
@@ -180,23 +180,6 @@ function bindFully(object, method, var_a
};
}
-// Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/
-// core.html#ID-1950641247>.
-var DOM_ELEMENT_NODE = 1;
-var DOM_ATTRIBUTE_NODE = 2;
-var DOM_TEXT_NODE = 3;
-var DOM_CDATA_SECTION_NODE = 4;
-var DOM_ENTITY_REFERENCE_NODE = 5;
-var DOM_ENTITY_NODE = 6;
-var DOM_PROCESSING_INSTRUCTION_NODE = 7;
-var DOM_COMMENT_NODE = 8;
-var DOM_DOCUMENT_NODE = 9;
-var DOM_DOCUMENT_TYPE_NODE = 10;
-var DOM_DOCUMENT_FRAGMENT_NODE = 11;
-var DOM_NOTATION_NODE = 12;
-
-
-
function domGetElementById(document, id) {
return document.getElementById(id);
}
Modified:
shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js?rev=964153&r1=964152&r2=964153&view=diff
==============================================================================
---
shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
(original)
+++
shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
Wed Jul 14 19:49:17 2010
@@ -145,7 +145,7 @@ os.Loader.loadUrls = function(urls, call
* Processes the XML markup of a Template Library.
*/
os.Loader.loadContent = function(xmlString, url) {
- var doc = opensocial.xmlutil.parseXML(xmlString);
+ var doc = gadgets.jsondom.parse(xmlString, url);
var templatesNode = doc.firstChild;
os.Loader.processTemplatesNode(templatesNode);
os.Loader.loadedUrls_[url] = true;
Added:
shindig/trunk/features/src/test/javascript/features/jsondom/jsondom-test.js
URL:
http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/jsondom/jsondom-test.js?rev=964153&view=auto
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/jsondom/jsondom-test.js
(added)
+++ shindig/trunk/features/src/test/javascript/features/jsondom/jsondom-test.js
Wed Jul 14 19:49:17 2010
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+function JsonDomTest(name) {
+ TestCase.call(this, name);
+}
+
+JsonDomTest.inherits(TestCase);
+
+JsonDomTest.prototype.setUp = function() {
+};
+
+JsonDomTest.prototype.tearDown = function() {
+};
+
+JsonDomTest.prototype.testParsePrecached = function() {
+ // TODO: implement
+};
+
+JsonDomTest.prototype.testParseNotPreloadedDelegatesToBrowser = function() {
+ // TODO: implement
+};
+
+JsonDomTest.prototype.testPreloadSingleString = function() {
+ // TODO: implement
+};
+
+JsonDomTest.prototype.testPreloadSingleElement = function() {
+ // TODO: implement
+};
+
+JsonDomTest.prototype.testPreloadError = function() {
+ // TODO: implement
+};
+
+JsonDomTest.prototype.testPreloadFullTemplate = function() {
+ // TODO: implement
+};
Added:
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriter.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriter.java?rev=964153&view=auto
==============================================================================
---
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriter.java
(added)
+++
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriter.java
Wed Jul 14 19:49:17 2010
@@ -0,0 +1,201 @@
+/*
+ * 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.inject.Inject;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.shindig.common.xml.DomUtil;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.rewrite.DomWalker;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+import java.util.List;
+
+public class OsTemplateXmlLoaderRewriter extends DomWalker.Rewriter {
+ public static final String OS_TEMPLATE_MIME = "os/template";
+ public static final String OS_TEMPLATES_FEATURE_NAME =
"opensocial-templates";
+ private static final String PRELOAD_TPL =
"gadgets.jsondom.preload_('%s',%s);";
+
+ private final Converter converter;
+
+ @Inject
+ public OsTemplateXmlLoaderRewriter(Converter converter) {
+ super(new GadgetHtmlVisitor(converter));
+ this.converter = converter;
+ }
+
+ // Override the HTTP rewrite method to provide custom type checking.
+ // The gadget rewrite method remains standard, using the Visitor pattern.
+ public boolean rewrite(HttpRequest request, HttpResponse original,
+ MutableContent content) throws RewritingException {
+ String mimeType = RewriterUtils.getMimeType(request, original);
+ if (OS_TEMPLATE_MIME.equalsIgnoreCase(mimeType)) {
+ content.setContent(converter.domToJson(content.getContent()));
+ return true;
+ }
+ return false;
+ }
+
+ public static class GadgetHtmlVisitor implements DomWalker.Visitor {
+ private final Converter converter;
+
+ public GadgetHtmlVisitor(Converter converter) {
+ this.converter = converter;
+ }
+
+ public VisitStatus visit(Gadget gadget, Node node) throws
RewritingException {
+ if (node.getNodeType() == Node.ELEMENT_NODE &&
+ "div".equalsIgnoreCase(((Element)node).getTagName()) &&
+
OS_TEMPLATE_MIME.equalsIgnoreCase(((Element)node).getAttribute("type")) &&
+ (!StringUtils.isEmpty(((Element)node).getAttribute("id")) ||
+ !StringUtils.isEmpty(((Element)node).getAttribute("name")))) {
+ return VisitStatus.RESERVE_NODE;
+ }
+ return VisitStatus.BYPASS;
+ }
+
+ public boolean revisit(Gadget gadget, List<Node> nodes) throws
RewritingException {
+ if (!gadget.getAllFeatures().contains(OS_TEMPLATES_FEATURE_NAME)) {
+ return false;
+ }
+
+ Document doc = nodes.get(0).getOwnerDocument();
+ Element docElem = doc.getDocumentElement();
+ if (docElem == null) {
+ throw new RewritingException("Unexpected error, missing document
element",
+ HttpResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ Node head = DomUtil.getFirstNamedChildNode(doc.getDocumentElement(),
"head");
+ if (head == null) {
+ throw new RewritingException("Unexpected error, could not find <head>
node",
+ HttpResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ StringBuilder preloadScript = new StringBuilder();
+
+ for (Node node : nodes) {
+ Element elem = (Element)node;
+ String value = elem.getTextContent();
+ String id = elem.getAttribute("name");
+ if (StringUtils.isEmpty(id)) {
+ id = elem.getAttribute("id");
+ }
+
+ preloadScript.append(String.format(PRELOAD_TPL, id,
converter.domToJson(value)));
+ }
+
+ Node script = doc.createElement("script");
+ script.setTextContent(preloadScript.toString());
+ head.appendChild(script);
+
+ return true;
+ }
+ }
+
+ public static class Converter {
+ public static final String NAME_KEY = "n";
+ public static final String VALUE_KEY = "v";
+ public static final String CHILDREN_KEY = "c";
+ public static final String ATTRIBS_KEY = "a";
+ public static final String ERROR_KEY = "e";
+
+ private final GadgetHtmlParser parser;
+ private final DOMImplementation domImpl;
+
+ @Inject
+ public Converter(GadgetHtmlParser parser, DOMImplementation domImpl) {
+ this.parser = parser;
+ this.domImpl = domImpl;
+ }
+
+ public String domToJson(String xml) {
+ try {
+ Document doc = domImpl.createDocument(null, null, null);
+ Element container = doc.createElement("template");
+ parser.parseFragment(xml, container);
+ return jsonFromElement(container).toString();
+ } catch (GadgetException e) {
+ return jsonError("Gadget Exception: " + e).toString();
+ } catch (JSONException e) {
+ return jsonError("JSON Exception: " + e).toString();
+ }
+ }
+
+ public JSONObject jsonFromElement(Element elem) throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put(NAME_KEY, elem.getTagName());
+
+ JSONArray attribs = new JSONArray();
+ NamedNodeMap attribMap = elem.getAttributes();
+ for (int i = 0; i < attribMap.getLength(); ++i) {
+ JSONObject attrib = new JSONObject();
+ Attr domAttrib = (Attr)attribMap.item(i);
+ attrib.put(NAME_KEY, domAttrib.getNodeName());
+ attrib.put(VALUE_KEY, domAttrib.getNodeValue());
+ attribs.put(attrib);
+ }
+ json.put(ATTRIBS_KEY, attribs);
+
+ JSONArray children = new JSONArray();
+ for (Node child = elem.getFirstChild(); child != null; child =
child.getNextSibling()) {
+ switch (child.getNodeType()) {
+ case Node.TEXT_NODE:
+ children.put(((Text)child).getNodeValue());
+ break;
+ case Node.DOCUMENT_NODE:
+ case Node.ELEMENT_NODE:
+ children.put(jsonFromElement((Element)child));
+ break;
+ default:
+ // No other node types are supported.
+ break;
+ }
+ }
+ json.put(CHILDREN_KEY, children);
+
+ return json;
+ }
+
+ private JSONObject jsonError(String err) {
+ JSONObject json = new JSONObject();
+ try {
+ json.put(ERROR_KEY, err);
+ } catch (JSONException e) {
+ // Doesn't happen.
+ }
+ return json;
+ }
+ }
+}
Added:
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriterTest.java
URL:
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriterTest.java?rev=964153&view=auto
==============================================================================
---
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriterTest.java
(added)
+++
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/OsTemplateXmlLoaderRewriterTest.java
Wed Jul 14 19:49:17 2010
@@ -0,0 +1,340 @@
+/*
+ * 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.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import org.apache.shindig.common.PropertiesModule;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.xml.DomUtil;
+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.http.HttpResponseBuilder;
+import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
+import org.apache.shindig.gadgets.parse.ParseModule;
+import org.apache.shindig.gadgets.rewrite.DomWalker.Visitor.VisitStatus;
+import
org.apache.shindig.gadgets.rewrite.OsTemplateXmlLoaderRewriter.Converter;
+
+import org.json.JSONObject;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class OsTemplateXmlLoaderRewriterTest {
+ private GadgetHtmlParser parser;
+ private DOMImplementation domImpl;
+ private Document doc;
+ private Converter converter;
+
+ @Before
+ public void setUp() {
+ Injector injector = Guice.createInjector(new ParseModule(), new
PropertiesModule());
+ parser = injector.getInstance(GadgetHtmlParser.class);
+ domImpl = injector.getInstance(DOMImplementation.class);
+ doc = domImpl.createDocument(null, null, null);
+ converter = new Converter(parser, domImpl);
+ }
+
+ @Test
+ public void convertSingleElement() throws Exception {
+ String xml = "<os:elem id=\"id\" foo=\"bar\">String value</os:elem>";
+ assertEquals(
+ new
JSONObject("{n:\"template\",a:[],c:[{n:\"os:elem\",a:[{n:\"foo\",v:\"bar\"}," +
+ "{n:\"id\",v:\"id\"}],c:[\"String value\"]}]}").toString(),
+ converter.domToJson(xml));
+ }
+
+ @Test
+ public void convertMixedTreeWithIgnorables() throws Exception {
+ String xml = "<b>Some ${viewer} content</b> <img/><!-- comment
--><os:Html/>";
+ assertEquals(
+ new JSONObject("{n:\"template\",a:[],c:[{n:\"b\",a:[],c:" +
+ "[\"Some ${viewer} content\"]},\" \",{n:\"img\",a:[],c:[]}," +
+ "{n:\"os:Html\",a:[],c:[]}]}").toString(),
+ converter.domToJson(xml));
+ }
+
+ @Test
+ public void visitNonElement() throws Exception {
+ assertEquals(VisitStatus.BYPASS, visit(doc.createTextNode("text")));
+ assertEquals(VisitStatus.BYPASS, visit(doc.createAttribute("foo")));
+ assertEquals(VisitStatus.BYPASS, visit(doc.createComment("comment")));
+ }
+
+ @Test
+ public void visitDivSansType() throws Exception {
+ assertEquals(VisitStatus.BYPASS, visit(doc.createElement("div")));
+ }
+
+ @Test
+ public void visitDivMismatchingType() throws Exception {
+ Element div = doc.createElement("div");
+ div.setAttribute("id", "id");
+ div.setAttribute("type", "os/template-but-not");
+ assertEquals(VisitStatus.BYPASS, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingTypeNoId() throws Exception {
+ Element div = doc.createElement("div");
+ div.setAttribute("type", OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME);
+ assertEquals(VisitStatus.BYPASS, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingTypeBlankIdAndName() throws Exception {
+ Element div = doc.createElement("div");
+ div.setAttribute("id", "");
+ div.setAttribute("name", "");
+ div.setAttribute("type", OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME);
+ assertEquals(VisitStatus.BYPASS, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingTypeWithId() throws Exception {
+ Element div = createRewritableDiv();
+ assertEquals(VisitStatus.RESERVE_NODE, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingCaseMixedWithId() throws Exception {
+ Element div = doc.createElement("dIv");
+ div.setAttribute("id", "id");
+ div.setAttribute("type",
OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME.toUpperCase());
+ assertEquals(VisitStatus.RESERVE_NODE, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingTypeWithName() throws Exception {
+ Element div = doc.createElement("div");
+ div.setAttribute("name", "id");
+ div.setAttribute("type", OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME);
+ assertEquals(VisitStatus.RESERVE_NODE, visit(div));
+ }
+
+ @Test
+ public void visitDivMatchingCaseMixedWithName() throws Exception {
+ Element div = doc.createElement("dIv");
+ div.setAttribute("name", "id");
+ div.setAttribute("type",
OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME.toUpperCase());
+ assertEquals(VisitStatus.RESERVE_NODE, visit(div));
+ }
+
+ private VisitStatus visit(Node node) throws Exception {
+ return new OsTemplateXmlLoaderRewriter.GadgetHtmlVisitor(null).visit(null,
node);
+ }
+
+ @Test
+ public void revisitWithoutOsTemplates() throws Exception {
+ assertFalse(revisit(mockGadget("foo", "bar"), null));
+ }
+
+ @Test(expected = RewritingException.class)
+ public void revisitWithoutValidDocument() throws Exception {
+ revisit(mockGadget(OsTemplateXmlLoaderRewriter.OS_TEMPLATES_FEATURE_NAME,
"foo"),
+ null, createRewritableDiv());
+ }
+
+ @Test(expected = RewritingException.class)
+ public void revisitWithoutHeadNode() throws Exception {
+ Node html = doc.createElement("html");
+ html.appendChild(doc.createElement("body"));
+ doc.appendChild(html);
+ revisit(mockGadget(OsTemplateXmlLoaderRewriter.OS_TEMPLATES_FEATURE_NAME,
"foo"),
+ null, createRewritableDiv());
+ }
+
+ @Test
+ public void revisitWithIdDivSingle() throws Exception {
+ Element tpl = createRewritableDiv("tpl_id");
+ checkRevisitSingle(tpl, "tpl_id");
+ }
+
+ @Test
+ public void revisitWithNameDivSingle() throws Exception {
+ Element tpl = createRewritableDiv();
+ tpl.removeAttribute("id");
+ tpl.setAttribute("name", "otherid");
+ checkRevisitSingle(tpl, "otherid");
+ }
+
+ @Test
+ public void revisitWithBothLabeledDivSingle() throws Exception {
+ Element tpl = createRewritableDiv();
+ tpl.setAttribute("name", "otherid");
+ checkRevisitSingle(tpl, "otherid");
+ }
+
+ private void checkRevisitSingle(Element tpl, String id) throws Exception {
+ Gadget gadget =
mockGadget(OsTemplateXmlLoaderRewriter.OS_TEMPLATES_FEATURE_NAME, "another");
+ String xmlVal = "xml";
+ Converter converter = mockConverter(xmlVal, "{thejson}", 1);
+ tpl.setTextContent(xmlVal);
+ completeDocAsHtml(tpl);
+ assertTrue(revisit(gadget, converter, tpl));
+ verify(gadget);
+ verify(converter);
+ Node head = DomUtil.getFirstNamedChildNode(doc.getDocumentElement(),
"head");
+ assertNotNull(head);
+ assertEquals(2, head.getChildNodes().getLength());
+ Node addedScript = head.getChildNodes().item(1);
+ assertEquals(Node.ELEMENT_NODE, addedScript.getNodeType());
+ assertEquals("script", addedScript.getNodeName());
+ assertEquals("gadgets.jsondom.preload_('" + id + "',{thejson});",
addedScript.getTextContent());
+ }
+
+ @Test
+ public void revisitMultiples() throws Exception {
+ Element tplId = createRewritableDiv("tpl_id");
+ Element tplName = createRewritableDiv();
+ tplName.removeAttribute("id");
+ tplName.setAttribute("name", "otherid");
+ Gadget gadget =
mockGadget(OsTemplateXmlLoaderRewriter.OS_TEMPLATES_FEATURE_NAME, "another");
+ String xmlVal = "thexml";
+ Converter converter = mockConverter(xmlVal, "{thejson}", 2);
+ tplId.setTextContent(xmlVal);
+ tplName.setTextContent(xmlVal);
+ completeDocAsHtml(tplId, tplName);
+ assertTrue(revisit(gadget, converter, tplId, tplName));
+ verify(gadget);
+ verify(converter);
+ Node head = DomUtil.getFirstNamedChildNode(doc.getDocumentElement(),
"head");
+ assertNotNull(head);
+ assertEquals(2, head.getChildNodes().getLength());
+ Node addedScript = head.getChildNodes().item(1);
+ assertEquals(Node.ELEMENT_NODE, addedScript.getNodeType());
+ assertEquals("script", addedScript.getNodeName());
+ assertEquals(
+
"gadgets.jsondom.preload_('tpl_id',{thejson});gadgets.jsondom.preload_('otherid',{thejson});",
+ addedScript.getTextContent());
+ }
+
+ private boolean revisit(Gadget gadget, Converter converter, Node... nodes)
throws Exception {
+ return new OsTemplateXmlLoaderRewriter.GadgetHtmlVisitor(converter)
+ .revisit(gadget, Arrays.asList(nodes));
+ }
+
+ private Gadget mockGadget(String... features) {
+ Gadget gadget = createMock(Gadget.class);
+ expect(gadget.getAllFeatures()).andReturn(Arrays.asList(features)).once();
+ replay(gadget);
+ return gadget;
+ }
+
+ private Converter mockConverter(String xml, String result, int times) {
+ Converter converter = createMock(Converter.class);
+ expect(converter.domToJson(xml)).andReturn(result).times(times);
+ replay(converter);
+ return converter;
+ }
+
+ private Element createRewritableDiv() {
+ return createRewritableDiv("id");
+ }
+
+ private Element createRewritableDiv(String id) {
+ Element div = doc.createElement("div");
+ div.setAttribute("type", OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME);
+ div.setAttribute("id", id);
+ return div;
+ }
+
+ private void completeDocAsHtml(Node... nodes) {
+ Node html = doc.createElement("html");
+ Node head = doc.createElement("head");
+ Node headScript = doc.createElement("script");
+ head.appendChild(headScript);
+ Node body = doc.createElement("body");
+ for (Node node : nodes) {
+ body.appendChild(node);
+ }
+ html.appendChild(head);
+ html.appendChild(body);
+ while (doc.hasChildNodes()) {
+ doc.removeChild(doc.getFirstChild());
+ }
+ doc.appendChild(html);
+ }
+
+ @Test
+ public void rewriteHttpNoMime() throws Exception {
+ checkRewriteHttp(null, null, false);
+ }
+
+ @Test
+ public void rewriteHttpMismatchedMime() throws Exception {
+ checkRewriteHttp("os/template-not!", null, false);
+ }
+
+ @Test
+ public void rewriteHttpMimeMatchOverride() throws Exception {
+ checkRewriteHttp(OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME,
"os/template-not!", true);
+ }
+
+ @Test
+ public void rewriteHttpMimeMatchOriginal() throws Exception {
+ checkRewriteHttp(null, OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME, true);
+ }
+
+ @Test
+ public void rewriteHttpMimeMatchOverrideMismatchOriginal() throws Exception {
+ checkRewriteHttp("foo", OsTemplateXmlLoaderRewriter.OS_TEMPLATE_MIME,
false);
+ }
+
+ private void checkRewriteHttp(String reqMime, String origMime, boolean
expectRewrite)
+ throws Exception {
+ HttpRequest req = new
HttpRequest(Uri.parse("http://dummy.com")).setRewriteMimeType(reqMime);
+ HttpResponse resp = new HttpResponseBuilder().setHeader("Content-Type",
origMime).create();
+ String inXml = "thexml";
+ String outJson = "{thejson}";
+ Converter converter = mockConverter(inXml, outJson, 1);
+ MutableContent mc = createMock(MutableContent.class);
+ if (expectRewrite) {
+ expect(mc.getContent()).andReturn(inXml).once();
+ mc.setContent(outJson);
+ expectLastCall().once();
+ }
+ replay(mc);
+ boolean result = new OsTemplateXmlLoaderRewriter(converter).rewrite(req,
resp, mc);
+ assertEquals(expectRewrite, result);
+ verify(mc);
+ if (expectRewrite) {
+ verify(converter);
+ }
+ }
+}