Author: awiner
Date: Thu Mar 26 18:55:18 2009
New Revision: 758799

URL: http://svn.apache.org/viewvc?rev=758799&view=rev
Log:
SHINDIG-748: OpenSocial Templates (in part): server-side processing by default, 
and spec compliance improved

Improve spec compliance in several ways:
- Added "support" for @autoUpdate attribute (see below)
- Added support for @cur attribute
- Fixed behavior of ${Cur}: equal to ${Top} to start, null when starting tags
- Added tests for the above

Moved to server-side rendering as the default.
- Server-side processing can be disabled with:
    <Param name="disableAutoProcessing">true</Param>
- Server-side templates are processed, but no longer deleted if @autoUpdate is 
set.  Further work
  will be needed to actually hook the client-side processing up to the 
server-side rendered
  results.
- The opensocial-templates feature is removed if:
    - disableAutoProcessing is not set
    - No @name script blocks were found
    - No blocks have @autoUpdate set to true
    - No blocks have @require that can't be satisfied

Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
    
incubator/shindig/trunk/java/server/src/test/resources/endtoend/opensocial-templates/ost_test.xml
    
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
 Thu Mar 26 18:55:18 2009
@@ -45,6 +45,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -59,8 +60,8 @@
 
   public final static Set<String> TAGS = ImmutableSet.of("script");
 
-  /** A temporary parameter while server-side templating is in development */
-  static final String SERVER_TEMPLATING_PARAM = "process-on-server";
+  /** Set to true to block auto-processing of templates */
+  static final Object DISABLE_AUTO_PROCESSING_PARAM = "disableAutoProcessing";
 
   /**
    * Provider of the processor.  TemplateRewriters are stateless and 
multithreaded,
@@ -102,13 +103,13 @@
   }
 
   /**
-   * For now, only enable server-side templating when the feature contains:
+   * Disable server-side templating when the feature contains:
    * <pre>
-   *   &lt;Param name="process-on-server"&gt;true&lt;/Param&gt;
+   *   &lt;Param name="disableAutoProcessing"&gt;true&lt;/Param&gt;
    * </pre>
    */
   private boolean isServerTemplatingEnabled(Feature f) {
-    return 
("true".equalsIgnoreCase(f.getParams().get(SERVER_TEMPLATING_PARAM)));
+    return 
(!"true".equalsIgnoreCase(f.getParams().get(DISABLE_AUTO_PROCESSING_PARAM)));
   }
 
   private RewriterResults rewriteImpl(Gadget gadget, MutableContent content)
@@ -160,50 +161,57 @@
   
   private RewriterResults processInlineTemplates(Gadget gadget, MutableContent 
content,
       List<Element> allTemplates, TagRegistry registry) throws GadgetException 
{
-    final Map<String, JSONObject> pipelinedData = content.getPipelinedData();
+    Map<String, JSONObject> pipelinedData = content.getPipelinedData();
 
-    List<Element> templates = ImmutableList.copyOf(
-        Iterables.filter(allTemplates, new Predicate<Element>() {
-      public boolean apply(Element element) {
-        String name = element.getAttribute("name");
-        String tag = element.getAttribute("tag");
-        String require = element.getAttribute("require");
-        // Templates with "tag" or "name" can't be processed;  templates
-        // that require data that isn't available on the server can't
-        // be processed either
-        return "".equals(name)
-            && "".equals(tag)
-            && checkRequiredData(require, pipelinedData.keySet());
-      }
-    }));
-
-    if (templates.isEmpty()) {
-      return null;
-    }
-
-    TemplateContext templateContext = new TemplateContext(
-        gadget.getContext(), pipelinedData);
-    
-    MessageBundle bundle = messageBundleFactory.getBundle(gadget.getSpec(),
-        gadget.getContext().getLocale(), gadget.getContext().getIgnoreCache());
-    MessageELResolver messageELResolver = new MessageELResolver(expressions, 
bundle);
-
-    for (Element template : templates) {
-      DocumentFragment result = processor.get().processTemplate(
-          template, templateContext, messageELResolver, registry);
-      // Note: replaceNode errors when replacing Element with DocumentFragment
-      template.getParentNode().insertBefore(result, template);
-      // TODO: clients that need to update data that is initially available,
-      // e.g. paging through friend lists, will still need the template
-      template.getParentNode().removeChild(template);
-    }
-
-    // TODO: Deactivate the "os-templates" feature if all templates have
-    // been rendered.
-    // Note: This may not always be correct behavior since client may want
-    // some purely client-side templating (such as from libraries)
-    MutableContent.notifyEdit(content.getDocument());
-    return RewriterResults.cacheableIndefinitely();
+    // If true, client-side processing will be needed
+    boolean needsFeature = false;
+    List<Element> templates = Lists.newArrayList();
+    for (Element element : allTemplates) {
+      String name = element.getAttribute("name");
+      String tag = element.getAttribute("tag");
+      String require = element.getAttribute("require");
+
+      if (!"".equals(name) ||
+          !checkRequiredData(require, pipelinedData.keySet())) {
+        // Can't be processed on the server at all;  keep client-side 
processing
+        needsFeature = true;
+      } else if ("".equals(tag)) {
+        templates.add(element);
+      }
+    }
+    
+    if (!templates.isEmpty()) {
+      TemplateContext templateContext = new TemplateContext(
+          gadget.getContext(), pipelinedData);
+      
+      MessageBundle bundle = messageBundleFactory.getBundle(gadget.getSpec(),
+          gadget.getContext().getLocale(), 
gadget.getContext().getIgnoreCache());
+      MessageELResolver messageELResolver = new MessageELResolver(expressions, 
bundle);
+  
+      for (Element template : templates) {
+        DocumentFragment result = processor.get().processTemplate(
+            template, templateContext, messageELResolver, registry);
+        template.getParentNode().insertBefore(result, template);
+        if ("true".equals(template.getAttribute("autoUpdate"))) {
+          // autoUpdate requires client-side processing.
+          // TODO: give client-side processing some hope of finding the 
pre-rendered content
+          needsFeature = true;
+        } else {
+          template.getParentNode().removeChild(template);
+        }
+      }
+  
+      MutableContent.notifyEdit(content.getDocument());
+    }
+    
+    // Remove the opensocial-templates feature if we've fully processed all
+    // inline templates.
+    // TODO: remove inline custom tags as well.
+    if (!needsFeature) {
+      gadget.removeFeature("opensocial-templates");
+    }
+    
+    return RewriterResults.notCacheable();
   }
 
   /**

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
 Thu Mar 26 18:55:18 2009
@@ -68,6 +68,7 @@
   public static final String ATTRIBUTE_INDEX = "index";
   public static final String ATTRIBUTE_REPEAT = "repeat";
   public static final String ATTRIBUTE_VAR = "var";
+  public static final String ATTRIBUTE_CUR = "cur";
   
   private final Expressions expressions;
   // Reused buffer for creating template output
@@ -310,6 +311,14 @@
       }
     }
 
+    // TODO: the spec is silent on order of evaluation of "cur" relative
+    // to "if" and "repeat"
+    Attr curAttribute = element.getAttributeNode(ATTRIBUTE_CUR);
+    Object oldCur = templateContext.getCur();
+    if (curAttribute != null) {
+      templateContext.setCur(evaluate(curAttribute.getValue(), Object.class, 
null));
+    }
+    
     if (handler != null) {
       handler.process(result, element, this);
     } else {
@@ -327,13 +336,17 @@
       result.appendChild(resultNode);
     }
     
+    if (curAttribute != null) {
+      templateContext.setCur(oldCur);
+    }
   }
 
   private void clearSpecialAttributes(Element element) {
     element.removeAttribute(ATTRIBUTE_IF);
     element.removeAttribute(ATTRIBUTE_REPEAT);
     element.removeAttribute(ATTRIBUTE_INDEX);
-    element.removeAttribute(ATTRIBUTE_VAR);    
+    element.removeAttribute(ATTRIBUTE_VAR);
+    element.removeAttribute(ATTRIBUTE_CUR);
   }
   
   /**

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateContext.java
 Thu Mar 26 18:55:18 2009
@@ -42,6 +42,7 @@
   public TemplateContext(GadgetContext gadgetContext, Map<String, JSONObject> 
top) {
     this.gadgetContext = gadgetContext;
     this.top = top;
+    this.cur = top;
   }
   
   public Map<String, ? extends Object> getTop() {
@@ -49,7 +50,7 @@
   }
   
   public Object getCur() {
-    return cur != null ? cur : top;
+    return cur;
   }
 
   public Object setCur(Object data) {

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/TemplateELResolver.java
 Thu Mar 26 18:55:18 2009
@@ -98,7 +98,7 @@
         }
       }
       
-      // Check current context next.
+      // Check ${Cur} next.
       Object cur = templateContext.getCur();
       // Resolve through "cur" as if it were a value - if 
"isPropertyResolved()"
       // is true, it was handled
@@ -113,14 +113,14 @@
         }
       }
       
-      // Check current scope variables next.
+      // Check ${My} next.
       Map<String, ? extends Object> scope = templateContext.getMy();
       if (scope != null && scope.containsKey(property)) {
         context.setPropertyResolved(true);
         return scope.get(property);
       }
       
-      // Look at Top context last.
+      // Look at ${Top} context last.
       scope = templateContext.getTop();
       if (scope != null && scope.containsKey(property)) {
         context.setPropertyResolved(true);

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
 Thu Mar 26 18:55:18 2009
@@ -18,6 +18,7 @@
  */
 package org.apache.shindig.gadgets.rewrite;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import org.apache.shindig.common.uri.Uri;
@@ -72,8 +73,9 @@
   
   private static final String CONTENT_WITH_TAG =
     "<script type='text/os-template' xmlns:foo='#foo' tag='foo:Bar'>Hello, 
${user.name}</script>";  
-
   
+  private static final String CONTENT_WITH_AUTO_UPDATE =
+    "<script type='text/os-template' autoUpdate='true'>Hello, 
${user.name}</script>";  
   @Before
   public void setUp() {
     Set<TagHandler> handlers = ImmutableSet.of();
@@ -92,6 +94,7 @@
   public void simpleTemplate() throws Exception {
     // Render a simple template
     testExpectingTransform(getGadgetXml(CONTENT_PLAIN), "simple");
+    testFeatureRemoved();
   }
   
   @Test
@@ -104,32 +107,59 @@
   public void requiredDataPresent() throws Exception {
     // Required data is present - render 
     testExpectingTransform(getGadgetXml(CONTENT_REQUIRE), "required data");
+    testFeatureRemoved();
   }
   
   @Test
   public void requiredDataMissing() throws Exception {
     // Required data is missing - don't render
     testExpectingNoTransform(getGadgetXml(CONTENT_REQUIRE_MISSING), "missing 
data");
+    testFeatureNotRemoved();
   }
   
   @Test
   public void nameAttributePresent() throws Exception {
     // Don't render templates with a @name
     testExpectingNoTransform(getGadgetXml(CONTENT_WITH_NAME), "with @name");
+    testFeatureNotRemoved();
   }
   
   @Test
   public void tagAttributePresent() throws Exception {
     // Don't render templates with a @tag
     testExpectingNoTransform(getGadgetXml(CONTENT_WITH_TAG), "with @tag");
+    testFeatureRemoved();
   }
    
   @Test
   public void templateUsingMessage() throws Exception {
     // Render a simple template
     testExpectingTransform(getGadgetXml(CONTENT_WITH_MESSAGE), "simple");
+    testFeatureRemoved();
   }
   
+  @Test
+  public void autoUpdateTemplate() throws Exception {
+    setupGadget(getGadgetXml(CONTENT_WITH_AUTO_UPDATE));
+    rewriter.rewrite(gadget, content);
+    // The template should get transformed, but not removed
+    assertTrue("Template wasn't transformed", 
+        content.getContent().indexOf("Hello, John") > 0);
+    assertTrue("Template tag was removed",
+        content.getContent().contains("text/os-template"));
+    testFeatureNotRemoved();
+  }
+
+  private void testFeatureRemoved() {
+    assertTrue("Feature wasn't removed",
+        gadget.getRemovedFeatures().contains("opensocial-templates"));
+  }
+
+  private void testFeatureNotRemoved() {
+    assertFalse("Feature was removed",
+        gadget.getRemovedFeatures().contains("opensocial-templates"));
+  }
+
   private void testExpectingTransform(String code, String condition) throws 
Exception {
     setupGadget(code);
     rewriter.rewrite(gadget, content);
@@ -170,9 +200,7 @@
   
   private static String getGadgetXml(String content, boolean requireFeature) {
     String feature = requireFeature ?
-        "<Require feature='opensocial-templates'>" +
-        "  <Param name='" + TemplateRewriter.SERVER_TEMPLATING_PARAM + 
"'>true</Param>" +
-        "</Require>" : "";
+        "<Require feature='opensocial-templates'/>" : "";
     return "<Module>" + "<ModulePrefs title='Title'>"
         + feature
         + "  <Locale>"

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
 Thu Mar 26 18:55:18 2009
@@ -93,6 +93,26 @@
   }
   
   @Test
+  public void testTopVariable() throws Exception {
+    String output = executeTemplate("${Top.foo.title}");
+    assertEquals("bar", output);
+  }
+  
+  @Test
+  public void testCurVariable() throws Exception {
+    // Cur starts as Top
+    String output = executeTemplate("${Cur.foo.title}");
+    assertEquals("bar", output);
+  }
+  
+  @Test
+  public void testMyVariable() throws Exception {
+    // My starts as null
+    String output = executeTemplate("${My.foo.title}");
+    assertEquals("", output);
+  }
+  
+  @Test
   public void testPlainText() throws Exception {
     // Verify that plain text is not interfered with, or incorrectly escaped
     String output = executeTemplate("<span>foo&amp;&bar</span>");
@@ -145,6 +165,12 @@
   }
   
   @Test
+  public void testCurAttribute() throws Exception {
+    String output = executeTemplate("<span 
cur=\"${user.name}\">${first}</span>");
+    assertEquals("<span>John</span>", output);
+  }
+  
+  @Test
   public void testConditional() throws Exception {
     String output = executeTemplate(
         "<span repeat=\"${toys}\">" +

Modified: 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/opensocial-templates/ost_test.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/resources/endtoend/opensocial-templates/ost_test.xml?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/opensocial-templates/ost_test.xml
 (original)
+++ 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/opensocial-templates/ost_test.xml
 Thu Mar 26 18:55:18 2009
@@ -20,7 +20,9 @@
 <Module>
   <ModulePrefs title="TemplatesEndToEndTest">
     <Require feature="opensocial-0.8"/>
-    <Require feature="opensocial-templates"/>
+    <Require feature="opensocial-templates">
+      <Param name="disableAutoProcessing">true</Param>
+    </Require>
   </ModulePrefs>
   <Content type="html">
     <![CDATA[

Modified: 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml?rev=758799&r1=758798&r2=758799&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
 (original)
+++ 
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
 Thu Mar 26 18:55:18 2009
@@ -20,9 +20,7 @@
 <Module>
   <ModulePrefs title="EndToEndTest">
     <Require feature="opensocial-data" />
-    <Require feature="opensocial-templates">
-      <Param name="process-on-server">true</Param>     
-    </Require>
+    <Require feature="opensocial-templates"/>
   </ModulePrefs>
   <Content type="html">
     <![CDATA[


Reply via email to