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