You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2009/09/22 23:30:55 UTC

svn commit: r817848 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java

Author: lindner
Date: Tue Sep 22 21:30:52 2009
New Revision: 817848

URL: http://svn.apache.org/viewvc?rev=817848&view=rev
Log:
SHINDIG-1179 | Allow extensible moduleprefs

Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java?rev=817848&r1=817847&r2=817848&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java Tue Sep 22 21:30:52 2009
@@ -17,24 +17,21 @@
  */
 package org.apache.shindig.gadgets.spec;
 import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.xml.XmlUtil;
 import org.apache.shindig.gadgets.variables.Substitutions;
 
 import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import com.google.common.collect.*;
+
+import org.w3c.dom.*;
+
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.*;
+import javax.xml.transform.stream.StreamResult;
+import java.util.*;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 
 /**
  * Represents the ModulePrefs element of a gadget spec.
@@ -87,40 +84,33 @@
       throw new SpecParserException("ModulePrefs@title is required.");
     }
 
-    categories = Arrays.asList(
-        getAttribute(ATTR_CATEGORY, ""), getAttribute(ATTR_CATEGORY2, ""));
+    categories = ImmutableList.of(getAttribute(ATTR_CATEGORY, ""), getAttribute(ATTR_CATEGORY2, ""));
 
-    // Child elements
-    PreloadVisitor preloadVisitor = new PreloadVisitor();
-    FeatureVisitor featureVisitor = new FeatureVisitor();
-    OAuthVisitor oauthVisitor = new OAuthVisitor();
-    IconVisitor iconVisitor = new IconVisitor();
-    LocaleVisitor localeVisitor = new LocaleVisitor();
-    LinkVisitor linkVisitor = new LinkVisitor();
-
-    Map<String, ElementVisitor> visitors = new ImmutableMap.Builder<String,ElementVisitor>()
-        .put("Preload", preloadVisitor)
-        .put("Optional", featureVisitor)
-        .put("Require", featureVisitor)
-        .put("OAuth", oauthVisitor)
-        .put("Icon", iconVisitor)
-        .put("Locale", localeVisitor)
-        .put("Link", linkVisitor)
-        .build();
+    // Eventually use a list of classes
+    Set<ElementVisitor> visitors = ImmutableSet.of(
+        new FeatureVisitor(),
+        new PreloadVisitor(),
+        new OAuthVisitor(),
+        new IconVisitor(),
+        new LocaleVisitor(),
+        new LinkVisitor(),
+        new ExtraElementsVisitor() // keep this last since it accepts any tag
+    );
 
     walk(element, visitors);
 
-    preloads = Collections.unmodifiableList(preloadVisitor.preloaded);
-    features = Collections.unmodifiableMap(featureVisitor.features);
-    icons = Collections.unmodifiableList(iconVisitor.icons);
-    locales = Collections.unmodifiableMap(localeVisitor.localeMap);
-    links = Collections.unmodifiableMap(linkVisitor.linkMap);
-    oauth = oauthVisitor.oauthSpec;
+    // Tell the visitors to apply their knowledge
+    for (ElementVisitor ev : visitors) {
+      ev.apply(this);
+    }
+
     needsUserPrefSubstitution = prefsNeedsUserPrefSubstitution(this);
   }
 
   /**
    * Produces a new, substituted ModulePrefs
+   * @param prefs An existing ModulePrefs instance
+   * @param substituter The substituter to apply
    */
   private ModulePrefs(ModulePrefs prefs, Substitutions substituter) {
     base = prefs.base;
@@ -153,6 +143,8 @@
       String substituted = substituter.substituteString(attr.getValue());
       attributes.put(attr.getKey(), substituted);
     }
+
+    this.extraElements = ImmutableMultimap.copyOf(prefs.extraElements);
     this.attributes = attributes.build();
     this.needsUserPrefSubstitution = prefs.needsUserPrefSubstitution;
   }
@@ -337,6 +329,7 @@
   }
 
   /**
+   * @param name the attribute name
    * @return the value of an ModulePrefs attribute by name, or null if the
    *     attribute doesn't exist
    */
@@ -345,6 +338,8 @@
   }
 
   /**
+   * @param name the attribute name
+   * @param defaultValue the default Value
    * @return the value of an ModulePrefs attribute by name, or the default
    *     value if the attribute doesn't exist
    */
@@ -358,6 +353,7 @@
   }
 
   /**
+   * @param name the attribute name
    * @return the attribute by name converted to an URI, or the empty URI if the
    *    attribute couldn't be converted
    */
@@ -375,6 +371,7 @@
   }
 
   /**
+   * @param name the attribute name
    * @return the attribute by name converted to a boolean (false if the
    *     attribute doesn't exist)
    */
@@ -384,7 +381,8 @@
   }
 
   /**
-   * @return the attribute by name converted to an interger, or 0 if the
+   * @param name the attribute name
+   * @return the attribute by name converted to an integer, or 0 if the
    *     attribute doesn't exist or is not a valid number.
    */
   public int getIntAttribute(String name) {
@@ -400,68 +398,78 @@
     }
   }
 
+
+  private final List<String> categories;
+  private Map<String, Feature> features;
+  private List<Preload> preloads;
+  private List<Icon> icons;
+  private Map<Locale, LocaleSpec> locales;
+  private Map<String, LinkSpec> links;
+  private OAuthSpec oauth;
+  private Multimap<String,Node> extraElements;
+
   /**
+   * @return Returns a list of flattened attributes for:
    * ModuleSpec@category
    * ModuleSpec@category2
-   * These fields are flattened into a single list.
    */
-  private final List<String> categories;
   public List<String> getCategories() {
     return categories;
   }
 
   /**
-   * ModuleSpec/Require
-   * ModuleSpec/Optional
+   * @return a map of ModuleSpec/Require and ModuleSpec/Optional elements to Feature
    */
-  private final Map<String, Feature> features;
   public Map<String, Feature> getFeatures() {
     return features;
   }
 
   /**
-   * ModuleSpec/Preload
+   * @return a list of Preloads from the ModuleSpec/Preload element
    */
-  private final List<Preload> preloads;
   public List<Preload> getPreloads() {
     return preloads;
   }
 
   /**
-   * ModuleSpec/Icon
+   * @return a list of Icons from the ModuleSpec/Icon element
    */
-  private final List<Icon> icons;
   public List<Icon> getIcons() {
     return icons;
   }
 
   /**
-   * ModuleSpec/Locale
+   * @return a map of Locales to LocalSpec from the ModuleSpec/Locale element
    */
-  private final Map<Locale, LocaleSpec> locales;
   public Map<Locale, LocaleSpec> getLocales() {
     return locales;
   }
 
   /**
-   * ModuleSpec/Link
+   * @return a map of Link names to LinkSpec from the ModuleSpec/Link element
    */
-  private final Map<String, LinkSpec> links;
   public Map<String, LinkSpec> getLinks() {
     return links;
   }
 
   /**
-   * ModuleSpec/OAuthSpec
+   * @return an OAuthSpec built from the ModuleSpec/OAuthSpec element
    */
-  private final OAuthSpec oauth;
   public OAuthSpec getOAuthSpec() {
     return oauth;
   }
 
   /**
-   * Not part of the spec. Indicates whether UserPref-substitutable
-   * fields in this prefs require __UP_ substitution.
+   * @return a Multimap of tagnames to child elements of the ModuleSpec element
+   */
+  public Multimap<String,Node> getExtraElements() {
+    return extraElements;
+  }
+
+  /**
+   * Note: not part of the spec.
+   *
+   * @return true when UserPref-substitutable fields in this prefs require __UP_ substitution.
    */
   public boolean needsUserPrefSubstitution() {
     return needsUserPrefSubstitution;
@@ -481,7 +489,8 @@
    * substituter. See comments on individual fields to see what actually
    * has substitutions performed.
    *
-   * @param substituter
+   * @param substituter the substituter to execute
+   * @return a substituted ModulePrefs
    */
   public ModulePrefs substitute(Substitutions substituter) {
     return new ModulePrefs(this, substituter);
@@ -490,17 +499,23 @@
 
   /**
    * Walks child nodes of the given node.
-   * @param element
-   * @param visitors Map of tag names to visitors for that tag.
+   * @param element root node to be applied
+   * @param visitors Set of visitors to apply to children of element.
+   * @throws SpecParserException when encountering bad input
    */
-  private static void walk(Element element, Map<String, ElementVisitor> visitors)
+  private static void walk(Element element, Set<ElementVisitor> visitors)
       throws SpecParserException {
     NodeList children = element.getChildNodes();
     for (int i = 0, j = children.getLength(); i < j; ++i) {
       Node child = children.item(i);
-      ElementVisitor visitor = visitors.get(child.getNodeName());
-      if (visitor != null) {
-        visitor.visit((Element)child);
+      String tagName = child.getNodeName();
+
+      if (!(child instanceof Element)) continue;
+
+      // Try our visitors in order until we find a match
+      for (ElementVisitor ev : visitors) {
+        if (ev.visit(tagName, (Element)child))
+          break;
       }
     }
   }
@@ -527,6 +542,22 @@
     if (oauth != null) {
       buf.append(oauth).append('\n');
     }
+    
+    if (extraElements != null) {
+      for (Node node : extraElements.values()) {
+        Source source = new DOMSource(node);
+        StringWriter sw = new StringWriter();
+        Result result = new StreamResult(sw);
+        try {
+          Transformer xformer = TransformerFactory.newInstance().newTransformer();
+          xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+          xformer.transform(source, result);
+        } catch (TransformerConfigurationException e) {
+        } catch (TransformerException e) {
+        }
+        buf.append(sw.toString());
+      }
+    }
     buf.append("</ModulePrefs>");
     return buf.toString();
   }
@@ -546,8 +577,27 @@
            prefs.getTitleUrl().toString().contains(UP_SUBST_PREFIX);
   }
 
+  /**
+   * Interface used for parsing specific chunks of the gadget spec
+   */
   interface ElementVisitor {
-    void visit(Element element) throws SpecParserException;
+    /**
+     * Called on each node that matches
+     *
+     * @param tag the name of the tag being parsed
+     * @param element the element to parse
+     * @return true if we handled the tag, false if not
+     * @throws SpecParserException when parsing issues are present
+     */
+    boolean visit(String tag, Element element) throws SpecParserException;
+
+    /**
+     * Called when all elements have been processed.  Any data that is set on the ModulePrefs instance should be
+     * Immutable
+     *
+     * @param moduleprefs The moduleprefs object to mutate
+     */
+    void apply(ModulePrefs moduleprefs);
   }
 
   /**
@@ -556,12 +606,16 @@
   private class PreloadVisitor implements ElementVisitor {
     private final List<Preload> preloaded = Lists.newLinkedList();
 
-    protected PreloadVisitor() {
-    }
+    public boolean visit(String tag,Element element) throws SpecParserException {
+      if (!"Preload".equals(tag)) return false;
 
-    public void visit(Element element) throws SpecParserException {
       Preload preload = new Preload(element, base);
       preloaded.add(preload);
+      return true;
+    }
+
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.preloads = ImmutableList.copyOf(preloaded);
     }
   }
 
@@ -569,18 +623,22 @@
    * Process ModulePrefs/OAuth
    */
   private class OAuthVisitor implements ElementVisitor {
-    private OAuthSpec oauthSpec;
+    private OAuthSpec oauthSpec = null;
 
-    public OAuthVisitor() {
-      this.oauthSpec = null;
-    }
+    public boolean visit(String tag, Element element) throws SpecParserException {
+      if (!"OAuth".equals(tag)) return false;
 
-    public void visit(Element element) throws SpecParserException {
       if (oauthSpec != null) {
         throw new SpecParserException("ModulePrefs/OAuth may only occur once.");
       }
       oauthSpec = new OAuthSpec(element, base);
+      return true;
+    }
+
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.oauth = oauthSpec;
     }
+
   }
 
   /**
@@ -589,9 +647,18 @@
   private static class FeatureVisitor implements ElementVisitor {
     private final Map<String, Feature> features = Maps.newHashMap();
 
-    public void visit (Element element) throws SpecParserException {
+    private static final Set<String> tags = ImmutableSet.of("Require", "Optional");
+    public Set<String> tags() { return tags; }
+
+    public boolean visit (String tag, Element element) throws SpecParserException {
+      if (!"Require".equals(tag) &&  !"Optional".equals(tag)) return false;
+
       Feature feature = new Feature(element);
       features.put(feature.getName(), feature);
+      return true;
+    }
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.features = ImmutableMap.copyOf(features);
     }
   }
 
@@ -601,8 +668,14 @@
   private static class IconVisitor implements ElementVisitor {
     private final List<Icon> icons = Lists.newLinkedList();
 
-    public void visit(Element element) throws SpecParserException {
+    public boolean visit(String tag, Element element) throws SpecParserException {
+      if (!"Icon".equals(tag)) return false;
+
       icons.add(new Icon(element));
+      return true;
+    }
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.icons = ImmutableList.copyOf(icons);
     }
   }
 
@@ -612,11 +685,15 @@
   private class LocaleVisitor implements ElementVisitor {
     private final Map<Locale, LocaleSpec> localeMap = Maps.newHashMap();
 
-    public void visit(Element element) throws SpecParserException {
+    public boolean visit(String tag, Element element) throws SpecParserException {
+      if (!"Locale".equals(tag)) return false;
       LocaleSpec locale = new LocaleSpec(element, base);
       localeMap.put(new Locale(locale.getLanguage(), locale.getCountry()), locale);
+      return true;
+    }
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.locales = ImmutableMap.copyOf(localeMap);
     }
-
   }
 
   /**
@@ -625,9 +702,27 @@
   private class LinkVisitor implements ElementVisitor {
     private final Map<String, LinkSpec> linkMap = Maps.newHashMap();
 
-    public void visit(Element element) throws SpecParserException {
+    public boolean visit(String tag, Element element) throws SpecParserException {
+      if (!"Link".equals(tag)) return false;
       LinkSpec link = new LinkSpec(element, base);
       linkMap.put(link.getRel(), link);
+      return true;
+    }
+
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.links = ImmutableMap.copyOf(linkMap);
+    }
+  }
+
+  private static class ExtraElementsVisitor implements ElementVisitor {
+    private Multimap<String,Node> elements = ArrayListMultimap.create();
+
+    public boolean visit(String tag, Element element) throws SpecParserException {
+      elements.put(tag, element.cloneNode(true));
+      return true;
+    }
+    public void apply(ModulePrefs moduleprefs) {
+      moduleprefs.extraElements = ImmutableMultimap.copyOf(elements);
     }
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java?rev=817848&r1=817847&r2=817848&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java Tue Sep 22 21:30:52 2009
@@ -29,9 +29,12 @@
 import org.apache.shindig.gadgets.variables.Substitutions;
 
 import org.junit.Test;
+import org.w3c.dom.Node;
 
 import java.util.Locale;
 
+import com.google.common.collect.Multimap;
+
 public class ModulePrefsTest {
   private static final Uri SPEC_URL = Uri.parse("http://example.org/g.xml");
   private static final String FULL_XML
@@ -73,6 +76,7 @@
         "          param_location='auth-header' />" +
         "    </Service>" +
         "  </OAuth>" +
+        "  <NavigationItem title=\"moo\"><AppParameter key=\"test\" value=\"1\"/></NavigationItem>" +
         "</ModulePrefs>";
 
   private void doAsserts(ModulePrefs prefs) {
@@ -118,6 +122,11 @@
     assertEquals(OAuthService.Method.GET, oauth.getAccessUrl().method);
     assertEquals(OAuthService.Location.HEADER, oauth.getAccessUrl().location);
     assertEquals(Uri.parse("http://www.example.com/authorize"), oauth.getAuthorizationUrl());
+
+    Multimap<String, Node> extra = prefs.getExtraElements();
+
+    assertTrue(extra.containsKey("NavigationItem"));
+    assertEquals(extra.get("NavigationItem").iterator().next().getChildNodes().getLength(), 1);
   }
 
   @Test