You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2008/02/27 03:36:23 UTC

svn commit: r631466 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/ main/java/org/apache/shindig/gadgets/http/ main/java/org/apache/shindig/util/ test/java/org/apache/shindig/gadgets/

Author: etnu
Date: Tue Feb 26 18:36:16 2008
New Revision: 631466

URL: http://svn.apache.org/viewvc?rev=631466&view=rev
Log:
Committed Bruno's view refactoring for SHINDIG-80 (with some heavy changes).


Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ProcessingOptions.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpProcessingOptions.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcProcessingOptions.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/XmlUtil.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecParserTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java Tue Feb 26 18:36:16 2008
@@ -263,42 +263,28 @@
   }
 
   /**
-   * @return Type of gadget to render
+   * @return debug string of internal state
    */
-  public ContentType getContentType() {
-    return baseSpec.getContentType();
+  @Override
+  public String toString() {
+    return baseSpec.toString();
   }
 
   /**
-   * @return URI of gadget to render of type == URL; null if malformed/missing
-   * @throws IllegalStateException if contentType is not URL.
+   * @param viewName of the view whose content to retrieve
+   * @return fully parsed View object
    */
-  public URI getContentHref() {
-    URI ret = null;
-    String uriStr = baseSpec.getContentHref().toString();
-    try {
-      ret = new URI(substitutions.substitute(uriStr));
-    } catch (URISyntaxException e) {
-      return null;
-    }
-    return ret;
+  public View getView(String viewName) {
+    return baseSpec.getView(viewName);
   }
 
   /**
-   * @return Gadget contents with all substitutions applied
-   * @throws IllegalStateException if contentType is not HTML.
-   */
-  public String getContentData() {
-    return getContentData(null);
-  }
-
-  /**
-   * @param view ID of the view whose content to retrieve
+   * @param viewName of the view whose content to retrieve
    * @return Gadget contents for the given view with all substitutions applied
    * @throws IllegalStateException if contentType is not HTML
    */
-  public String getContentData(String view) {
-    return substitutions.substitute(baseSpec.getContentData(view));
+  public String getContentData(String viewName) {
+    return substitutions.substitute(getView(viewName).getData());
   }
 
   private MessageBundle currentMessageBundle = MessageBundle.EMPTY;

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java Tue Feb 26 18:36:16 2008
@@ -45,6 +45,11 @@
     // Feature-related errors
     UNSUPPORTED_FEATURE,
 
+    // General error, should be accompanied by message
+    INVALID_PARAMETER,
+    MISSING_PARAMETER,
+    UNRECOGNIZED_PARAMETER,
+
     // Interface component errors.
     MISSING_FEATURE_REGISTRY,
     MISSING_MESSAGE_BUNDLE_CACHE,

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java Tue Feb 26 18:36:16 2008
@@ -514,6 +514,11 @@
       this.components.add(new GadgetException(code));
     }
 
+    public GadgetProcessException(GadgetException.Code code, String message) {
+      this.components = new ArrayList<GadgetException>();
+      this.components.add(new GadgetException(code, message));
+    }
+
     public List<GadgetException> getComponents() {
       return components;
     }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java Tue Feb 26 18:36:16 2008
@@ -82,35 +82,54 @@
   public List<UserPref> getUserPrefs();
 
   public enum ContentType {
-    HTML, URL
-  }
-
-  public ContentType getContentType();
+    HTML, URL;
 
-  /**
-   * Must be a URI type gadget.
-   *
-   * @return The URI for this gadget spec.
-   * @throws IllegalStateException if contentType is not URI.
-   */
-  public URI getContentHref();
+    public static ContentType parse(String type) {
+      if ("url".equals(type)) {
+        return URL;
+      }
+      return HTML;
+    }
+  }
 
-  /**
-   * @return The HTML content for the default view of this gadget spec.
-   * @throws IllegalStateException if contentType is not HTML.
-   */
-  public String getContentData();
+  public static interface View {
+    /**
+     * @return Content type for the view, either HTML or URL
+     */
+    public ContentType getType();
+    /**
+     * Must be a URI type gadget.
+     *
+     * @return The URI for this gadget spec.
+     * @throws IllegalStateException if contentType is not URI.
+     */
+    public URI getHref();
+    /**
+     * @return The HTML content for the default view of this gadget spec.
+     * @throws IllegalStateException if contentType is not HTML.
+     */
+    public String getData();
+    /**
+     * @return Whether to use quirks or standards mode.
+     *         If standards mode, then add appropriate DOCTYPE.
+     */
+    public boolean getQuirks();
+  }
 
   /**
    * @param view Identifier of the desired view to retrieve.
-   * @return The HTML content for the specified view of this gadget spec,
+   * @return The View for the specified view of this gadget spec,
    *         or null if no such view was defined.
-   * @throws IllegalStateException if contentType is not HTML.
    */
-  public String getContentData(String view);
+  public View getView(String view);
 
   /**
    * @return A copy of the spec. This is NOT the same as clone().
    */
   public GadgetSpec copy();
+
+  /**
+   * @return Info string for all views on attributes/contents
+   */
+  public String toString();
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java Tue Feb 26 18:36:16 2008
@@ -18,9 +18,9 @@
 package org.apache.shindig.gadgets;
 
 import org.apache.shindig.util.Check;
+import org.apache.shindig.util.XmlUtil;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
@@ -121,24 +121,21 @@
                                   Node prefs,
                                   ParsedGadgetSpec spec)
       throws SpecParserException {
-    NamedNodeMap attrs = prefs.getAttributes();
-
-    Node title = attrs.getNamedItem("title");
+    String title = XmlUtil.getAttribute(prefs, "title");
     if (null == title) {
       throw new SpecParserException("Missing \"title\" attribute.");
     }
-    spec.title = title.getNodeValue();
-
-    spec.titleUrl = getUriAttributeOrNull(attrs, "title_url");
-    spec.description = getAttributeOrNull(attrs, "description");
-    spec.directoryTitle = getAttributeOrNull(attrs, "directory_title");
-    spec.author = getAttributeOrNull(attrs, "author");
-    spec.authorEmail = getAttributeOrNull(attrs, "author_email");
-    spec.thumbnail = getUriAttributeOrNull(attrs, "thumbnail");
-    spec.screenshot = getUriAttributeOrNull(attrs, "screenshot");
+    spec.title = title;
+    spec.titleUrl = XmlUtil.getUriAttribute(prefs, "title_url");
+    spec.description = XmlUtil.getAttribute(prefs, "description");
+    spec.directoryTitle = XmlUtil.getAttribute(prefs, "directory_title");
+    spec.author = XmlUtil.getAttribute(prefs, "author");
+    spec.authorEmail = XmlUtil.getAttribute(prefs, "author_email");
+    spec.thumbnail = XmlUtil.getUriAttribute(prefs, "thumbnail");
+    spec.screenshot = XmlUtil.getUriAttribute(prefs, "screenshot");
 
     for (String attrName : CATEGORY_ATTRS) {
-      String attr = getAttributeOrNull(attrs, attrName);
+      String attr = XmlUtil.getAttribute(prefs, attrName);
       if (attr != null) {
         spec.categories.add(attr);
       }
@@ -147,7 +144,7 @@
     NodeList children = prefs.getChildNodes();
     for (int i = 0, j = children.getLength(); i < j; ++i) {
       Node child = children.item(i);
-      if (child.getNodeName().equals("Locale")) {
+      if ("Locale".equals(child.getNodeName())) {
         spec.localeSpecs.add(processLocale(children.item(i), id.getURI()));
       }
     }
@@ -155,30 +152,6 @@
     // TODO: Icon parsing
   }
 
-  private String getAttributeOrNull(NamedNodeMap attrs, String attrName) {
-    Node attr = attrs.getNamedItem(attrName);
-    if (null != attr) {
-      return attr.getNodeValue();
-    }
-    return null;
-  }
-
-  private URI getUriAttributeOrNull(NamedNodeMap attrs, String attrName)
-      throws SpecParserException {
-    Node attr = attrs.getNamedItem(attrName);
-    if (null != attr) {
-      try {
-        return new URI(attr.getNodeValue());
-      } catch (URISyntaxException e) {
-        // TODO: This is really not a great way to ensure valid URL's. All
-        // kinds of invalid URL's are parsed as valid URI's.
-        throw new SpecParserException(
-            "Malformed \"" + attrName + "\": " + attr.getNodeValue());
-      }
-    }
-    return null;
-  }
-
   /**
    * Processes the &lt;Locale&gt; section of the spec.
    *
@@ -188,35 +161,15 @@
    * @throws SpecParserException If a malformed message bundle URI is found
    */
   private ParsedGadgetSpec.ParsedMessageBundle processLocale(
-      Node locale,
-      URI baseUrl) throws SpecParserException {
-    NamedNodeMap attrs = locale.getAttributes();
-    Node messagesAttr = attrs.getNamedItem("messages");
-    Node languageAttr = attrs.getNamedItem("lang");
-    Node countryAttr = attrs.getNamedItem("country");
-    Node rtlAttr = attrs.getNamedItem("language_direction");
-
-    String messages = null;
-    if (null != messagesAttr) {
-      messages = messagesAttr.getNodeValue();
-    }
-
-    String country;
-    if (null == countryAttr) {
-      country = "all";
-    } else {
-      country = countryAttr.getNodeValue();
-    }
-
-    String language;
-    if (null == languageAttr) {
-      language = "all";
-    } else {
-      language = languageAttr.getNodeValue();
-    }
+      Node locale, URI baseUrl) throws SpecParserException {
+    String messages = XmlUtil.getAttribute(locale, "messages");
+    String country = XmlUtil.getAttribute(locale, "country", "all");
+    String language = XmlUtil.getAttribute(locale, "lang", "all");
 
+    String direction
+        = XmlUtil.getAttribute(locale, "language_direction", "ltr");
     boolean rightToLeft = false;
-    if (rtlAttr != null && "rtl".equals(rtlAttr.getTextContent())) {
+    if ("rtl".equals(direction)) {
       rightToLeft = true;
     }
 
@@ -246,48 +199,28 @@
   private ParsedGadgetSpec.ParsedUserPref processUserPref(Node pref)
       throws SpecParserException {
     ParsedGadgetSpec.ParsedUserPref up = new ParsedGadgetSpec.ParsedUserPref();
-    NamedNodeMap attrs = pref.getAttributes();
-    Node name = attrs.getNamedItem("name");
-    if (null == name) {
-      throw new SpecParserException("All UserPrefs must have name attributes.");
-    }
-    up.name = name.getNodeValue();
-
-    Node displayName = attrs.getNamedItem("display_name");
-    if (null != displayName) {
-      up.displayName = displayName.getNodeValue();
-    }
 
-    Node dataType = attrs.getNamedItem("datatype");
-    if (null == dataType) {
-      up.dataType = GadgetSpec.UserPref.DataType.STRING;
-    } else {
-      up.dataType =
-          ParsedGadgetSpec.ParsedUserPref.parse(dataType.getNodeValue());
+    String name = XmlUtil.getAttribute(pref, "name");
+    if (name == null) {
+      throw new SpecParserException("All UserPrefs must have name attributes.");
     }
+    up.name = name;
 
-    Node defaultValue = attrs.getNamedItem("default_value");
-    if (null != defaultValue) {
-      up.defaultValue = defaultValue.getNodeValue();
-    }
+    up.displayName = XmlUtil.getAttribute(pref, "display_name");
+    up.dataType = ParsedGadgetSpec.ParsedUserPref.parse(
+        XmlUtil.getAttribute(pref, "datatype"));
+    up.defaultValue = XmlUtil.getAttribute(pref, "default_value");
 
     // Check for enum types.
     up.enumValues = new HashMap<String, String>();
     NodeList children = pref.getChildNodes();
     for (int i = 0, j = children.getLength(); i < j; ++i) {
       Node child = children.item(i);
-      if (child.getNodeName().equals("EnumValue")) {
-        NamedNodeMap childAttrs = child.getAttributes();
-
-        // Must have both name and value.
-        Node value = childAttrs.getNamedItem("value");
-        Node displayValue = childAttrs.getNamedItem("display_value");
+      if ("EnumValue".equals(child.getNodeName())) {
+        String value = XmlUtil.getAttribute(child, "value");
         if (value != null) {
-          String valueText = value.getTextContent();
-          String displayText = displayValue == null
-              ? valueText
-              : displayValue.getTextContent();
-          up.enumValues.put(valueText, displayText);
+          up.enumValues.put(value,
+              XmlUtil.getAttribute(child, "display_value", value));
         }
       }
     }
@@ -302,37 +235,14 @@
    */
   private void processContent(ParsedGadgetSpec spec, Node content)
       throws SpecParserException {
-    NamedNodeMap attrs = content.getAttributes();
-    Node type = attrs.getNamedItem("type");
-    if (null == type) {
-      throw new SpecParserException("No content type specified!");
-    } else if ("url".equals(type.getNodeValue())) {
-      spec.contentType = GadgetSpec.ContentType.URL;
-      Node href = attrs.getNamedItem("href");
-      if (href != null) {
-        try {
-          spec.contentHref = new URI(href.getNodeValue());
-        } catch (URISyntaxException e) {
-          throw new SpecParserException("Malformed <Content> href value");
-        }
-      }
-    } else {
-      spec.contentType = GadgetSpec.ContentType.HTML;
-      Node viewNode = attrs.getNamedItem("view");
-      String viewStr = (viewNode == null) ? "" : viewNode.getNodeValue();
-      String[] views = viewStr.split(",");
-      Node child = content.getFirstChild();
-      String contentData = content.getTextContent();
-      if (contentData.length() > 0) {
-        for (String view : views) {
-          spec.addContent(view, contentData);
-        }
-      } else {
-        throw new SpecParserException("Empty or malformed <Content> section!");
-      }
+    String[] viewNames
+        = XmlUtil.getAttribute(content, "view", "default").split(",");
+    for (String viewName : viewNames) {
+      spec.addContent(viewName.trim(), content);
     }
   }
 
+
   /**
    * Processes &ltlOptional&gt; and &lt;Require&gt; tags.
    *
@@ -345,9 +255,8 @@
                               Node feature,
                               boolean required)
       throws SpecParserException {
-    NamedNodeMap attrs = feature.getAttributes();
-    Node name = attrs.getNamedItem("feature");
-    if (name == null || name.getNodeValue().length() == 0) {
+    String name = XmlUtil.getAttribute(feature, "feature");
+    if (name == null) {
       throw new SpecParserException(
           "Feature not specified in <" +
           (required ? "Required" : "Optional") +
@@ -358,10 +267,9 @@
       for (int i = 0, j = children.getLength(); i < j; ++i) {
         Node child = children.item(i);
         if ("Param".equals(child.getNodeName())) {
-          NamedNodeMap paramAttrs = child.getAttributes();
-          Node paramName = paramAttrs.getNamedItem("name");
-          if (paramName != null) {
-            params.put(paramName.getNodeValue(), child.getTextContent());
+          String param = XmlUtil.getAttribute(child, "name");
+          if (param != null) {
+            params.put(param, child.getTextContent());
           } else {
             throw new SpecParserException("Missing name attribute in <Param>.");
           }
@@ -369,7 +277,7 @@
       }
       ParsedGadgetSpec.ParsedFeatureSpec featureSpec =
         new ParsedGadgetSpec.ParsedFeatureSpec();
-      featureSpec.name = name.getNodeValue();
+      featureSpec.name = name;
       featureSpec.optional = !required;
       featureSpec.params = params;
       spec.requires.put(featureSpec.name, featureSpec);
@@ -384,10 +292,8 @@
     private String authorEmail;
     private String description;
     private String directoryTitle;
-    private ContentType contentType;
-    private URI contentHref;
-    private Map<String, StringBuilder> contentData
-        = new HashMap<String, StringBuilder>();
+    private Map<String, ParsedView> views
+        = new HashMap<String, ParsedView>();
     private List<Icon> icons = new ArrayList<Icon>();
     private List<LocaleSpec> localeSpecs = new ArrayList<LocaleSpec>();
     private List<String> preloads = new ArrayList<String>();
@@ -407,9 +313,7 @@
       spec.authorEmail = authorEmail;
       spec.description = description;
       spec.directoryTitle = directoryTitle;
-      spec.contentType = contentType;
-      spec.contentHref = contentHref;
-      spec.contentData = new HashMap<String, StringBuilder>(contentData);
+      spec.views = new HashMap<String, ParsedView>(views);
       spec.icons = new ArrayList<Icon>(icons);
       spec.localeSpecs = new ArrayList<LocaleSpec>(localeSpecs);
       spec.preloads = new ArrayList<String>(preloads);
@@ -571,56 +475,135 @@
       return userPrefs;
     }
 
-    public List<String> getCategories() {
-      return categories;
-    }
-
-    public ContentType getContentType() {
-      return contentType;
-    }
-
-    public URI getContentHref() {
-      Check.is(contentType == ContentType.URL,
-               "getContentHref() requires contentType URL");
-      return contentHref;
-    }
 
-    public String getContentData() {
-      return getContentData(DEFAULT_VIEW);
-    }
+    private static class ParsedView implements View {
+      private static final String QUIRKS_ATTR_NAME = "quirks";
+      private static final String TYPE_ATTR_NAME = "type";
+      private static final String HREF_ATTR_NAME = "href";
+      private URI href = null;
+      private ContentType type = null;
+      private boolean quirks = true;
+      private final StringBuilder builder = new StringBuilder();
+
+      /**
+       * Appends to an existing view.
+       * @param node
+       * @throws SpecParserException
+       */
+      public void append(Node node)
+          throws SpecParserException {
+        String newType = XmlUtil.getAttribute(node, TYPE_ATTR_NAME);
+        if (newType != null) {
+          ContentType contentType = ContentType.parse(newType);
+          if (type != null && !type.equals(contentType)) {
+            throw new SpecParserException(
+                GadgetException.Code.INVALID_PARAMETER,
+                "Can't mix content types for the same view.");
+          }
+          type = contentType;
+        }
+        String quirkAttr = XmlUtil.getAttribute(node, QUIRKS_ATTR_NAME);
+        if (quirkAttr != null) {
+          if ("false".equals(quirkAttr)) {
+            quirks = false;
+          } else {
+            quirks = true;
+          }
+        }
+        href = XmlUtil.getUriAttribute(node, HREF_ATTR_NAME, href);
+        if (ContentType.URL.equals(type) && href == null) {
+          throw new SpecParserException(GadgetException.Code.INVALID_PARAMETER,
+              "href attribute is required for type=url gadgets. " +
+              "It is either missing or malformed");
+        } else if (ContentType.HTML.equals(type) && href != null) {
+          throw new SpecParserException(GadgetException.Code.INVALID_PARAMETER,
+              "href attribute is not allowed for type=html gadgets.");
+        }
+        builder.append(node.getTextContent());
+      }
 
-    public String getContentData(String view) {
-      Check.is(contentType == ContentType.HTML,
-               "getContentData() requires contentType HTML");
-      if (view == null || view.length() == 0) {
-        view = DEFAULT_VIEW;
+      public ContentType getType() {
+        return type;
       }
 
-      StringBuilder content = contentData.get(view);
-      if (content == null) {
-        content = contentData.get(DEFAULT_VIEW);
+      /**
+       * Must be a URI type gadget.
+       *
+       * @return The URI for this gadget spec.
+       * @throws IllegalStateException if contentType is not URI.
+       */
+      public URI getHref() {
+        Check.eq(type, ContentType.URL,
+            "Attempted to get href of a non-url type gadget!");
+        return href;
+      }
+
+      /**
+       * @return The HTML content for the default view of this gadget spec.
+       * @throws IllegalStateException if contentType is not HTML.
+       */
+      public String getData() {
+        return builder.toString();
+      }
+
+      public boolean getQuirks() {
+        return quirks;
+      }
+
+      @Override
+      public String toString() {
+        return String.format(
+            "<Content quirks=\"%s\" href=\"%s\"><![CDATA[%s]]></Content>",
+            quirks ? "true" : "false",
+            href == null ? "" : href.toString(),
+            builder.toString());
       }
+    }
 
-      if (content == null) {
-        return "";
+    @Override
+    public String toString() {
+      StringBuilder buf = new StringBuilder();
+      buf.append("\nGadget Spec Debug String: ")
+         .append(views.size())
+         .append(" views");
+      for (Map.Entry<String, ParsedView> entry : views.entrySet()) {
+        buf.append("\nView = ")
+           .append(entry.getKey())
+           .append("\n")
+           .append(entry.getValue().toString());
+      }
+      return buf.toString();
+    }
+
+    public View getView(String viewName) {
+      if (viewName == null || viewName.length() == 0) {
+        viewName = DEFAULT_VIEW;
       }
+      return views.get(viewName);
+    }
 
-      return content.toString();
+    public List<String> getCategories() {
+      return categories;
     }
 
-    // TODO: Synchronizing this seems unnecessary...a parse job should never
-    // happen across threads, and addContent *can't* be called anywhere but
-    // within a call to GadgetSpecParser.parse()
-    public synchronized void addContent(String view, String content) {
+    /**
+     * Adds content to a view. Creates the view if it doesn't exist, and
+     * appends if it does.
+     *
+     * @param view
+     * @param node
+     * @throws SpecParserException
+     */
+    void addContent(String view, Node node)
+        throws SpecParserException {
       if (view == null || view.length() == 0) {
         view = DEFAULT_VIEW;
       }
 
-      if (!contentData.containsKey(view)) {
-        contentData.put(view, new StringBuilder());
+      if (!views.containsKey(view)) {
+        views.put(view, new ParsedView());
       }
-
-      contentData.get(view).append(content);
+      views.get(view).append(node);
     }
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ProcessingOptions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ProcessingOptions.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ProcessingOptions.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ProcessingOptions.java Tue Feb 26 18:36:16 2008
@@ -59,4 +59,11 @@
   public boolean getDebug() {
     return true;
   }
+
+  /**
+   * @return Name of view to show
+   */
+  public String getView() {
+    return null;
+  }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java Tue Feb 26 18:36:16 2008
@@ -31,4 +31,8 @@
   public SpecParserException(GadgetException.Code code) {
     super(code);
   }
+
+  public SpecParserException(GadgetException.Code code, String message) {
+    super(code, message);
+  }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java Tue Feb 26 18:36:16 2008
@@ -101,7 +101,7 @@
    * context object). A better choice would probably be to add the view params
    * to ProcessingOptions and pass that here.
    */
-  public abstract String getIframeUrl(Gadget gadget,  ProcessingOptions opts);
+  public abstract String getIframeUrl(Gadget gadget, ProcessingOptions opts);
 
   /**
    * Initializes this handler using the provided implementation.

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java Tue Feb 26 18:36:16 2008
@@ -87,10 +87,10 @@
     // We don't have any meaningful data in the current request anyway, so
     // we'll just do this statically.
     StringBuilder buf = new StringBuilder();
-
     try {
       String url = gadget.getId().getURI().toString();
-      if (gadget.getContentType().equals(GadgetSpec.ContentType.HTML)) {
+      GadgetSpec.View view = gadget.getView(opts.getView());
+      if (view.getType().equals(GadgetSpec.ContentType.HTML)) {
         buf.append(iframePath)
            .append("url=")
            .append(URLEncoder.encode(url, "UTF-8"))

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java Tue Feb 26 18:36:16 2008
@@ -30,6 +30,7 @@
 import org.apache.shindig.gadgets.SyndicatorConfig;
 import org.apache.shindig.gadgets.UserPrefs;
 import org.apache.shindig.gadgets.GadgetFeatureRegistry.Entry;
+import org.apache.shindig.gadgets.GadgetSpec.View;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -75,7 +76,6 @@
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp)
       throws IOException {
-
     String urlParam = req.getParameter("url");
     if (urlParam == null) {
       resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
@@ -131,7 +131,7 @@
       gadget = servletState.getGadgetServer().processGadget(gadgetId,
           getPrefsFromRequest(req), context.getLocale(),
           RenderingContext.GADGET, options);
-      outputGadget(gadget, view, options, contentFilters, resp);
+      outputGadget(gadget, options, contentFilters, resp);
     } catch (GadgetServer.GadgetProcessException e) {
       outputErrors(e, resp);
     }
@@ -141,7 +141,6 @@
    * Renders a successfully processed gadget.
    *
    * @param gadget
-   * @param view
    * @param options
    * @param contentFilters
    * @param resp
@@ -149,18 +148,30 @@
    * @throws GadgetServer.GadgetProcessException
    */
   private void outputGadget(Gadget gadget,
-                            String view,
                             ProcessingOptions options,
                             List<GadgetContentFilter> contentFilters,
                             HttpServletResponse resp)
       throws IOException, GadgetServer.GadgetProcessException {
-    switch(gadget.getContentType()) {
-    case HTML:
-      outputHtmlGadget(gadget, view, options, contentFilters, resp);
-      break;
-    case URL:
-      outputUrlGadget(gadget, options, resp);
-      break;
+    View view = gadget.getView(options.getView());
+    if (view == null) {
+      String viewName = options.getView();
+      if (viewName != null) {
+        throw new GadgetServer.GadgetProcessException(
+            GadgetException.Code.INVALID_PARAMETER,
+            "Requested view '" + viewName + "' does not exist in this gadget.");
+      } else {
+        throw new GadgetServer.GadgetProcessException(
+            GadgetException.Code.MISSING_PARAMETER,
+            "View must be specified as Gadget does not have default view.");
+      }
+    }
+    switch(view.getType()) {
+      case HTML:
+        outputHtmlGadget(gadget, options, contentFilters, resp);
+        break;
+      case URL:
+        outputUrlGadget(gadget, options, resp);
+        break;
     }
   }
 
@@ -176,26 +187,41 @@
    * @throws GadgetServer.GadgetProcessException
    */
   private void outputHtmlGadget(Gadget gadget,
-                                String view,
                                 ProcessingOptions options,
                                 List<GadgetContentFilter> contentFilters,
                                 HttpServletResponse resp)
       throws IOException, GadgetServer.GadgetProcessException {
     resp.setContentType("text/html; charset=UTF-8");
-
     StringBuilder markup = new StringBuilder();
-    markup.append("<html><head>");
+    View view = gadget.getView(options.getView());
+
+    // use single character for tab to simulate indentation within source code
+    String t = "  ";
+    String n = "\n";
+    if (!options.getDebug()) {
+      t = "";
+      n = "";
+    }
+
+    if (!view.getQuirks())
+      markup.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"+n);
+
     // TODO: This is so wrong.
-    markup.append("<style type=\"text/css\">" +
-                  "body,td,div,span,p{font-family:arial,sans-serif;}" +
-                  "a {color:#0000cc;}a:visited {color:#551a8b;}" +
-                  "a:active {color:#ff0000;}" +
-                  "body{margin: 0px;padding: 0px;background-color:white;}" +
-                  "</style>");
-    markup.append("</head><body>");
+    markup.append(
+        "<html>"+n+
+        t+"<head>"+n+
+        t+t+"<style type=\"text/css\">"+n+
+        t+t+t+"body,td,div,span,p{font-family:arial,sans-serif;}"+n+
+        t+t+t+"a {color:#0000cc;}a:visited {color:#551a8b;}"+n+
+        t+t+t+"a:active {color:#ff0000;}"+n+
+        t+t+t+"body{margin: 0px;padding: 0px;background-color:white;}"+n+
+        t+t+"</style>"+n+
+        t+"</head>"+n+
+        t+"<body>"+n);
     StringBuilder externJs = new StringBuilder();
     StringBuilder inlineJs = new StringBuilder();
-    String externFmt = "<script src=\"%s\"></script>\n";
+    String externFmt =
+        t+t+"<script src=\"%s\"></script>"+n;
     String forcedLibs = options.getForcedJsLibs();
 
     for (JsLibrary library : gadget.getJsLibraries()) {
@@ -237,13 +263,13 @@
     }
 
     List<GadgetException> gadgetExceptions = new LinkedList<GadgetException>();
-    String content = gadget.getContentData(view);
+    String content = view.getData();
     if (content == null) {
       // unknown view
       gadgetExceptions.add(
           new GadgetException(
               GadgetException.Code.UNKNOWN_VIEW_SPECIFIED,
-              "View: '" + view + "' invalid for gadget: " +
+              "View: '" + options.getView() + "' invalid for gadget: " +
               gadget.getId().getKey()));
     } else {
       for (GadgetContentFilter filter : contentFilters) {
@@ -260,18 +286,21 @@
     }
 
     markup.append(content);
-    markup.append("<script>gadgets.util.runOnLoadHandlers();</script>");
-    markup.append("</body></html>");
+    markup.append(
+        t+t+"<script>gadgets.util.runOnLoadHandlers();</script>"+n);
+    markup.append(
+        t+"</body>"+n+
+        "</html>");
 
     resp.getWriter().print(markup.toString());
   }
 
-  private void outputUrlGadget(Gadget gadget,
-      ProcessingOptions options, HttpServletResponse resp) throws IOException {
+  private void outputUrlGadget(Gadget gadget,  ProcessingOptions options,
+      HttpServletResponse resp) throws IOException {
     // TODO: generalize this as injectedArgs on Gadget object
 
     // Preserve existing query string parameters.
-    URI redirURI = gadget.getContentHref();
+    URI redirURI = gadget.getView(options.getView()).getHref();
     String queryStr = redirURI.getQuery();
     StringBuilder query = new StringBuilder(queryStr == null ? "" : queryStr);
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpProcessingOptions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpProcessingOptions.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpProcessingOptions.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpProcessingOptions.java Tue Feb 26 18:36:16 2008
@@ -31,12 +31,14 @@
   private final String forceJsLibs;
   private final String syndicator;
   private final boolean debug;
+  private final String view;
 
   public HttpProcessingOptions(HttpServletRequest req) {
     ignoreCache = getIgnoreCache(req);
     forceJsLibs = getForceJsLibs(req);
     syndicator = getSyndicator(req);
     debug = getDebug(req);
+    view = getView(req);
   }
 
   /** {@inheritDoc} */
@@ -66,6 +68,12 @@
     return debug;
   }
 
+  /** {@inheritDoc} */
+  @Override
+  public String getView() {
+    return view;
+  }
+
   /**
    * @param req
    * @return Whether or not to ignore the cache.
@@ -75,7 +83,11 @@
     if (noCacheParam == null) {
       noCacheParam = req.getParameter("bpc");
     }
-    return "1".equals(noCacheParam);
+    // interpret empty value string as true
+    if (noCacheParam == null || noCacheParam.equals("0")) {
+      return false;
+    }
+    return true;
   }
 
   /**
@@ -99,6 +111,18 @@
    * @return True if the debug parameter is set.
    */
   protected static boolean getDebug(HttpServletRequest req) {
-    return "1".equals(req.getParameter("debug"));
+    String value = req.getParameter("debug");
+    if (value == null || value.equals("0")) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * @param req
+   * @return Name of view to use
+   */
+  protected static String getView(HttpServletRequest req) {
+    return req.getParameter("view");
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcProcessingOptions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcProcessingOptions.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcProcessingOptions.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcProcessingOptions.java Tue Feb 26 18:36:16 2008
@@ -46,4 +46,10 @@
   public JsonRpcProcessingOptions(JsonRpcContext context) {
     this.context = context;
   }
+
+  /** {@inheritDoc} */
+  @Override
+  public String getView() {
+    return context.getView();
+  }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java Tue Feb 26 18:36:16 2008
@@ -78,7 +78,8 @@
                   .put("moduleId", outGadget.getId().getModuleId())
                   .put("title", outGadget.getTitle())
                   .put("contentType",
-                      outGadget.getContentType().toString().toLowerCase());
+                      outGadget.getView(options.getView()).getType()
+                      .toString().toLowerCase());
 
         // Features.
         Set<String> feats = outGadget.getRequires().keySet();

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/XmlUtil.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/XmlUtil.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/XmlUtil.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/XmlUtil.java Tue Feb 26 18:36:16 2008
@@ -21,6 +21,9 @@
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 public class XmlUtil {
 
   /**
@@ -47,5 +50,34 @@
    */
   public static String getAttribute(Node node, String attr) {
     return getAttribute(node, attr, null);
+  }
+
+  /**
+   * Retrieves an attribute as a URI.
+   * @param node
+   * @param attr
+   * @return The parsed uri, or def if the attribute doesn't exist or can not
+   *     be parsed as a URI.
+   */
+  public static URI getUriAttribute(Node node, String attr, URI def) {
+    String uri = getAttribute(node, attr);
+    if (uri != null) {
+      try {
+        return new URI(uri);
+      } catch (URISyntaxException e) {
+        return def;
+      }
+    }
+    return def;
+  }
+
+  /**
+   * Retrieves an attribute as a URI.
+   * @param node
+   * @param attr
+   * @return The parsed uri, or null.
+   */
+  public static URI getUriAttribute(Node node, String attr) {
+    return getUriAttribute(node, attr, null);
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java Tue Feb 26 18:36:16 2008
@@ -112,7 +112,7 @@
     Gadget gadget = gadgetServer.processGadget(DATETIME_ID, UserPrefs.EMPTY, EN_US_LOCALE,
                                                RenderingContext.GADGET, options);
     assertEquals("Hello, World!", gadget.getTitle());
-    assertEquals("Goodbye, World!", gadget.getContentData());
+    assertEquals("Goodbye, World!", gadget.getView(null).getData());
     verify();
   }
 

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecParserTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecParserTest.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecParserTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecParserTest.java Tue Feb 26 18:36:16 2008
@@ -24,64 +24,117 @@
 import java.util.List;
 import java.util.Map;
 
+
 public class GadgetSpecParserTest extends TestCase {
 
   private static final GadgetSpecParser parser = new GadgetSpecParser();
+  private BasicGadgetId basicId;
+
+  @Override
+  public void setUp() throws Exception {
+    basicId = new BasicGadgetId(1, "http://example.org/text.xml");
+  }
 
   private static class BasicGadgetId implements GadgetView.ID {
-    URI uri;
-    public URI getURI() {
-      return uri;
+    BasicGadgetId(int moduleId, String uri) throws Exception {
+      this.moduleId = moduleId;
+      this.uri = new URI(uri);
     }
-    int moduleId;
     public int getModuleId() {
       return moduleId;
     }
+    public URI getURI() {
+      return uri;
+    }
     public String getKey() {
       return uri.toString();
     }
-  }
-
-  private GadgetSpec parse(String specXml) throws Exception {
-    BasicGadgetId id = new BasicGadgetId();
-    id.uri = new URI("http://example.org/text.xml");
-    id.moduleId = 1;
-    return parser.parse(id, specXml);
-  }
 
-  private void assertParseException(String specXml) throws Exception {
-    try {
-      GadgetSpec spec = parse(specXml);
-      fail();
-    } catch (SpecParserException ex) {
-      // expected
-    }
+    private URI uri;
+    private int moduleId;
   }
 
-  public void testBasicGadget() throws Exception {
-    String xml = "<?xml version=\"1.0\"?>" +
-                 "<Module>" +
-                 "<ModulePrefs title=\"Hello, world!\"/>" +
-                 "<Content type=\"html\">Hello!</Content>" +
-                 "</Module>";
-    GadgetSpec spec = parse(xml);
-
-    assertEquals("Hello!", spec.getContentData());
-    assertEquals("Hello, world!", spec.getTitle());
+  // METADATA
+  public void testParsedMetadata() throws Exception {
+    String nonCanonicalMetadataXml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs" +
+        "    title=\"TITLE\"" +
+        "    title_url=\"TITLE_URL\"" +
+        "    description=\"DESCRIPTION\"" +
+        "    author=\"AUTHOR_NAME\"" +
+        "    author_email=\"author@example.org\"" +
+        "    screenshot=\"http://www.example.org/screenshot.png\"" +
+        "    thumbnail=\"http://www.example.org/thumbnail.png\"" +
+        "  />" +
+        "  <Content type=\"html\"></Content>" +
+        "</Module>";
+    GadgetSpec spec = parser.parse(basicId, nonCanonicalMetadataXml);
+    assertEquals("TITLE", spec.getTitle());
+    assertEquals("TITLE_URL", spec.getTitleURI().toString());
+    assertEquals("DESCRIPTION", spec.getDescription());
+    assertEquals("AUTHOR_NAME", spec.getAuthor());
+    assertEquals("author@example.org", spec.getAuthorEmail());
+    assertEquals("http://www.example.org/screenshot.png",
+                 spec.getScreenshot().toString());
+    assertEquals("http://www.example.org/thumbnail.png",
+                 spec.getThumbnail().toString());
+  }
+  public void testExtendedMetadata() throws Exception {
+    String nonCanonicalMetadataXml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs" +
+        "    title=\"TITLE\"" + // parsing fails without this
+        "    author_affiliation=\"AUTHOR_AFFILIATION\"" +
+        "    author_location=\"AUTHOR_LOCATION\"" +
+        "    author_photo=\"www.example.org/author_photo.png\"" +
+        "    author_aboutme=\"AUTHOR_ABOUTME\"" +
+        "    author_quote=\"AUTHOR_QUOTE\"" +
+        "    author_link=\"www.example.org/author_aboutme.html\"" +
+        "    show_stats=\"true\"" +
+        "    show_in_directory=\"true\"" +
+        "    width=\"200\"" +
+        "    height=\"300\"" +
+        "    category=\"CATEGORY\"" +
+        "    category2=\"CATEGORY2\"" +
+        "    singleton=\"SINGLETON\"" +
+        "    render_inline=\"RENDER_INLINE\"" +
+        "    scaling=\"SCALING\"" +
+        "    scrolling=\"SCROLLING\"" +
+        "  />" +
+        "  <Content type=\"html\"></Content>" +
+        "</Module>";
+    // Just check that it doesn't cause a parse error
+    // As these values are supported in shindig, add them to testParsedMetadata
+    parser.parse(basicId, nonCanonicalMetadataXml);
+  }
+
+  public void testUnknownAttributesOK() throws Exception {
+    String unknownAttributesXml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs unknownmoduleprefsattr=\"UNKNOWN\" title=\"TITLE\"/>" +
+        "  <Content type=\"html\" unknowncontentattr=\"UNKNOWN\"/>" +
+        "</Module>";
+    parser.parse(basicId, unknownAttributesXml);
   }
 
+  // USERPREFS
   public void testEnumParsing() throws Exception {
-    String xml = "<?xml version=\"1.0\"?>" +
-                 "<Module>" +
-                 "<ModulePrefs title=\"Test Enum\">" +
-                 "<UserPref name=\"test\" datatype=\"enum\">" +
-                 "<EnumValue value=\"0\" display_value=\"Zero\"/>" +
-                 "<EnumValue value=\"1\" display_value=\"One\"/>" +
-                 "</UserPref>" +
-                 "</ModulePrefs>" +
-                 "<Content type=\"html\">Hello!</Content>" +
-                 "</Module>";
-    GadgetSpec spec = parse(xml);
+    String xml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs title=\"Test Enum\">" +
+        "    <UserPref name=\"test\" datatype=\"enum\">" +
+        "      <EnumValue value=\"0\" display_value=\"Zero\"/>" +
+        "      <EnumValue value=\"1\" display_value=\"One\"/>" +
+        "    </UserPref>" +
+        "  </ModulePrefs>" +
+        "  <Content type=\"html\">Hello!</Content>" +
+        "</Module>";
+    GadgetSpec spec = parser.parse(basicId, xml);
 
     List<GadgetSpec.UserPref> prefs = spec.getUserPrefs();
 
@@ -97,38 +150,113 @@
     assertEquals("One", enumValues.get("1"));
   }
 
-  public void testNonCanonicalMetadata() throws Exception {
-    String desc = "World greetings";
-    String dirTitle = "Dir title";
-    String cat = "hello";
-    String cat2 = "hello2";
-    String thumb = "http://foo.com/bar.xml";
-    String xml = "<?xml version=\"1.0\"?>" +
-                 "<Module>" +
-                 "<ModulePrefs title=\"Hello, world!\"" +
-                 " description=\"" + desc + "\"" +
-                 " directory_title=\"" + dirTitle + "\"" +
-                 " thumbnail=\"" + thumb + "\"" +
-                 " category=\"" + cat + "\" category2=\"" + cat2 + "\"/>" +
-                 "<Content type=\"html\">Hello!</Content>" +
-                 "</Module>";
-    GadgetSpec spec = parse(xml);
-
-    assertEquals(desc, spec.getDescription());
-    assertEquals(dirTitle, spec.getDirectoryTitle());
-    assertEquals(thumb, spec.getThumbnail().toString());
-    assertEquals(2, spec.getCategories().size());
-    assertEquals(cat, spec.getCategories().get(0));
-    assertEquals(cat2, spec.getCategories().get(1));
-  }
-
-  public void testIllegalUris() throws Exception {
-    String xml = "<?xml version=\"1.0\"?>" +
-                 "<Module>" +
-                 "<ModulePrefs title=\"Hello, world!\"" +
-                 " thumbnail=\"foo.com#bar#png\"/>" +
-                 "<Content type=\"html\">Hello!</Content>" +
-                 "</Module>";
-    assertParseException(xml);
+  // TYPE = URL
+  public void testTypeUrlParsing() throws Exception {
+    GadgetSpec spec = parser.parse(basicId,
+        attrs1Xml("type=\"url\" href=\"http://www.example.org\""));
+    assertEquals(spec.getView(null).getType(), Gadget.ContentType.URL);
+    assertEquals(spec.getView(null).getHref().toString(),
+        "http://www.example.org");
+  }
+  public void testTypeUrlHrefInvalid() throws Exception {
+    assertParseException(basicId, attrs1Xml("type=\"url\" href=\":INVALID_URL\""),
+        GadgetException.Code.INVALID_PARAMETER);
+  }
+
+  // TYPE = HTML
+  public void testTypeHtmlParsing() throws Exception {
+    final String typeHtmlXml =
+        "<Module>" +
+        "  <ModulePrefs title=\"Title\"/>" +
+        "  <Content type=\"html\">CONTENTS</Content>" +
+        "</Module>";
+    GadgetSpec spec = parser.parse(basicId, typeHtmlXml);
+    assertEquals(Gadget.ContentType.HTML, spec.getView(null).getType());
+    assertEquals("CONTENTS", spec.getView(null).getData());
+  }
+  public void testTypeHtmlWithHrefAttr() throws Exception {
+    assertParseException(basicId, attrs1Xml("type=\"html\" href=\"www.example.org\""),
+        GadgetException.Code.INVALID_PARAMETER);
+  }
+
+  // MULTIPLE VIEWS
+  public void testMultipleViews() throws Exception {
+    System.out.println(junit.runner.Version.id());
+
+    final String multipleViewsXml =
+        "<Module>" +
+        "  <ModulePrefs title=\"Title\"/>" +
+        "  <Content type=\"html\" view=\"mobile\">mobileCSS</Content>" +
+        "  <Content type=\"html\" view=\"profile\">profileCSS</Content>" +
+        "  <Content type=\"html\" view=\"mobile,profile\">+sharedHTML</Content>" +
+        "  <Content type=\"url\" view=\"maximized\" href=\"http://www.example.org\"></Content>" +
+        "</Module>";
+    GadgetSpec spec = parser.parse(basicId, multipleViewsXml);
+    assertEquals(Gadget.ContentType.HTML, spec.getView("mobile").getType());
+    assertEquals("mobileCSS+sharedHTML", spec.getView("mobile").getData());
+    assertEquals(Gadget.ContentType.HTML, spec.getView("profile").getType());
+    assertEquals("profileCSS+sharedHTML", spec.getView("profile").getData());
+    assertEquals(Gadget.ContentType.URL, spec.getView("maximized").getType());
+    assertEquals("http://www.example.org",
+        spec.getView("maximized").getHref().toString());
+  }
+  public void testMultipleViewsMixedTypes() throws Exception {
+    assertParseException(basicId,
+      attrs2Xml("type=\"url\" href=\"www.example.org\"", "type=\"html\""),
+      GadgetException.Code.INVALID_PARAMETER);
+  }
+  public void testMultipleViewsAttributePrecedence() throws Exception {
+    // first declaration of attribute takes precedence
+    GadgetSpec spec = parser.parse(basicId,
+        attrs2Xml("type=\"html\" view=\"A\" quirks=\"false\"",
+                  "type=\"html\" view=\"A,B\" quirks=\"true\""));
+    assertEquals(true, spec.getView("A").getQuirks());
+    assertEquals(true, spec.getView("B").getQuirks());
+  }
+
+  // QUIRKS ATTRIBUTE
+  public void testQuirksParsing() throws Exception {
+    boolean quirksDefault = true;
+
+    GadgetSpec spec = parser.parse(basicId, attrs1Xml("type=\"html\""));
+    assertEquals("Quirks default: ", quirksDefault, spec.getView(null).getQuirks());
+
+    spec = parser.parse(basicId, attrs1Xml("type=\"html\" quirks=\"true\""));
+    assertEquals("Parsing quirks=\"true\"", true, spec.getView(null).getQuirks());
+
+    spec = parser.parse(basicId, attrs1Xml("type=\"html\" quirks=\"false\""));
+    assertEquals("Parsing quirks=\"false\"", false, spec.getView(null).getQuirks());
+  }
+
+  private String attrs1Xml(String attr1) {
+    String xml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs title=\"Hello, world!\"/>" +
+        "  <Content %s></Content>" +
+        "</Module>";
+    return String.format(xml, attr1);
+  }
+
+  private String attrs2Xml(String attr1, String attr2) {
+    String xml =
+        "<?xml version=\"1.0\"?>" +
+        "<Module>" +
+        "  <ModulePrefs title=\"Hello, world!\"/>" +
+        "  <Content %s></Content>" +
+        "  <Content %s></Content>" +
+        "</Module>";
+    return String.format(xml, attr1, attr2);
+  }
+
+  private void assertParseException(BasicGadgetId id, String xml,
+      GadgetException.Code code) {
+    GadgetException.Code test = null;
+    try {
+      parser.parse(id, xml);
+    } catch (SpecParserException e) {
+      test = e.getCode();
+    }
+    assertEquals(code, test);
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java?rev=631466&r1=631465&r2=631466&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java Tue Feb 26 18:36:16 2008
@@ -34,17 +34,17 @@
   private static final String DATETIME_TITLE = "Hello, World!";
   private static final String DATETIME_CONTENT = "Goodbye, World!";
 
-  public static final String DATETIME_URI_STRING
-      = "http://www.google.com/ig/modules/datetime.xml";
+  public static final String DATETIME_URI_STRING =
+      "http://www.google.com/ig/modules/datetime.xml";
   public static final URI DATETIME_URI;
   public static final int DATETIME_MODULE_ID = 1;
   public static final GadgetView.ID DATETIME_ID;
-  public static final String DATETIME_XML
-      = "<?xml version=\"1.0\"?>" +
-        "<Module>" +
-        "  <ModulePrefs title=\"" + DATETIME_TITLE + "\"/>" +
-        "  <Content type=\"html\">" + DATETIME_CONTENT + "</Content>" +
-        "</Module>";
+  public static final String DATETIME_XML =
+      "<?xml version=\"1.0\"?>" +
+      "<Module>" +
+      "  <ModulePrefs title=\"" + DATETIME_TITLE + "\"/>" +
+      "  <Content type=\"html\">" + DATETIME_CONTENT + "</Content>" +
+      "</Module>";
 
   public static final GadgetSpec DATETIME_SPEC =
       new GadgetSpec() {
@@ -57,17 +57,21 @@
         public String getAuthorEmail() {
           return null;
         }
-        public String getContentData() {
-          return DATETIME_CONTENT;
-        }
-        public String getContentData(String s) {
-          return DATETIME_CONTENT;
-        }
-        public URI getContentHref() {
-          return null;
-        }
-        public ContentType getContentType() {
-          return ContentType.HTML;
+        public View getView(String viewName) {
+          return new View() {
+            public ContentType getType() {
+              return ContentType.HTML;
+            }
+            public URI getHref() {
+              return null;
+            }
+            public String getData() {
+              return DATETIME_CONTENT;
+            }
+            public boolean getQuirks() {
+              return true;
+            }
+          };
         }
         public String getDescription() {
           return null;
@@ -104,6 +108,10 @@
         }
         public List<String> getCategories() {
           return new LinkedList<String>();
+        }
+        @Override
+        public String toString() {
+          return "";
         }
       };