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>
- * <Param name="process-on-server">true</Param>
+ * <Param name="disableAutoProcessing">true</Param>
* </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&&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[