/*
* 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.rewrite;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.xml.XmlUtil;
import org.apache.shindig.config.AbstractContainerConfig;
import org.apache.shindig.expressions.Expressions;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetException;
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.AbstractTagHandler;
import org.apache.shindig.gadgets.templates.DefaultTagRegistry;
import org.apache.shindig.gadgets.templates.DefaultTemplateProcessor;
import org.apache.shindig.gadgets.templates.TagHandler;
import org.apache.shindig.gadgets.templates.TemplateLibrary;
import org.apache.shindig.gadgets.templates.TemplateLibraryFactory;
import org.apache.shindig.gadgets.templates.TemplateProcessor;
import org.apache.shindig.gadgets.templates.XmlTemplateLibrary;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Provider;
/**
* Tests for TemplateRewriter
*/
public class TemplateRewriterTest {
private GadgetSpec gadgetSpec;
private Gadget gadget;
private MutableContent content;
private TemplateRewriter rewriter;
private final Map<String, Object> config = Maps.newHashMap();
private static final Uri GADGET_URI = Uri.parse("http://example.org/gadget.php");
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>";
private static final String CONTENT_REQUIRE_MISSING =
"<script type='text/os-template' require='foo'>Hello, ${user.name}</script>";
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>";
private static final String TEMPLATE_LIBRARY =
"<Templates xmlns:my='#my'>" +
" <Namespace prefix='my' url='#my'/>" +
" <JavaScript>script</JavaScript>" +
" <Style>style</Style>" +
" <Template tag='my:Tag1'>external1</Template>" +
" <Template tag='my:Tag2'>external2</Template>" +
" <Template tag='my:Tag3'>external3</Template>" +
" <Template tag='my:Tag4'>external4</Template>" +
"</Templates>";
private static final String TEMPLATE_LIBRARY_URI = "http://example.org/library.xml";
private static final String CONTENT_WITH_TAG_FROM_LIBRARY =
"<script type='text/os-template' xmlns:my='#my'><my:Tag4/></script>"; ;
private static final String CONTENT_TESTING_PRECEDENCE_RULES =
"<script type='text/os-template' xmlns:my='#my' tag='my:Tag1'>inline1</script>" +
"<script type='text/os-template' xmlns:my='#my' tag='my:Tag2'>inline2</script>" +
"<script type='text/os-template' xmlns:my='#my' tag='my:Tag3'>inline3</script>" +
"<script type='text/os-template' xmlns:my='#my'><my:Tag1/><my:Tag2/><my:Tag3/><my:Tag4/></script>"; ;
@Before
public void setUp() {
Set<TagHandler> handlers = ImmutableSet.of(testTagHandler("Tag1", "default1"));
rewriter = new TemplateRewriter(
new Provider<TemplateProcessor>() {
public TemplateProcessor get() {
return new DefaultTemplateProcessor(Expressions.forTesting());
}
},
new FakeMessageBundleFactory(),
Expressions.forTesting(),
new DefaultTagRegistry(handlers),
new FakeTemplateLibraryFactory(),
new FakeContainerConfig());
}
private static TagHandler testTagHandler(String name, final String content) {
return new AbstractTagHandler("#my", name) {
public void process(Node result, Element tag, TemplateProcessor processor) {
result.appendChild(result.getOwnerDocument().createTextNode(content));
}
};
}
@Test
public void simpleTemplate() throws Exception {
// Render a simple template
testExpectingTransform(getGadgetXml(CONTENT_PLAIN), "simple");
testFeatureRemoved();
}
@Test
public void noTemplateFeature() throws Exception {
// Without opensocial-templates feature, shouldn't render
testExpectingNoTransform(getGadgetXml(CONTENT_PLAIN, false), "no feature");
}
@Test
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 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"));
assertTrue("ID span was not created",
content.getContent().contains("<span id=\"_T_template_auto0\">"));
testFeatureNotRemoved();
}
@Test
public void templateWithLibrary() throws Exception {
setupGadget(getGadgetXmlWithLibrary(CONTENT_WITH_TAG_FROM_LIBRARY));
rewriter.rewrite(gadget, content);
assertTrue("Script not inserted", content.getContent().indexOf(
"<script type=\"text/javascript\">script</script>") > 0);
assertTrue("Style not inserted", content.getContent().indexOf(
"<style type=\"text/css\">style</style>") > 0);
assertTrue("Tag not executed", content.getContent().indexOf(
"external4") > 0);
testFeatureRemoved();
}
@Test
public void tagPrecedenceRules() throws Exception {
// Tag definitions include:
// Default handlers: tag1 default1
// OSML: tag1 osml1 tag2 osml2
// inline tags: tag1 inline1 tag2 inline2 tag3 inline3
// External tags: tag1 external1 tag2 external2 tag3 external3 tag4 external4
config.put("${Cur['gadgets.features'].osml.library}",
"org/apache/shindig/gadgets/rewrite/OSML_test.xml");
setupGadget(getGadgetXmlWithLibrary(CONTENT_TESTING_PRECEDENCE_RULES));
rewriter.rewrite(gadget, content);
assertTrue("Precedence rules violated",
content.getContent().indexOf("default1osml2inline3external4") > 0);
testFeatureRemoved();
}
@Test
public void tagPrecedenceRulesWithoutOSML() throws Exception {
// Tag definitions include:
// Default handlers: tag1 default1
// OSML: tag1 osml1 tag2 osml2
// inline tags: tag1 inline1 tag2 inline2 tag3 inline3
// External tags: tag1 external1 tag2 external2 tag3 external3 tag4 external4
// Explicitly don't support OSML
config.put("${Cur['gadgets.features'].osml.library}", "");
setupGadget(getGadgetXmlWithLibrary(CONTENT_TESTING_PRECEDENCE_RULES));
rewriter.rewrite(gadget, content);
assertTrue("Precedence rules violated",
content.getContent().indexOf("default1inline2inline3external4") > 0);
testFeatureRemoved();
}
@Test
public void testClientOverride() throws Exception {
// Should normally remove feature
testExpectingTransform(getGadgetXml(CONTENT_PLAIN, true, "true"), "keep client");
testFeatureNotRemoved();
// Should normally keep feature
testExpectingNoTransform(getGadgetXml(CONTENT_WITH_TAG, true, "false"), "remove client");
testFeatureRemoved();
}
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);
assertTrue("Template wasn't transformed (" + condition + ")",
content.getContent().indexOf("Hello, John") > 0);
assertTrue("Template tag wasn't removed (" + condition + ")",
!content.getContent().contains("text/os-template"));
}
private void testExpectingNoTransform(String code, String condition) throws Exception {
setupGadget(code);
rewriter.rewrite(gadget, content);
assertTrue("Template was transformed (" + condition + ")",
content.getContent().indexOf("${user.name}") > 0);
assertTrue("Template tag was removed (" + condition + ")",
content.getContent().indexOf("text/os-template") > 0);
}
private void setupGadget(String gadgetXml) throws SpecParserException, JSONException {
gadgetSpec = new GadgetSpec(GADGET_URI, gadgetXml);
gadget = new Gadget();
gadget.setSpec(gadgetSpec);
gadget.setContext(new GadgetContext() {
@Override
public Uri getUrl() {
return GADGET_URI;
}
});
gadget.setCurrentView(gadgetSpec.getView("default"));
content = new MutableContent(new SocialMarkupHtmlParser(
new ParseModule.DOMImplementationProvider().get()), gadget.getCurrentView().getContent());
putPipelinedData("user", new JSONObject("{ name: 'John'}"));
}
private void putPipelinedData(String key, JSONObject data) {
content.addPipelinedData(key, data);
}
private static String getGadgetXml(String content) {
return getGadgetXml(content, true);
}
private static String getGadgetXml(String content, boolean requireFeature) {
return getGadgetXml(content, requireFeature, null);
}
private static String getGadgetXml(String content, boolean requireFeature,
String clientParam) {
String feature = requireFeature ?
"<Require feature='opensocial-templates'" +
(clientParam != null ?
("><Param name='client'>" + clientParam + "</Param></Require>")
: "/>")
: "";
return "<Module>" + "<ModulePrefs title='Title'>"
+ feature
+ " <Locale>"
+ " <msg name='name'>John</msg>"
+ " </Locale>"
+ "</ModulePrefs>"
+ "<Content>"
+ " <![CDATA[" + content + "]]>"
+ "</Content></Module>";
}
private static String getGadgetXmlWithLibrary(String content) {
return "<Module>" + "<ModulePrefs title='Title'>"
+ " <Require feature='opensocial-templates'>"
+ " <Param name='" + TemplateRewriter.REQUIRE_LIBRARY_PARAM + "'>"
+ TEMPLATE_LIBRARY_URI
+ " </Param>"
+ " </Require>"
+ "</ModulePrefs>"
+ "<Content>"
+ " <![CDATA[" + content + "]]>"
+ "</Content></Module>";
}
private static class FakeTemplateLibraryFactory extends TemplateLibraryFactory {
public FakeTemplateLibraryFactory() {
super(null, null);
}
@Override
public TemplateLibrary loadTemplateLibrary(GadgetContext context, Uri uri)
throws GadgetException {
assertEquals(TEMPLATE_LIBRARY_URI, uri.toString());
return new XmlTemplateLibrary(uri, XmlUtil.parseSilent(TEMPLATE_LIBRARY),
TEMPLATE_LIBRARY);
}
}
private class FakeContainerConfig extends AbstractContainerConfig {
@Override
public Object getProperty(String container, String name) {
return config.get(name);
}
}
}