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);
+    }
+  }
+}


Reply via email to