Author: awiner
Date: Tue Mar 10 00:19:40 2009
New Revision: 751919
URL: http://svn.apache.org/viewvc?rev=751919&view=rev
Log:
SHINDIG-748: OpenSocial templates (in part)
- Add support for ${Msg} variable in server-side templates, including recursive
EL execution
Added:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java
(with props)
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
(with props)
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java
(with props)
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/TemplateELResolver.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/TemplateRewriterTest.java
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=751919&r1=751918&r2=751919&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
Tue Mar 10 00:19:40 2009
@@ -18,22 +18,28 @@
package org.apache.shindig.gadgets.rewrite;
import org.apache.shindig.common.xml.DomUtil;
+import org.apache.shindig.expressions.Expressions;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetELResolver;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.MessageBundleFactory;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.spec.Feature;
+import org.apache.shindig.gadgets.spec.MessageBundle;
+import org.apache.shindig.gadgets.templates.MessageELResolver;
import org.apache.shindig.gadgets.templates.TemplateContext;
import org.apache.shindig.gadgets.templates.TemplateProcessor;
+import org.json.JSONObject;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
-import org.json.JSONObject;
-import org.w3c.dom.DocumentFragment;
-import org.w3c.dom.Element;
+import javax.el.CompositeELResolver;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -61,10 +67,15 @@
* processors are not.
*/
private final Provider<TemplateProcessor> processor;
+ private final MessageBundleFactory messageBundleFactory;
+ private final Expressions expressions;
@Inject
- public TemplateRewriter(Provider<TemplateProcessor> processor) {
+ public TemplateRewriter(Provider<TemplateProcessor> processor,
+ MessageBundleFactory messageBundleFactory, Expressions expressions) {
this.processor = processor;
+ this.messageBundleFactory = messageBundleFactory;
+ this.expressions = expressions;
}
public RewriterResults rewrite(HttpRequest request, HttpResponse original,
@@ -76,7 +87,13 @@
Feature f = gadget.getSpec().getModulePrefs().getFeatures()
.get("opensocial-templates");
if (f != null && isServerTemplatingEnabled(f)) {
- return rewriteImpl(gadget, content);
+ try {
+ return rewriteImpl(gadget, content);
+ } catch (GadgetException ge) {
+ // TODO: Rewriter interface needs to be modified to handle
GadgetException or
+ // RewriterException or something along those lines.
+ throw new RuntimeException(ge);
+ }
}
return null;
}
@@ -91,7 +108,8 @@
return
("true".equalsIgnoreCase(f.getParams().get(SERVER_TEMPLATING_PARAM)));
}
- private RewriterResults rewriteImpl(Gadget gadget, MutableContent content) {
+ private RewriterResults rewriteImpl(Gadget gadget, MutableContent content)
+ throws GadgetException {
List<Element> tagList =
DomUtil.getElementsByTagNameCaseInsensitive(content.getDocument(), TAGS);
final Map<String, JSONObject> pipelinedData = content.getPipelinedData();
@@ -119,10 +137,17 @@
TemplateContext templateContext = new TemplateContext(pipelinedData);
GadgetELResolver globalGadgetVars = new
GadgetELResolver(gadget.getContext());
+
+ MessageBundle bundle = messageBundleFactory.getBundle(gadget.getSpec(),
+ gadget.getContext().getLocale(), gadget.getContext().getIgnoreCache());
+ MessageELResolver messageELResolver = new MessageELResolver(expressions,
bundle);
+ CompositeELResolver resolvers = new CompositeELResolver();
+ resolvers.add(globalGadgetVars);
+ resolvers.add(messageELResolver);
for (Element template : templates) {
DocumentFragment result = processor.get().processTemplate(
- template, templateContext, globalGadgetVars);
+ template, templateContext, resolvers);
// Note: replaceNode errors when replacing Element with DocumentFragment
template.getParentNode().insertBefore(result, template);
// TODO: clients that need to update data that is initially available,
Added:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java?rev=751919&view=auto
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java
(added)
+++
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java
Tue Mar 10 00:19:40 2009
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import org.apache.shindig.expressions.Expressions;
+import org.apache.shindig.gadgets.spec.MessageBundle;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+
+import com.google.common.collect.Lists;
+
+/**
+ * ELResolver for the Msg property in templates.
+ */
+public class MessageELResolver extends ELResolver {
+ public static final String PROPERTY_MSG = "Msg";
+ private final MessageBundle bundle;
+ private final Expressions expressions;
+
+ public MessageELResolver(Expressions expressions, MessageBundle bundle) {
+ this.expressions = expressions;
+ this.bundle = bundle;
+ }
+
+ @Override
+ public Class<?> getCommonPropertyType(ELContext context, Object base) {
+ if (base == null) {
+ return String.class;
+ }
+
+ return null;
+ }
+
+ @Override
+ public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
+ Object base) {
+ return null;
+ }
+
+ @Override
+ public Class<?> getType(ELContext context, Object base, Object property) {
+ // TODO: implement
+ return null;
+ }
+
+ @Override
+ public Object getValue(ELContext context, Object base, Object property) {
+ if ((base == null) && PROPERTY_MSG.equals(property)) {
+ context.setPropertyResolved(true);
+ return bundle;
+ } else if (base instanceof MessageBundle) {
+ String text = bundle.getMessages().get(property);
+ if (text == null) {
+ context.setPropertyResolved(true);
+ return null;
+ }
+
+ List<Object> properties = null;
+ try {
+ properties = pushCurrentProperty(context, property);
+ context.setPropertyResolved(false);
+ return expressions.parse(text, Object.class).getValue(context);
+ } finally {
+ popProperty(properties);
+ context.setPropertyResolved(true);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Track the set of message bundle properties being evaluated. We allow
+ * recursion, but don't want to allow infinite self-recursion (though the
+ * stack overflows quickly).
+ */
+ private List<Object> pushCurrentProperty(ELContext context, Object property)
{
+ @SuppressWarnings("unchecked")
+ List<Object> propertyList = (List<Object>)
context.getContext(MessageELResolver.class);
+ if (propertyList == null) {
+ propertyList = Lists.newArrayList();
+ context.putContext(MessageELResolver.class, propertyList);
+ } else {
+ if (propertyList.contains(property)) {
+ throw new ELException("Recursive invocation of message bundle
properties");
+ }
+ }
+
+ propertyList.add(property);
+ return propertyList;
+ }
+
+ private void popProperty(List<Object> properties) {
+ if (properties != null) {
+ properties.remove(properties.size() - 1);
+ }
+ }
+
+ @Override
+ public boolean isReadOnly(ELContext context, Object base, Object property) {
+ if ((base == null) && PROPERTY_MSG.equals(property)) {
+ context.setPropertyResolved(true);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void setValue(ELContext context, Object base, Object property, Object
value) {
+ if ((base == null) && PROPERTY_MSG.equals(property)) {
+ throw new PropertyNotWritableException();
+ }
+ }
+}
Propchange:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/MessageELResolver.java
------------------------------------------------------------------------------
svn:eol-style = native
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=751919&r1=751918&r2=751919&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
Tue Mar 10 00:19:40 2009
@@ -34,6 +34,8 @@
* ELResolver used to process OpenSocial templates. Provides three variables:
* <ul>
* <li>"Top": Global values </li>
+ * <li>"Cur": Current template variable</li>
+ * <li>"Context": Miscellaneous contextual information</li>
*/
public class TemplateELResolver extends ELResolver {
public static final String PROPERTY_TOP = "Top";
Added:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java?rev=751919&view=auto
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
(added)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
Tue Mar 10 00:19:40 2009
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.render;
+
+import org.apache.shindig.gadgets.MessageBundleFactory;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.LocaleSpec;
+import org.apache.shindig.gadgets.spec.MessageBundle;
+
+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) {
+ LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
+ if (localeSpec == null) {
+ return MessageBundle.EMPTY;
+ }
+ return spec.getModulePrefs().getLocale(locale).getMessageBundle();
+ }
+}
\ No newline at end of file
Propchange:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/FakeMessageBundleFactory.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java?rev=751919&r1=751918&r2=751919&view=diff
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
(original)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
Tue Mar 10 00:19:40 2009
@@ -35,7 +35,6 @@
import org.apache.shindig.gadgets.GadgetFeature;
import org.apache.shindig.gadgets.GadgetFeatureRegistry;
import org.apache.shindig.gadgets.JsLibrary;
-import org.apache.shindig.gadgets.MessageBundleFactory;
import org.apache.shindig.gadgets.UrlGenerator;
import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
import org.apache.shindig.gadgets.parse.ParseModule;
@@ -43,8 +42,6 @@
import org.apache.shindig.gadgets.preload.PreloadedData;
import org.apache.shindig.gadgets.rewrite.MutableContent;
import org.apache.shindig.gadgets.spec.GadgetSpec;
-import org.apache.shindig.gadgets.spec.LocaleSpec;
-import org.apache.shindig.gadgets.spec.MessageBundle;
import org.apache.shindig.gadgets.spec.View;
import org.json.JSONArray;
import org.json.JSONException;
@@ -55,7 +52,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@@ -737,22 +733,6 @@
}
}
- /**
- * Simple message bundle factory -- only honors inline bundles.
- */
- private static class FakeMessageBundleFactory implements
MessageBundleFactory {
- protected FakeMessageBundleFactory() {
- }
-
- public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean
ignoreCache) {
- LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
- if (localeSpec == null) {
- return MessageBundle.EMPTY;
- }
- return spec.getModulePrefs().getLocale(locale).getMessageBundle();
- }
- }
-
private static class FakeUrlGenerator implements UrlGenerator {
protected FakeUrlGenerator() {
}
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=751919&r1=751918&r2=751919&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
Tue Mar 10 00:19:40 2009
@@ -26,6 +26,7 @@
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.parse.ParseModule;
import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
+import org.apache.shindig.gadgets.render.FakeMessageBundleFactory;
import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.gadgets.spec.SpecParserException;
import org.apache.shindig.gadgets.templates.DefaultTemplateProcessor;
@@ -57,6 +58,9 @@
private static final String CONTENT_PLAIN =
"<script type='text/os-template'>Hello, ${user.name}</script>";
+ private static final String CONTENT_WITH_MESSAGE =
+ "<script type='text/os-template'>Hello, ${Msg.name}</script>";
+
private static final String CONTENT_REQUIRE =
"<script type='text/os-template' require='user'>Hello,
${user.name}</script>";
@@ -79,7 +83,9 @@
return new DefaultTemplateProcessor(Expressions.sharedInstance(),
new TagRegistry(handlers));
}
- });
+ },
+ new FakeMessageBundleFactory(),
+ new Expressions());
}
@Test
@@ -118,6 +124,12 @@
testExpectingNoTransform(getGadgetXml(CONTENT_WITH_TAG), "with @tag");
}
+ @Test
+ public void templateUsingMessage() throws Exception {
+ // Render a simple template
+ testExpectingTransform(getGadgetXml(CONTENT_WITH_MESSAGE), "simple");
+ }
+
private void testExpectingTransform(String code, String condition) throws
Exception {
setupGadget(code);
rewriter.rewrite(gadget, content);
@@ -162,7 +174,11 @@
" <Param name='" + TemplateRewriter.SERVER_TEMPLATING_PARAM +
"'>true</Param>" +
"</Require>" : "";
return "<Module>" + "<ModulePrefs title='Title'>"
- + feature + "</ModulePrefs>"
+ + feature
+ + " <Locale>"
+ + " <msg name='name'>John</msg>"
+ + " </Locale>"
+ + "</ModulePrefs>"
+ "<Content>"
+ " <![CDATA[" + content + "]]>"
+ "</Content></Module>";
Added:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java?rev=751919&view=auto
==============================================================================
---
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java
(added)
+++
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java
Tue Mar 10 00:19:40 2009
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets.templates;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.shindig.common.xml.XmlUtil;
+import org.apache.shindig.expressions.Expressions;
+import org.apache.shindig.gadgets.spec.MessageBundle;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+
+public class MessageELResolverTest {
+ static private final String MESSAGE_BUNDLE =
+ "<messagebundle>" +
+ "<msg name='hello'>world</msg>" +
+ "<msg name='number'>${1+1}</msg>" +
+ "<msg name='concat'>${Msg.hello} ${Msg.number}</msg>" +
+ "<msg name='multiLevel'>${Msg.concat} ${Msg.concat}</msg>" +
+ // Self-recursive EL, should fail
+ "<msg name='recurse'>${Msg.recurse}</msg>" +
+ // Mutually recursive EL, should fail
+ "<msg name='mutual1'>${Msg.mutual2}</msg>" +
+ "<msg name='mutual2'>${Msg.mutual1}</msg>" +
+ "</messagebundle>";
+ private MessageBundle messageBundle;
+ private Expressions expressions;
+ private ELContext context;
+
+ @Before
+ public void setUp() throws Exception {
+ messageBundle = new MessageBundle(XmlUtil.parse(MESSAGE_BUNDLE));
+ expressions = new Expressions();
+ context = expressions.newELContext(new MessageELResolver(expressions,
messageBundle));
+ }
+
+ @Test
+ public void basicExpression() {
+ assertEquals("world", expressions.parse("${Msg.hello}",
String.class).getValue(context));
+ }
+
+ @Test
+ public void nullForMissingProperty() {
+ assertNull(expressions.parse("${Msg.notThere}",
Object.class).getValue(context));
+ }
+
+ @Test
+ public void innerEvaluation() {
+ assertEquals(2, expressions.parse("${Msg.number}",
Integer.class).getValue(context));
+ }
+
+ @Test
+ public void recursiveEvaluation() {
+ assertEquals("world 2", expressions.parse("${Msg.concat}",
String.class).getValue(context));
+ }
+
+ @Test
+ public void multiLevelRecursiveEvaluation() {
+ assertEquals("world 2 world 2", expressions.parse("${Msg.multiLevel}",
String.class).getValue(context));
+ }
+
+ @Test(expected = ELException.class)
+ public void failsInsteadOfInfiniteRecursion() {
+ expressions.parse("${Msg.recurse}", String.class).getValue(context);
+ }
+
+ @Test(expected = ELException.class)
+ public void failsInsteadOfMutualInfiniteRecursion() {
+ expressions.parse("${Msg.mutual1}", String.class).getValue(context);
+ }
+}
Propchange:
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/MessageELResolverTest.java
------------------------------------------------------------------------------
svn:eol-style = native