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 2011/04/03 11:29:27 UTC

svn commit: r1088228 - in /shindig/trunk/java: gadgets/src/main/java/org/apache/shindig/gadgets/ gadgets/src/main/java/org/apache/shindig/gadgets/config/ gadgets/src/main/java/org/apache/shindig/gadgets/render/ gadgets/src/main/java/org/apache/shindig/...

Author: lindner
Date: Sun Apr  3 09:29:25 2011
New Revision: 1088228

URL: http://svn.apache.org/viewvc?rev=1088228&view=rev
Log:
SHINDIG-1492 | Patch from Matthew Marum | View level support for Features and Locales

Added:
    shindig/trunk/java/server/src/test/resources/endtoend/viewLevelElementsTest.xml
    shindig/trunk/java/server/src/test/resources/endtoend/viewMessages.xml
Modified:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultMessageBundleFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/config/CoreUtilConfigContributor.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/Renderer.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataGadgetRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultIframeUriManager.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/BidiSubstituter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/MessageSubstituter.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/DefaultMessageBundleFactoryTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/FeatureTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/LocaleSpecTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/UriManagerTestBase.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/variables/VariableSubstituterTest.java
    shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultMessageBundleFactory.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultMessageBundleFactory.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultMessageBundleFactory.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultMessageBundleFactory.java Sun Apr  3 09:29:25 2011
@@ -59,9 +59,9 @@ public class DefaultMessageBundleFactory
     return new MessageBundle(((LocaleQuery) query).locale, content);
   }
 
-  public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container)
+  public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container, String view)
       throws GadgetException {
-    MessageBundle exact = getBundleFor(spec, locale, ignoreCache, container);
+    MessageBundle exact = getBundleFor(spec, locale, ignoreCache, container, view);
 
     // We don't want to fetch the same bundle multiple times, so we verify that the exact match
     // has not already been fetched.
@@ -73,28 +73,28 @@ public class DefaultMessageBundleFactory
     if (isAllCountry) {
       lang = MessageBundle.EMPTY;
     } else {
-      lang = getBundleFor(spec, new Locale(locale.getLanguage(), "ALL"), ignoreCache, container);
+      lang = getBundleFor(spec, new Locale(locale.getLanguage(), "ALL"), ignoreCache, container, view);
     }
 
     if (isAllLanguage) {
       country = MessageBundle.EMPTY;
     } else {
-      country = getBundleFor(spec, new Locale("all", locale.getCountry()), ignoreCache, container);
+      country = getBundleFor(spec, new Locale("all", locale.getCountry()), ignoreCache, container, view);
     }
 
     if (isAllCountry || isAllLanguage) {
       // If either of these is true, we already picked up both anyway.
       all = MessageBundle.EMPTY;
     } else {
-      all = getBundleFor(spec, ALL_ALL, ignoreCache, container);
+      all = getBundleFor(spec, ALL_ALL, ignoreCache, container, view);
     }
 
     return new MessageBundle(all, country, lang, exact);
   }
 
-  private MessageBundle getBundleFor(GadgetSpec spec, Locale locale, boolean ignoreCache, String container)
+  private MessageBundle getBundleFor(GadgetSpec spec, Locale locale, boolean ignoreCache, String container, String view)
       throws GadgetException {
-    LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
+    LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale, view);
     if (localeSpec == null) {
       return MessageBundle.EMPTY;
     }

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java Sun Apr  3 09:29:25 2011
@@ -20,6 +20,7 @@ package org.apache.shindig.gadgets;
 import org.apache.shindig.common.util.OpenSocialVersion;
 import org.apache.shindig.gadgets.features.FeatureRegistry;
 import org.apache.shindig.gadgets.preload.PreloadedData;
+import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
 import org.apache.shindig.gadgets.spec.View;
@@ -32,8 +33,8 @@ import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-
 /**
  * Intermediary representation of all state associated with processing
  * of a single gadget request.
@@ -51,6 +52,8 @@ public class Gadget {
    */
   public Gadget setContext(GadgetContext context) {
     this.context = context;
+    directFeatureDeps = null;  //New context means View may have changed
+    allGadgetFeatures = null;
     return this;
   }
 
@@ -72,7 +75,6 @@ public class Gadget {
    */
   public Gadget setSpec(GadgetSpec spec) {
     this.spec = spec;
-    this.directFeatureDeps = Sets.newHashSet(spec.getModulePrefs().getFeatures().keySet());
     return this;
   }
 
@@ -125,7 +127,7 @@ public class Gadget {
   public synchronized List<String> getAllFeatures() {
     if (allGadgetFeatures == null) {
       Preconditions.checkState(featureRegistry != null, "setGadgetFeatureRegistry must be called before Gadget.getAllFeatures()");
-      allGadgetFeatures = featureRegistry.getFeatures(Lists.newArrayList(directFeatureDeps));
+      allGadgetFeatures = featureRegistry.getFeatures(Lists.newArrayList(getDirectFeatureDeps()));
     }
     return allGadgetFeatures;
   }
@@ -150,20 +152,59 @@ public class Gadget {
    * gadget.getSpec().getModulePrefs().getLocale(locale);
    */
   public LocaleSpec getLocale() {
-    return spec.getModulePrefs().getLocale(context.getLocale());
+    String viewName = null;
+    View view = getCurrentView();
+    if (view == null) { // Use default view if current view is not set
+      viewName = GadgetSpec.DEFAULT_VIEW;
+    } else {
+      viewName = view.getName();
+    }
+    return spec.getModulePrefs().getLocale(context.getLocale(), viewName);
   }
 
+  private void initializeFeatureDeps() {
+    if (directFeatureDeps == null) {
+      directFeatureDeps = Sets.newHashSet();
+      // If we have context, lets generate the correct set of views.
+      if (context != null) {
+        directFeatureDeps.addAll(spec.getModulePrefs()
+            .getViewFeatures(context.getView()).keySet());
+      } else {
+        directFeatureDeps.addAll(spec.getModulePrefs().getFeatures().keySet());
+      }
+    }
+  }
+  
   public void addFeature(String name) {
+    initializeFeatureDeps();
     directFeatureDeps.add(name);
   }
-
+  
   public void removeFeature(String name) {
+    initializeFeatureDeps();
     directFeatureDeps.remove(name);
   }
-
+  
   public Set<String> getDirectFeatureDeps() {
+    initializeFeatureDeps();
     return Collections.unmodifiableSet(directFeatureDeps);
   }
+  
+  /**
+   * Convenience method that returns Map of features to load for gadget's current view
+   * 
+   * @return a map of ModuleSpec/Require and ModuleSpec/Optional elements to Feature
+   */
+  public Map<String, Feature> getViewFeatures() {    
+    String name = null;
+    View view = getCurrentView();   
+    if (view == null) { // Use default view name if current view is not set
+      name = GadgetSpec.DEFAULT_VIEW;
+    } else {
+      name = view.getName();
+    }
+    return spec.getModulePrefs().getViewFeatures(name);
+  }
 
   /**
    * Should the gadget content be sanitized on output

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java Sun Apr  3 09:29:25 2011
@@ -158,7 +158,7 @@ public class HashLockedDomainService imp
     if (lockSecurityTokens) {
       return gadget.getAllFeatures().contains("locked-domain");
     }
-    return gadget.getSpec().getModulePrefs().getFeatures().keySet().contains("locked-domain");
+    return gadget.getViewFeatures().keySet().contains("locked-domain");
   }
 
   private boolean hostRequiresLockedDomain(String host) {

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFactory.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFactory.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFactory.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFactory.java Sun Apr  3 09:29:25 2011
@@ -39,9 +39,10 @@ public interface MessageBundleFactory {
    * @param locale The language and country to get a message bundle for.
    * @param ignoreCache  True to bypass any caching of message bundles for debugging purposes.
    * @param container The container that is requesting this message bundle
+   * @param view The view for which to return the Locale appropriate message bundle.  To retrieve only globally scoped bundles pass 'null'.
    * @return The newly created MesageBundle.
    * @throws GadgetException if retrieval fails for any reason.
    */
-  MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container)
+  MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container, String view)
       throws GadgetException;
 }

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/config/CoreUtilConfigContributor.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/config/CoreUtilConfigContributor.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/config/CoreUtilConfigContributor.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/config/CoreUtilConfigContributor.java Sun Apr  3 09:29:25 2011
@@ -49,8 +49,7 @@ public class CoreUtilConfigContributor i
   /** {@inheritDoc} */
   public void contribute(Map<String, Object> config, Gadget gadget) {
     // Add gadgets.util support. This is calculated dynamically based on request inputs.
-    ModulePrefs prefs = gadget.getSpec().getModulePrefs();
-    Collection<Feature> features = prefs.getFeatures().values();
+    Collection<Feature> features = gadget.getViewFeatures().values();
     Map<String, Map<String, Object>> featureMap = Maps.newHashMapWithExpectedSize(features.size());
     Set<String> allFeatureNames = registry.getAllFeatureNames();
 

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/Renderer.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/Renderer.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/Renderer.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/Renderer.java Sun Apr  3 09:29:25 2011
@@ -124,7 +124,7 @@ public class Renderer {
    * Returns true iff the gadget opts into the caja or the container forces caja by flag
    */
   private boolean requiresCaja(Gadget gadget) {
-    return gadget.getSpec().getModulePrefs().getFeatures().containsKey("caja")
+    return gadget.getViewFeatures().containsKey("caja")
         || "1".equals(gadget.getContext().getParameter(UriCommon.Param.CAJOLE.getKey()));
   }
 

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java Sun Apr  3 09:29:25 2011
@@ -216,7 +216,7 @@ public class RenderingGadgetRewriter imp
       Element mainScriptTag = document.createElement("script");
       GadgetContext context = gadget.getContext();
       MessageBundle bundle = messageBundleFactory.getBundle(
-          gadget.getSpec(), context.getLocale(), context.getIgnoreCache(), context.getContainer());
+          gadget.getSpec(), context.getLocale(), context.getIgnoreCache(), context.getContainer(), context.getView());
       injectMessageBundles(bundle, mainScriptTag);
       injectDefaultPrefs(gadget, mainScriptTag);
       injectPreloads(gadget, mainScriptTag);
@@ -302,8 +302,8 @@ public class RenderingGadgetRewriter imp
     }
 
     // Get all resources requested by the gadget's requires/optional features.
-    Map<String, Feature> featureMap = gadget.getSpec().getModulePrefs().getFeatures();
-    List<String> gadgetFeatureKeys = Lists.newArrayList(gadget.getDirectFeatureDeps());
+    Map<String, Feature> featureMap = gadget.getViewFeatures();
+    List<String> gadgetFeatureKeys = Lists.newLinkedList(gadget.getDirectFeatureDeps());
     List<FeatureResource> gadgetResources =
         featureRegistry.getFeatureResources(context, gadgetFeatureKeys, unsupported).getResources();
     if (!unsupported.isEmpty()) {

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataGadgetRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataGadgetRewriter.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataGadgetRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/PipelineDataGadgetRewriter.java Sun Apr  3 09:29:25 2011
@@ -58,7 +58,7 @@ public class PipelineDataGadgetRewriter 
   
   public void rewrite(Gadget gadget, MutableContent content) {
     // Only bother for gadgets using the opensocial-data feature
-    if (!gadget.getSpec().getModulePrefs().getFeatures().containsKey("opensocial-data")) {
+    if (!gadget.getViewFeatures().containsKey("opensocial-data")) {
       return;
     }
     

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java Sun Apr  3 09:29:25 2011
@@ -117,8 +117,7 @@ public class TemplateRewriter implements
   }
 
   public void rewrite(Gadget gadget, MutableContent content) throws RewritingException {
-    Map<String, Feature> directFeatures = gadget.getSpec().getModulePrefs()
-        .getFeatures();
+    Map<String, Feature> directFeatures = gadget.getViewFeatures();
 
     Feature feature = directFeatures.get(TEMPLATES_FEATURE_NAME);
     if (feature == null && directFeatures.containsKey(OSML_FEATURE_NAME)) {
@@ -349,7 +348,8 @@ public class TemplateRewriter implements
       Gadget gadget = templateContext.getGadget();
       
       MessageBundle bundle = messageBundleFactory.getBundle(gadget.getSpec(),
-          gadget.getContext().getLocale(), gadget.getContext().getIgnoreCache(), gadget.getContext().getContainer());
+          gadget.getContext().getLocale(), gadget.getContext().getIgnoreCache(), 
+          gadget.getContext().getContainer(), gadget.getContext().getView());
       MessageELResolver messageELResolver = new MessageELResolver(expressions, bundle);
   
       int autoUpdateID = 0;

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java Sun Apr  3 09:29:25 2011
@@ -17,14 +17,18 @@
  */
 package org.apache.shindig.gadgets.spec;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.shindig.common.xml.XmlUtil;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
 
 /**
@@ -40,6 +44,7 @@ public class Feature {
     this.params = ImmutableMultimap.of();
     this.required = true;
     this.name = name;
+    this.views = ImmutableSet.of();
   }
   
   /**
@@ -90,6 +95,17 @@ public class Feature {
   public boolean getRequired() {
     return required;
   }
+  
+  /**
+   * Require@views
+   * Optional@views
+   * 
+   * Views associated with this feature
+   */
+  private final Set<String> views;
+  public Set<String> getViews() {
+    return views;
+  }
 
   /**
    * Produces an xml representation of the feature.
@@ -99,8 +115,11 @@ public class Feature {
     StringBuilder buf = new StringBuilder();
     buf.append(required ? "<Require" : "<Optional")
        .append(" feature=\"")
-       .append(name)
-       .append("\">");
+       .append(name);
+    if (views.size() > 0) {
+      buf.append("\" views=\"").append(StringUtils.join(views, ','));
+    }
+    buf.append("\">");
     for (Map.Entry<String, Collection<String>> entry : params.asMap().entrySet()) {
       buf.append("\n<Param name=\"")
          .append(entry.getKey())
@@ -142,5 +161,8 @@ public class Feature {
     } else {
       this.params = ImmutableMultimap.of();
     }
+    // Record all the associated views
+    String viewNames = XmlUtil.getAttribute(feature, "views", "").trim();
+    this.views = ImmutableSet.copyOf(Splitter.on(',').omitEmptyStrings().trimResults().split(viewNames));
   }
 }

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java Sun Apr  3 09:29:25 2011
@@ -16,13 +16,17 @@
  * specific language governing permissions and limitations under the License.
  */
 package org.apache.shindig.gadgets.spec;
+import org.apache.commons.lang.StringUtils;
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlUtil;
-
 import org.w3c.dom.Element;
 
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Represents a Locale tag.
@@ -53,6 +57,11 @@ public class LocaleSpec {
     if (!("ltr".equals(languageDirection) || "rtl".equals(languageDirection))) {
       throw new SpecParserException("Locale/@language_direction must be ltr or rtl");
     }
+    // Record all the associated views
+    String viewNames = XmlUtil.getAttribute(element, "views", "").trim();
+
+    this.views = ImmutableSet.copyOf(Splitter.on(',').omitEmptyStrings().trimResults().split(viewNames));
+    
     String messagesString = XmlUtil.getAttribute(element, "messages");
     if (messagesString == null) {
       this.messages = Uri.parse("");
@@ -104,19 +113,29 @@ public class LocaleSpec {
   public MessageBundle getMessageBundle() {
     return messageBundle;
   }
+  
+  /**
+   * Locale@views
+   * 
+   * Views associated with this Locale
+   */
+  private final Set<String> views;
+  public Set<String> getViews() {
+    return views;
+  }
 
   @Override
   public String toString() {
     StringBuilder buf = new StringBuilder();
-    buf.append("<Locale")
-       .append(" lang='").append(getLanguage()).append('\'')
-       .append(" country='").append(getCountry()).append('\'')
-       .append(" language_direction='").append(languageDirection).append('\'')
-       .append(" messages='").append(messages).append("'>\n");
+    buf.append("<Locale").append(" lang='").append(getLanguage()).append('\'')
+        .append(" country='").append(getCountry()).append('\'')
+        .append(" language_direction='").append(languageDirection).append('\'');
+    if (views.size() > 0) {
+      buf.append(" views=\'").append(StringUtils.join(views, ',')).append('\'');
+    }
+    buf.append(" messages='").append(messages).append("'>\n");
     for (Map.Entry<String, String> entry : messageBundle.getMessages().entrySet()) {
-      buf.append("<msg name='").append(entry.getKey()).append("'>")
-         .append(entry.getValue())
-         .append("</msg>\n");
+      buf.append("<msg name='").append(entry.getKey()).append("'>").append(entry.getValue()).append("</msg>\n");
     }
     buf.append("</Locale>");
     return buf.toString();

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java Sun Apr  3 09:29:25 2011
@@ -50,6 +50,9 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
+
+
+
 /**
  * Represents the ModulePrefs element of a gadget spec.
  *
@@ -57,6 +60,7 @@ import java.util.Set;
  * Content and UserPref nodes.
  */
 public class ModulePrefs {
+	
 
   private static final String ATTR_TITLE = "title";
   private static final String ATTR_TITLE_URL = "title_url";
@@ -84,6 +88,9 @@ public class ModulePrefs {
   private static final String ATTR_CATEGORY2 = "category2";
   private static final Uri EMPTY_URI = Uri.parse("");
   private static final String UP_SUBST_PREFIX = "__UP_";
+  
+  // Used to identify Locales that are globally scoped
+  private static final String GLOBAL_LOCALE = "";
 
   private final Map<String, String> attributes;
   private final Uri base;
@@ -131,7 +138,10 @@ public class ModulePrefs {
     base = prefs.base;
     categories = prefs.getCategories();
     features = prefs.getFeatures();
-    locales = prefs.getLocales();
+    globalFeatures = prefs.globalFeatures;
+    allFeatures = prefs.getAllFeatures();
+    allLocales = prefs.allLocales;
+    locales = prefs.locales;
     oauth = prefs.oauth;
 
     List<Preload> preloads = Lists.newArrayList();
@@ -425,10 +435,13 @@ public class ModulePrefs {
 
 
   private final List<String> categories;
+  private List<Feature> allFeatures;
   private Map<String, Feature> features;
+  private Map<String, Feature> globalFeatures;
   private List<Preload> preloads;
   private List<Icon> icons;
-  private Map<Locale, LocaleSpec> locales;
+  private Map<String, Map<Locale, LocaleSpec>>  locales;
+  private Map<Locale, LocaleSpec> allLocales;
   private Map<String, LinkSpec> links;
   private OAuthSpec oauth;
   private Multimap<String,Node> extraElements;
@@ -441,13 +454,41 @@ public class ModulePrefs {
   public List<String> getCategories() {
     return categories;
   }
-
+  
   /**
-   * @return a map of ModuleSpec/Require and ModuleSpec/Optional elements to Feature
+   * All features are included in ModulePrefs.  
+   * View level features have view qualifiers appended.
+   * @return a map of ModulePrefs/Require and ModulePrefs/Optional elements to Feature
    */
   public Map<String, Feature> getFeatures() {
     return features;
   }
+  
+  
+  /**
+   * All features elements defined in ModulePrefs
+   * @return a list of all Features included in ModulePrefs
+   */
+  public List<Feature> getAllFeatures() {
+    return allFeatures;
+  }
+  
+  /**
+   * Returns Map of features to load for the given View
+   * @return a map of ModuleSpec/Require and ModuleSpec/Optional elements to Feature
+   */
+  public Map<String, Feature> getViewFeatures(String view) {
+    Map<String, Feature> map = Maps.newHashMap();
+    // Global features are in all views..
+    map.putAll(globalFeatures);
+    // By adding view level features last so they can override global feature configurations
+    for (Feature feature : features.values()) {
+      if (feature.getViews().contains(view)) {
+        map.put(feature.getName(), feature);
+      }
+    }
+    return map;
+  }
 
   /**
    * @return a list of Preloads from the ModuleSpec/Preload element
@@ -464,10 +505,10 @@ public class ModulePrefs {
   }
 
   /**
-   * @return a map of Locales to LocalSpec from the ModuleSpec/Locale element
+   * @return a Map of Locales to LocalSpec from the ModuleSpec/Locale element
    */
   public Map<Locale, LocaleSpec> getLocales() {
-    return locales;
+    return allLocales;
   }
 
   /**
@@ -501,12 +542,32 @@ public class ModulePrefs {
   }
 
   /**
-   * Gets the locale spec for the given locale, if any exists.
+   * Gets the global locale spec for the given locale, if any exists.
+   *
+   * @return The locale spec, if there is a matching one, or null.
+   */
+  public LocaleSpec getGlobalLocale(Locale locale) {
+    return getLocale(locale, GLOBAL_LOCALE);
+  }
+  
+  /**
+   * Gets the locale spec for the given locale and view, if any exists.
    *
    * @return The locale spec, if there is a matching one, or null.
    */
-  public LocaleSpec getLocale(Locale locale) {
-    return locales.get(locale);
+  public LocaleSpec getLocale(Locale locale, String view) {
+    if (view == null) {
+      view = GLOBAL_LOCALE;
+    }
+    Map<Locale, LocaleSpec> viewLocales = locales.get(view);
+    LocaleSpec locSpec = null;
+    if (viewLocales != null) {
+      locSpec = viewLocales.get(locale); // Check view specific locale...
+    }
+    if (locSpec == null && !view.equals(GLOBAL_LOCALE)) { // If not there, check Global map
+      locSpec = getGlobalLocale(locale);
+    }
+    return locSpec;
   }
 
   /**
@@ -679,6 +740,7 @@ public class ModulePrefs {
    */
   private static final class FeatureVisitor implements ElementVisitor {
     private final Map<String, Feature> features = Maps.newHashMap();
+    private final Map<String, Feature> globalFeatures = Maps.newHashMap();
     private final MutableBoolean oauthMarker;
     private boolean coreIncluded = false;
 
@@ -688,24 +750,45 @@ public class ModulePrefs {
       this.oauthMarker = oauthMarker;
     }
 
-    public boolean visit (String tag, Element element) throws SpecParserException {
-      if (!TAGS.contains(tag)) return false;
+    public boolean visit(String tag, Element element)
+        throws SpecParserException {
+      if (!TAGS.contains(tag))
+        return false;
 
       Feature feature = new Feature(element);
-      coreIncluded = coreIncluded || feature.getName().startsWith("core");
-      features.put(feature.getName(), feature);
+      if (feature.getViews().size() == 0) {
+        coreIncluded = coreIncluded || feature.getName().startsWith("core");
+        features.put(feature.getName(), feature);
+        globalFeatures.put(feature.getName(), feature);
+      } else {
+        // We are going to include Core feature globally, so skip it if it was
+        // included for any Views
+        if (!feature.getName().startsWith("core")) {
+          // Key view level features by qualifying with the view ID
+          for (String view : feature.getViews()) {
+            StringBuffer buff = new StringBuffer(feature.getName());
+            buff.append('.');
+            buff.append(view);
+            features.put(buff.toString(), feature);
+          }
+        }
+      }
       return true;
     }
+    
     public void apply(ModulePrefs moduleprefs) {
       if (!coreIncluded) {
         // No library was explicitly included from core - add it as an implicit dependency.
         features.put(Feature.CORE_FEATURE.getName(), Feature.CORE_FEATURE);
+        globalFeatures.put(Feature.CORE_FEATURE.getName(), Feature.CORE_FEATURE);
       }
       if (oauthMarker.booleanValue()) {
         // <OAuth> tag found: security token needed.
         features.put(Feature.SECURITY_TOKEN_FEATURE.getName(), Feature.SECURITY_TOKEN_FEATURE);
       }
       moduleprefs.features = ImmutableMap.copyOf(features);
+      moduleprefs.globalFeatures = ImmutableMap.copyOf(globalFeatures);
+      moduleprefs.allFeatures = ImmutableList.copyOf(features.values());
     }
   }
 
@@ -730,17 +813,46 @@ public class ModulePrefs {
    * Process ModulePrefs/Locale
    */
   private class LocaleVisitor implements ElementVisitor {
-    private final Map<Locale, LocaleSpec> localeMap = Maps.newHashMap();
 
-    public boolean visit(String tag, Element element) throws SpecParserException {
-      if (!"Locale".equals(tag)) return false;
+    private Map<String, Map<Locale, LocaleSpec>> locales = Maps.newHashMap();
+
+    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);
+      if (locale.getViews().isEmpty()) {
+        storeLocaleSpec(GLOBAL_LOCALE, locale);
+      } else {
+        // We've got a view level Locale, need to store the mapping of Views to
+        // the appropriate LocaleSpecs
+        for (String view : locale.getViews()) {
+          storeLocaleSpec(view,locale);
+        }
+      }
       return true;
     }
+    
     public void apply(ModulePrefs moduleprefs) {
-      moduleprefs.locales = ImmutableMap.copyOf(localeMap);
+      Map<Locale, LocaleSpec> allLocales = Maps.newHashMap();
+      moduleprefs.locales = locales;
+      for(Map<Locale, LocaleSpec> map : locales.values()){
+        allLocales.putAll(map);
+      }
+      moduleprefs.allLocales = ImmutableMap.copyOf(allLocales);
     }
+    
+    private void storeLocaleSpec(String view, LocaleSpec locale){
+      Map<Locale, LocaleSpec> viewLocaleSpecs;
+      if (locales.get(view) == null) {
+        viewLocaleSpecs = Maps.newHashMap();
+        locales.put(view, viewLocaleSpecs);
+      } else {
+        viewLocaleSpecs = locales.get(view);
+      }
+      viewLocaleSpecs.put(new Locale(locale.getLanguage(), locale.getCountry()), locale);      
+    }
+    
   }
 
   /**

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultIframeUriManager.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultIframeUriManager.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultIframeUriManager.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultIframeUriManager.java Sun Apr  3 09:29:25 2011
@@ -291,7 +291,7 @@ public class DefaultIframeUriManager imp
   }
 
   protected void addExtrasForTypeUrl(UriBuilder uri, Gadget gadget) {
-    Set<String> features = gadget.getSpec().getModulePrefs().getFeatures().keySet();
+    Set<String> features = gadget.getViewFeatures().keySet();
     addParam(uri, Param.LIBS.getKey(), DefaultJsUriManager.addJsLibs(features), false, false);
   }
 

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/BidiSubstituter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/BidiSubstituter.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/BidiSubstituter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/BidiSubstituter.java Sun Apr  3 09:29:25 2011
@@ -51,7 +51,7 @@ public class BidiSubstituter implements 
       throws GadgetException {
     MessageBundle bundle =
         messageBundleFactory.getBundle(spec, context.getLocale(), context.getIgnoreCache(),
-                    context.getContainer());
+                    context.getContainer(), context.getView());
     String dir = bundle.getLanguageDirection();
 
     boolean rtl = RTL.equals(dir);

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/MessageSubstituter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/MessageSubstituter.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/MessageSubstituter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/variables/MessageSubstituter.java Sun Apr  3 09:29:25 2011
@@ -41,7 +41,7 @@ public class MessageSubstituter implemen
   public void addSubstitutions(Substitutions substituter, GadgetContext context, GadgetSpec spec)
           throws GadgetException {
     MessageBundle bundle = messageBundleFactory.getBundle(spec, context.getLocale(),
-        context.getIgnoreCache(), context.getContainer());
+        context.getIgnoreCache(), context.getContainer(), context.getView());
         
     substituter.addSubstitutions(Substitutions.Type.MESSAGE, bundle.getMessages());
   }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/DefaultMessageBundleFactoryTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/DefaultMessageBundleFactoryTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/DefaultMessageBundleFactoryTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/DefaultMessageBundleFactoryTest.java Sun Apr  3 09:29:25 2011
@@ -62,9 +62,11 @@ public class DefaultMessageBundleFactory
   private static final String MSG_0_VALUE = "Message 0 VALUE";
   private static final String MSG_0_LANG_VALUE = "Message 0 language VALUE";
   private static final String MSG_0_COUNTRY_VALUE = "Message 0 country VALUE";
+  private static final String MSG_0_VIEW_VALUE = "Message 0 view VALUE";
   private static final String MSG_0_ALL_VALUE = "Message 0 a VALUE";
   private static final String MSG_1_VALUE = "msg one val";
   private static final String MSG_2_VALUE = "message two val.";
+  private static final String MSG_2_VIEW_VALUE = "message two view val.";
   private static final String MSG_3_VALUE = "message three value";
 
   private static final Locale COUNTRY_LOCALE = new Locale("all", "US");
@@ -105,6 +107,14 @@ public class DefaultMessageBundleFactory
         "  <msg name='" + MSG_0_NAME + "'>" + MSG_0_COUNTRY_VALUE + "</msg>" +
         "  <msg name='" + MSG_3_NAME + "'>" + MSG_3_VALUE + "</msg>" +
         " </Locale>" +
+        " <Locale country='" + LOCALE.getCountry() + "' views='view1,view2'>" +
+        "  <msg name='" + MSG_0_NAME + "'>" + MSG_0_VIEW_VALUE + "</msg>" +
+        "  <msg name='" + MSG_3_NAME + "'>" + MSG_3_VALUE + "</msg>" +
+        " </Locale>" +
+        " <Locale views='view1'>" +
+        "  <msg name='" + MSG_0_NAME + "'>" + MSG_0_ALL_VALUE + "</msg>" +
+        "  <msg name='" + MSG_2_NAME + "'>" + MSG_2_VIEW_VALUE + "</msg>" +
+        " </Locale>" +
         " <Locale lang='" + LOCALE.getLanguage() + "'>" +
         "  <msg name='" + MSG_0_NAME + "'>" + MSG_0_LANG_VALUE + "</msg>" +
         "  <msg name='" + MSG_1_NAME + "'>" + MSG_1_VALUE + "</msg>" +
@@ -155,7 +165,7 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(response);
     replay(pipeline);
 
-    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
 
     assertEquals(MSG_0_VALUE, bundle.getMessages().get(MSG_0_NAME));
     assertEquals(MSG_1_VALUE, bundle.getMessages().get(MSG_1_NAME));
@@ -165,7 +175,7 @@ public class DefaultMessageBundleFactory
 
   @Test
   public void getLangBundle() throws Exception {
-    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, LANG_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, LANG_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
 
     assertEquals(MSG_0_LANG_VALUE, bundle.getMessages().get(MSG_0_NAME));
     assertEquals(MSG_1_VALUE, bundle.getMessages().get(MSG_1_NAME));
@@ -175,17 +185,38 @@ public class DefaultMessageBundleFactory
 
   @Test
   public void getCountryBundle() throws Exception {
-    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, COUNTRY_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, COUNTRY_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
 
     assertEquals(MSG_0_COUNTRY_VALUE, bundle.getMessages().get(MSG_0_NAME));
     assertNull(bundle.getMessages().get(MSG_1_NAME));
     assertNull(bundle.getMessages().get(MSG_2_NAME));
     assertEquals(MSG_3_VALUE, bundle.getMessages().get(MSG_3_NAME));
   }
+  
+  @Test
+  public void getViewCountryBundle() throws Exception {
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, COUNTRY_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, "view1");
+
+    assertEquals(MSG_0_VIEW_VALUE, bundle.getMessages().get(MSG_0_NAME));
+    assertNull(bundle.getMessages().get(MSG_1_NAME));
+    assertEquals(MSG_2_VIEW_VALUE, bundle.getMessages().get(MSG_2_NAME));
+    assertEquals(MSG_3_VALUE, bundle.getMessages().get(MSG_3_NAME));
+  }
+  
+
+  @Test
+  public void getViewAllAllBundle() throws Exception {
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER, "view1");
+
+    assertEquals(MSG_0_ALL_VALUE, bundle.getMessages().get(MSG_0_NAME));
+    assertNull(bundle.getMessages().get(MSG_1_NAME));
+    assertEquals(MSG_2_VIEW_VALUE, bundle.getMessages().get(MSG_2_NAME));
+    assertNull(bundle.getMessages().get(MSG_3_NAME));
+  }
 
   @Test
   public void getAllAllBundle() throws Exception {
-    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(gadgetSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER, null);
     assertEquals(MSG_0_ALL_VALUE, bundle.getMessages().get(MSG_0_NAME));
     assertNull(bundle.getMessages().get(MSG_1_NAME));
     assertNull(bundle.getMessages().get(MSG_2_NAME));
@@ -204,7 +235,7 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(allAllResponse);
 
     replay(pipeline);
-    MessageBundle bundle = bundleFactory.getBundle(externalSpec, LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(externalSpec, LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
     verify(pipeline);
 
     assertEquals("true", bundle.getMessages().get("lang"));
@@ -221,7 +252,7 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(allAllResponse);
 
     replay(pipeline);
-    MessageBundle bundle = bundleFactory.getBundle(externalSpec, LANG_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(externalSpec, LANG_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
     verify(pipeline);
 
     assertEquals("true", bundle.getMessages().get("lang"));
@@ -237,7 +268,7 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(allAllResponse);
 
     replay(pipeline);
-    MessageBundle bundle = bundleFactory.getBundle(externalSpec, COUNTRY_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(externalSpec, COUNTRY_LOCALE, true, ContainerConfig.DEFAULT_CONTAINER, null);
     verify(pipeline);
 
     assertEquals("true", bundle.getMessages().get("country"));
@@ -251,7 +282,7 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(allAllResponse);
 
     replay(pipeline);
-    MessageBundle bundle = bundleFactory.getBundle(externalSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle = bundleFactory.getBundle(externalSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER, null);
     verify(pipeline);
 
     assertEquals("true", bundle.getMessages().get("all"));
@@ -264,8 +295,8 @@ public class DefaultMessageBundleFactory
     expect(pipeline.execute(isA(HttpRequest.class))).andReturn(response).once();
     replay(pipeline);
 
-    MessageBundle bundle0 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
-    MessageBundle bundle1 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle0 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
+    MessageBundle bundle1 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
 
     verify(pipeline);
 
@@ -274,7 +305,7 @@ public class DefaultMessageBundleFactory
 
   @Test
   public void ignoreCacheDoesNotStore() throws Exception {
-    bundleFactory.getBundle(gadgetSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER);
+    bundleFactory.getBundle(gadgetSpec, new Locale("all", "ALL"), true, ContainerConfig.DEFAULT_CONTAINER, null);
     assertEquals(0, cache.getSize());
   }
 
@@ -303,11 +334,11 @@ public class DefaultMessageBundleFactory
 
     time.set(System.currentTimeMillis());
 
-    MessageBundle bundle0 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle0 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
 
     time.set(time.get() + MAX_AGE + 1);
 
-    MessageBundle bundle1 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
+    MessageBundle bundle1 = bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
 
     verify(pipeline);
 
@@ -322,7 +353,7 @@ public class DefaultMessageBundleFactory
         .andReturn(badResponse).once();
     replay(pipeline);
 
-    bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
+    bundleFactory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
   }
 
   @Test
@@ -332,7 +363,7 @@ public class DefaultMessageBundleFactory
     MessageBundleFactory factory = new DefaultMessageBundleFactory(
         new ImmediateExecutorService(), capturingFetcher, cacheProvider, MAX_AGE);
 
-    factory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER);
+    factory.getBundle(gadgetSpec, LOCALE, false, ContainerConfig.DEFAULT_CONTAINER, null);
 
     assertEquals(MAX_AGE / 1000, capturingFetcher.request.getCacheTtl());
   }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTest.java Sun Apr  3 09:29:25 2011
@@ -20,13 +20,15 @@ package org.apache.shindig.gadgets;
 
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
+
 import org.apache.shindig.common.EasyMockTestCase;
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.gadgets.features.FeatureRegistry;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
-
+import org.apache.shindig.gadgets.spec.View;
 import org.junit.Test;
+import org.w3c.dom.Element;
 
 import java.util.Collection;
 import java.util.List;
@@ -77,7 +79,7 @@ public class GadgetTest extends EasyMock
         .setGadgetFeatureRegistry(registry)
         .setSpec(new GadgetSpec(Uri.parse(SPEC_URL), xml));
     Collection<String> needed = Lists.newArrayList(gadget.getSpec().getModulePrefs().getFeatures().keySet());
-    List<String> returned = Lists.newArrayList();
+    List<String> returned = Lists.newArrayList(needed);
     // Call should only happen once, and be cached from there on out.
     expect(registry.getFeatures(eq(needed))).andReturn(returned).anyTimes();
     replay();
@@ -87,6 +89,70 @@ public class GadgetTest extends EasyMock
     assertSame(returned, requiredFeatures2);
     verify();
   }
+  
+  @Test
+  public void testGetView1Features() throws Exception {
+    String xml = "<Module>" +
+                 "<ModulePrefs title=\"hello\">" +
+                 "<Require feature=\"required1\"/>" +
+                 "<Require feature=\"requiredview1\" views=\"default\"/>" +
+                 "<Require feature=\"requiredview2\" views=\"view2\"/>" +
+                 "</ModulePrefs>" +
+                 "<Content views=\"view1, default\" type=\"html\"/>" +
+                 "<Content views=\"view2\" type=\"html\"/>" +
+                 "</Module>";
+    FeatureRegistry registry = mock(FeatureRegistry.class, true);
+    Gadget gadget = new Gadget()
+    		.setContext(context)
+        .setGadgetFeatureRegistry(registry)
+        .setSpec(new GadgetSpec(Uri.parse(SPEC_URL), xml));
+    Collection<String> needed = Lists.newArrayList(gadget.getSpec().getModulePrefs().getViewFeatures(GadgetSpec.DEFAULT_VIEW).keySet());
+    List<String> returned = Lists.newArrayList(needed);
+    // Call should only happen once, and be cached from there on out.
+    expect(registry.getFeatures(eq(needed))).andReturn(returned).anyTimes();
+    replay();
+    List<String> requiredFeatures = Lists.newArrayList(gadget.getViewFeatures().keySet());
+    assertEquals(returned, requiredFeatures);
+    assertTrue(requiredFeatures.contains("requiredview1"));
+    assertTrue(requiredFeatures.contains("core"));
+    assertTrue(!requiredFeatures.contains("requiredview2"));
+    
+    verify();
+  }
+  
+  @Test
+  public void testGetView2Features() throws Exception {
+    String xml = "<Module>" +
+                 "<ModulePrefs title=\"hello\">" +
+                 "<Require feature=\"required\"/>" +
+                 "<Require feature=\"requiredview1\" views=\"default\"/>" +
+                 "<Require feature=\"requiredview2\" views=\"view2\"/>" +
+                 "</ModulePrefs>" +
+                 "<Content views=\"view1, default\" type=\"html\"/>" +
+                 "<Content views=\"view2\" type=\"html\"/>" +
+                 "</Module>";
+    FeatureRegistry registry = mock(FeatureRegistry.class, true);
+    Gadget gadget = new Gadget()
+        .setContext(context)
+        .setGadgetFeatureRegistry(registry)
+        .setSpec(new GadgetSpec(Uri.parse(SPEC_URL), xml));
+    List<Element> viewEles = Lists.newArrayList();
+    gadget.setCurrentView(new View("view2", viewEles, null));
+    Collection<String> needed = Lists.newArrayList(gadget.getSpec().getModulePrefs().getViewFeatures("view2").keySet());
+    List<String> returned = Lists.newArrayList(needed);
+    // Call should only happen once, and be cached from there on out.
+    expect(registry.getFeatures(eq(needed))).andReturn(returned).anyTimes();
+    replay();
+    List<String> requiredFeatures = Lists.newArrayList(gadget.getViewFeatures().keySet());
+    assertEquals(returned, requiredFeatures);
+    assertEquals(3, requiredFeatures.size());
+    assertTrue(!requiredFeatures.contains("requiredview1"));
+    assertTrue(requiredFeatures.contains("required"));
+    assertTrue(requiredFeatures.contains("core"));
+    assertTrue(requiredFeatures.contains("requiredview2"));
+    
+    verify();
+  }
 
 
   private static class DummyContext extends GadgetContext {

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java Sun Apr  3 09:29:25 2011
@@ -77,7 +77,8 @@ public class HashLockedDomainServiceTest
 
     FeatureRegistry registry = mock(FeatureRegistry.class);
     expect(registry.getFeatures(isA(Collection.class))).andReturn(gadgetFeatures).anyTimes();
-    return new Gadget().setSpec(spec).setGadgetFeatureRegistry(registry);
+
+    return new Gadget().setSpec(spec).setContext(new GadgetContext()).setGadgetFeatureRegistry(registry);
   }
 
   @Before

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java Sun Apr  3 09:29:25 2011
@@ -29,11 +29,11 @@ import java.util.Locale;
  * Simple message bundle factory -- only honors inline bundles.
  */
 public class FakeMessageBundleFactory implements MessageBundleFactory {
-  public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container) {
-    LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
+  public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container, String view) {
+    LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale, view);
     if (localeSpec == null) {
       return MessageBundle.EMPTY;
     }
-    return spec.getModulePrefs().getLocale(locale).getMessageBundle();
+    return spec.getModulePrefs().getLocale(locale, view).getMessageBundle();
   }
 }
\ No newline at end of file

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java Sun Apr  3 09:29:25 2011
@@ -128,7 +128,9 @@ public class RenderingGadgetRewriterTest
         .setContext(context)
         .setPreloads(ImmutableList.<PreloadedData>of())
         .setSpec(spec)
+        .setCurrentView(spec.getView(GadgetSpec.DEFAULT_VIEW))
         .setGadgetFeatureRegistry(featureRegistry);
+    
     // Convenience: by default expect no features requested, by gadget or extern.
     // expectFeatureCalls(...) resets featureRegistry if called again.
     expectFeatureCalls(gadget,
@@ -842,13 +844,56 @@ public class RenderingGadgetRewriterTest
     expect(lr.getResources()).andReturn(ImmutableList.<FeatureResource>of());
     replay(lr);
     expect(featureRegistry.getFeatureResources(same(gadget.getContext()),
-        eq(ImmutableSet.<String>of()), eq(Lists.<String>newArrayList())))
+        eq(ImmutableSet.<String>of()), eq(Lists.<String>newLinkedList())))
         .andReturn(lr);
     final FeatureRegistry.LookupResult lr2 = createMock(FeatureRegistry.LookupResult.class);
     expect(lr2.getResources()).andReturn(ImmutableList.<FeatureResource>of());
     replay(lr2);
+    assertTrue(gadget.getDirectFeatureDeps().contains("core"));
+    assertTrue(gadget.getDirectFeatureDeps().contains("foo"));
+    assertEquals(gadget.getDirectFeatureDeps().size(),2);
     expect(featureRegistry.getFeatureResources(same(gadget.getContext()),
-        eq(ImmutableList.<String>of("foo", "core")), eq(Lists.<String>newArrayList())))
+        eq(Lists.newLinkedList(gadget.getDirectFeatureDeps())), eq(Lists.<String>newLinkedList())))
+        .andAnswer(new IAnswer<FeatureRegistry.LookupResult>() {
+          @SuppressWarnings("unchecked")
+          public FeatureRegistry.LookupResult answer() throws Throwable {
+            List<String> unsupported = (List<String>)getCurrentArguments()[2];
+            unsupported.add("foo");
+            return lr2;
+          }
+        });
+    replay(featureRegistry);
+
+    rewrite(gadget, "");
+  }
+  
+  @Test(expected = RewritingException.class)
+  public void unsupportedViewFeatureThrows() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo' views='default'/>" +
+      "</ModulePrefs>" +
+      "<Content view='default' type='html'/>" +
+      "</Module>";
+
+    Gadget gadget = makeGadgetWithSpec(gadgetXml);
+
+    reset(featureRegistry);
+    FeatureRegistry.LookupResult lr = createMock(FeatureRegistry.LookupResult.class);
+    expect(lr.getResources()).andReturn(ImmutableList.<FeatureResource>of());
+    replay(lr);
+    expect(featureRegistry.getFeatureResources(same(gadget.getContext()),
+        eq(ImmutableSet.<String>of()), eq(Lists.<String>newLinkedList())))
+        .andReturn(lr);
+    final FeatureRegistry.LookupResult lr2 = createMock(FeatureRegistry.LookupResult.class);
+    expect(lr2.getResources()).andReturn(ImmutableList.<FeatureResource>of());
+    replay(lr2);
+    assertTrue(gadget.getDirectFeatureDeps().contains("core"));
+    assertTrue(gadget.getDirectFeatureDeps().contains("foo"));
+    assertEquals(gadget.getDirectFeatureDeps().size(),2);
+    Lists.newLinkedList();
+    expect(featureRegistry.getFeatureResources(same(gadget.getContext()),
+        eq(Lists.newLinkedList(gadget.getDirectFeatureDeps())), eq(Lists.<String>newLinkedList())))
         .andAnswer(new IAnswer<FeatureRegistry.LookupResult>() {
           @SuppressWarnings("unchecked")
           public FeatureRegistry.LookupResult answer() throws Throwable {
@@ -938,6 +983,24 @@ public class RenderingGadgetRewriterTest
     rewrite(gadget, "");
     // rewrite will throw if the optional unsupported feature doesn't work.
   }
+  
+  @Test
+  public void unsupportedViewFeaturesDoNotThrow() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Optional feature='foo'/>" +
+      "  <Optional feature='bar'/>" +
+      "  <Require feature='bar2' views='view1'/>" +
+      "  <Optional feature='bar3' views='view1'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    Gadget gadget = makeGadgetWithSpec(gadgetXml);
+
+    rewrite(gadget, "");
+    // rewrite will throw if the optional unsupported feature doesn't work.
+  }
 
   private JSONArray getPreloadedJson(String content) throws JSONException {
     Pattern preloadPattern
@@ -1061,11 +1124,11 @@ public class RenderingGadgetRewriterTest
                                   List<FeatureResource> externResources) {
     reset(featureRegistry);
     GadgetContext gadgetContext = gadget.getContext();
-    List<String> gadgetFeatures = Lists.newArrayList(gadget.getDirectFeatureDeps());
-    List<String> allFeatures = Lists.newArrayList(gadgetFeatures);
-    List<String> allFeaturesAndLibs = Lists.newArrayList(gadgetFeatures);
+    List<String> gadgetFeatures = Lists.newLinkedList(gadget.getDirectFeatureDeps());
+    List<String> allFeatures = Lists.newLinkedList(gadgetFeatures);
+    List<String> allFeaturesAndLibs = Lists.newLinkedList(gadgetFeatures);
     allFeaturesAndLibs.addAll(externLibs);
-    List<String> emptyList = Lists.newArrayList();
+    List<String> emptyList = Lists.newLinkedList();
     final FeatureRegistry.LookupResult externLr = createMock(FeatureRegistry.LookupResult.class);
     expect(externLr.getResources()).andReturn(externResources);
     replay(externLr);

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/FeatureTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/FeatureTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/FeatureTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/FeatureTest.java Sun Apr  3 09:29:25 2011
@@ -19,8 +19,9 @@
 
 package org.apache.shindig.gadgets.spec;
 
-import org.apache.shindig.common.xml.XmlUtil;
+import java.util.Set;
 
+import org.apache.shindig.common.xml.XmlUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -80,6 +81,32 @@ public class FeatureTest extends Assert 
     assertTrue(params.get("foobar").isEmpty());
     assertNull(feature.getParam("foobar"));
   }
+  
+  
+  @Test
+  public void testViews() throws Exception {
+    String xml = "<Require feature=\"foo\" views=\"view1\">" +
+                 "</Require>";
+    Feature feature = new Feature(XmlUtil.parse(xml));
+    Set<String> views = feature.getViews();
+    assertTrue(views.size() == 1);
+    assertTrue(views.contains("view1"));
+    
+    xml = "<Require feature=\"foo\" views=\"view1, view2\">" +
+    "</Require>";
+		feature = new Feature(XmlUtil.parse(xml));
+		views = feature.getViews();
+		assertTrue(views.size() == 2);
+		assertTrue(views.contains("view1"));
+		assertTrue(views.contains("view2"));
+		
+    xml = "<Require feature=\"foo\">" +
+    "</Require>";
+		feature = new Feature(XmlUtil.parse(xml));
+		views = feature.getViews();
+		assertTrue(views != null);
+		assertTrue(views.size() == 0);
+  }
 
   @Test(expected=SpecParserException.class)
   public void testDoesNotLikeUnnamedFeatures() throws Exception {

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/LocaleSpecTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/LocaleSpecTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/LocaleSpecTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/LocaleSpecTest.java Sun Apr  3 09:29:25 2011
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertEqu
 
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlUtil;
-
 import org.junit.Test;
 
 public class LocaleSpecTest {
@@ -42,6 +41,26 @@ public class LocaleSpecTest {
     assertEquals("US", locale.getCountry());
     assertEquals("rtl", locale.getLanguageDirection());
     assertEquals("http://example.org/msgs.xml", locale.getMessages().toString());
+    assertEquals(0, locale.getViews().size());
+  }
+  
+  @Test
+  public void viewLocale() throws Exception {
+    String xml = "<Locale" +
+                 " lang=\"en\"" +
+                 " country=\"US\"" +
+                 " language_direction=\"rtl\"" +
+                 " messages=\"http://example.org/msgs.xml\"" +
+                 " views=\"view1\"/>";
+
+    LocaleSpec locale = new LocaleSpec(XmlUtil.parse(xml), SPEC_URL);
+    assertEquals("en", locale.getLanguage());
+    assertEquals("US", locale.getCountry());
+    assertEquals("rtl", locale.getLanguageDirection());
+    assertEquals("http://example.org/msgs.xml", locale.getMessages().toString());
+    assertEquals(1, locale.getViews().size());
+    Object[] views = locale.getViews().toArray();
+    assertEquals("view1",views[0].toString());
   }
 
   @Test
@@ -85,7 +104,7 @@ public class LocaleSpecTest {
   @Test
   public void toStringIsSane() throws Exception {
     String xml = "<Locale lang='en' country='US' language_direction='rtl'" +
-                 " messages='foo'>" +
+                 " messages='foo' views='view1, view2'>" +
                  "  <msg name='hello'>World</msg>" +
                  "  <msg name='foo'>Bar</msg>" +
                  "</Locale>";
@@ -97,5 +116,6 @@ public class LocaleSpecTest {
     assertEquals(loc.getMessages(), loc2.getMessages());
     assertEquals(loc.getMessageBundle().getMessages(),
                  loc2.getMessageBundle().getMessages());
+    assertEquals(loc.getViews().toString(),loc2.getViews().toString());
   }
 }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ModulePrefsTest.java Sun Apr  3 09:29:25 2011
@@ -25,15 +25,15 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Locale;
+import java.util.Map;
+
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.XmlUtil;
 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 {
@@ -65,6 +65,11 @@ public class ModulePrefsTest {
         "  <Require feature='require'/>" +
         "  <Optional feature='optional'/>" +
         "  <Preload href='http://example.org' authz='signed'/>" +
+        "	 <Require feature='requiredview1' views='default, view1'/>" +
+        "	 <Require feature='requiredview2' views='view2'/>" +
+        "	 <Require feature='require' views='view2'>" +
+        "	 		<Param name='param_name'>param_value</Param>" +
+        "  </Require>" +
         "  <Icon/>" +
         "  <Locale/>" +
         "  <Link rel='link' href='http://example.org/link'/>" +
@@ -129,6 +134,21 @@ public class ModulePrefsTest {
     assertTrue(extra.containsKey("NavigationItem"));
     assertEquals(1, extra.get("NavigationItem").iterator().next().getChildNodes().getLength());
   }
+  
+  @Test
+  public void substitutionsCopyConstructor() throws Exception{
+    ModulePrefs basePrefs = new ModulePrefs(XmlUtil.parse(FULL_XML), SPEC_URL);
+    Substitutions substituter = new Substitutions();
+    String gadgetXml = "<Module>" +
+    FULL_XML +
+    "<Content type=\"html\"></Content>" +
+    "</Module>";
+    GadgetSpec baseSpec = new GadgetSpec(SPEC_URL, gadgetXml);
+    GadgetSpec spec = baseSpec.substitute(substituter);
+    ModulePrefs subsPrefs = spec.getModulePrefs();
+    assertEquals(basePrefs.toString(), subsPrefs.toString());
+    doAsserts(subsPrefs);
+  }
 
   @Test
   public void basicElementsParseOk() throws Exception {
@@ -147,21 +167,41 @@ public class ModulePrefsTest {
   }
 
   @Test
-  public void getLocale() throws Exception {
+  public void getGlobalLocale() throws Exception {
     String xml = "<ModulePrefs title='locales'>" +
                  "  <Locale lang='en' country='UK' messages='en.xml'/>" +
                  "  <Locale lang='foo' language_direction='rtl'/>" +
                  "</ModulePrefs>";
     ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
-    LocaleSpec spec = prefs.getLocale(new Locale("en", "UK"));
+    LocaleSpec spec = prefs.getGlobalLocale(new Locale("en", "UK"));
     assertEquals("http://example.org/en.xml", spec.getMessages().toString());
 
-    spec = prefs.getLocale(new Locale("foo", "ALL"));
+    spec = prefs.getGlobalLocale(new Locale("foo", "ALL"));
     assertEquals("rtl", spec.getLanguageDirection());
 
-    spec = prefs.getLocale(new Locale("en", "notexist"));
+    spec = prefs.getGlobalLocale(new Locale("en", "notexist"));
     assertNull(spec);
   }
+  
+  @Test
+  public void getViewLocale() throws Exception {
+    String xml = "<ModulePrefs title='locales'>" +
+                 "  <Locale lang='en' country='UK' messages='en.xml' views=\"view1\"/>" +
+                 "  <Locale lang='en' country='US' messages='en_US.xml' views=\"view2\"/>" +
+                 "  <Locale lang='foo' language_direction='rtl'/>" +
+                 "</ModulePrefs>";
+    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(xml), SPEC_URL);
+    LocaleSpec spec = prefs.getGlobalLocale(new Locale("en", "UK"));
+    assertNull(spec);
+    spec = prefs.getLocale(new Locale("en", "UK"),"view1");
+    assertEquals("http://example.org/en.xml", spec.getMessages().toString());
+    spec = prefs.getLocale(new Locale("en", "UK"),"view2");
+    assertNull(spec);
+    spec = prefs.getLocale(new Locale("en", "US"),"view2");
+    assertEquals("http://example.org/en_US.xml", spec.getMessages().toString());
+    spec = prefs.getLocale(new Locale("foo", "ALL"),"view2");
+    assertEquals("rtl", spec.getLanguageDirection());
+  }
 
   @Test
   public void getLinks() throws Exception {
@@ -180,6 +220,35 @@ public class ModulePrefsTest {
     assertEquals(link1Href, prefs.getLinks().get(link1Rel).getHref());
     assertEquals(SPEC_URL.resolve(link2Href), prefs.getLinks().get(link2Rel).getHref());
   }
+  
+  @Test
+  public void getViewFeatures() throws Exception {
+    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(FULL_XML), SPEC_URL);
+    Map<String, Feature> features = prefs.getViewFeatures("default");
+    assertEquals(4, features.size());
+    assertTrue(features.containsKey("requiredview1"));
+    assertTrue(features.containsKey("require"));
+    assertTrue(features.containsKey("optional"));
+    assertTrue(features.containsKey("core"));
+    assertTrue(!features.containsKey("requiredview2"));
+
+    features = prefs.getViewFeatures("view2");
+    assertEquals(4, features.size());
+    assertTrue(features.containsKey("requiredview2"));
+    assertTrue(features.containsKey("require"));
+    assertTrue(features.containsKey("optional"));
+    assertTrue(features.containsKey("core"));
+    assertTrue(!features.containsKey("requiredview1"));
+  }
+  
+  @Test
+  public void getViewFeatureParams() throws Exception {
+    ModulePrefs prefs = new ModulePrefs(XmlUtil.parse(FULL_XML), SPEC_URL);
+    Map<String, Feature> features = prefs.getViewFeatures("view2");
+    String paramValue = features.get("require").getParam("param_name");
+    assertNotNull(paramValue);
+    assertEquals("param_value", paramValue);
+  }
 
   @Test
   public void doSubstitution() throws Exception {

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/UriManagerTestBase.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/UriManagerTestBase.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/UriManagerTestBase.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/UriManagerTestBase.java Sun Apr  3 09:29:25 2011
@@ -115,7 +115,7 @@ public class UriManagerTestBase {
     for (String feature : features) {
       featureMap.put(feature, null);
     }
-    expect(modulePrefs.getFeatures()).andReturn(featureMap).anyTimes();
+    expect(gadget.getViewFeatures()).andReturn(featureMap).anyTimes();
 
     // User prefs
     Map<String, UserPref> specPrefMap = Maps.newLinkedHashMap();

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/variables/VariableSubstituterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/variables/VariableSubstituterTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/variables/VariableSubstituterTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/variables/VariableSubstituterTest.java Sun Apr  3 09:29:25 2011
@@ -121,9 +121,9 @@ public class VariableSubstituterTest {
 
   private static class FakeMessageBundleFactory implements MessageBundleFactory {
 
-    public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container)
+    public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache, String container, String view)
         throws GadgetException {
-      LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
+      LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale, view);
       if (localeSpec == null) {
         return MessageBundle.EMPTY;
       }

Modified: shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java?rev=1088228&r1=1088227&r2=1088228&view=diff
==============================================================================
--- shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java (original)
+++ shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java Sun Apr  3 09:29:25 2011
@@ -68,6 +68,7 @@ public class EndToEndTest {
     "fetchPeopleTest.xml",
     "errorTest.xml",
     "jsonTest.xml",
+    "viewLevelElementsTest.xml",
     "cajaTest.xml",
     "failCajaTest.xml",      
     "failCajaUrlTest.xml",      
@@ -116,6 +117,11 @@ public class EndToEndTest {
   }
 
   @Test
+  public void viewLevelElements() throws Exception {
+    executeAllPageTests("viewLevelElementsTest");
+  }
+  
+  @Test
   @Ignore("Issues with passing the neko dom to caja") // FIXME
   public void cajaJsonParse() throws Exception {
     executeAllPageTests("jsonTest", true /* caja */);
@@ -422,7 +428,7 @@ public class EndToEndTest {
     if (!(page instanceof HtmlPage)) {
       fail("Got wrong page type. Was: " + page.getWebResponse().getContentType());
     }
-    webClient.waitForBackgroundJavaScript(3000);
+    webClient.waitForBackgroundJavaScript(5000);
     return (HtmlPage) page;
   }
 

Added: shindig/trunk/java/server/src/test/resources/endtoend/viewLevelElementsTest.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/java/server/src/test/resources/endtoend/viewLevelElementsTest.xml?rev=1088228&view=auto
==============================================================================
--- shindig/trunk/java/server/src/test/resources/endtoend/viewLevelElementsTest.xml (added)
+++ shindig/trunk/java/server/src/test/resources/endtoend/viewLevelElementsTest.xml Sun Apr  3 09:29:25 2011
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<Module>
+  <ModulePrefs title="EndToEndTest">
+    <Require feature="views" />
+    <Require feature="opensocial-0.8" views="blank"/>
+    <Require feature="osapi">
+      <Param name="paramName">BAD_VALUE</Param>
+    </Require>
+    <Require feature="osapi" views="default">
+      <Param name="paramName">GOOD_VALUE</Param>
+    </Require>
+    <Optional feature="content-rewrite">
+      <Param name="exclude-urls">.*</Param>
+    </Optional>
+    <Locale messages="messages.xml"/>
+    <!-- This bundle should be the one used in substituting messages in the default view -->
+    <Locale messages="viewMessages.xml" views="default"/>
+  </ModulePrefs>
+  <Content type="html">
+    <![CDATA[
+    
+      <span id="substituteHere">__MSG_TEST__</span>
+          
+      <script type="text/javascript" src="/testframework.js"></script>
+      <script type="text/javascript">
+
+        var tests = {
+           viewFeaturesTest: function() {
+              var params = gadgets.util.getFeatureParameters('osapi');
+              var expected = "GOOD_VALUE";
+              assertTrue('Should be GOOD_VALUE', expected == params["paramName"]);
+              assertTrue('Missing content-rewrite feature', gadgets.util.hasFeature("content-rewrite"));
+              assertTrue('Should not have opensocial-0.8 loaded', !gadgets.util.hasFeature("opensocial-0.8"));
+              finished();
+           },
+            
+           viewLocalesTest: function(){
+              var span = document.getElementById('substituteHere');
+              var expected = "test view FTW";
+              assertTrue('Wrong text substituted', expected == span.firstChild.data);
+              finished();
+           }
+        }
+      </script>
+    ]]>
+  </Content>
+  <Content type="html" view="blank">
+    <![CDATA[
+      <h1>Blank</h1>
+    ]]>
+  </Content>
+</Module>

Added: shindig/trunk/java/server/src/test/resources/endtoend/viewMessages.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/java/server/src/test/resources/endtoend/viewMessages.xml?rev=1088228&view=auto
==============================================================================
--- shindig/trunk/java/server/src/test/resources/endtoend/viewMessages.xml (added)
+++ shindig/trunk/java/server/src/test/resources/endtoend/viewMessages.xml Sun Apr  3 09:29:25 2011
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<messagebundle>
+  <msg name="TEST">test view FTW</msg>
+</messagebundle>