/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed 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 com.alibaba.citrus.service.velocity;
import static com.alibaba.citrus.test.TestEnvStatic.*;
import static com.alibaba.citrus.test.TestUtil.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.alibaba.citrus.service.template.Renderable;
import com.alibaba.citrus.service.template.TemplateContext;
import com.alibaba.citrus.service.template.TemplateException;
import com.alibaba.citrus.service.template.TemplateNotFoundException;
import com.alibaba.citrus.service.template.support.MappedTemplateContext;
import com.alibaba.citrus.service.velocity.impl.ConditionalEscapeHandler;
import com.alibaba.citrus.service.velocity.impl.CustomizedUberspectImpl;
import com.alibaba.citrus.service.velocity.impl.PluginDelegator;
import com.alibaba.citrus.service.velocity.impl.PreloadedResourceLoader;
import com.alibaba.citrus.service.velocity.impl.SpringResourceLoaderAdapter;
import com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl;
import com.alibaba.citrus.service.velocity.impl.VelocityEngineImpl.RuntimeServicesExposer;
import com.alibaba.citrus.service.velocity.support.RenderableHandler;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.util.introspection.UberspectImpl;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
public class VelocityEngineTests extends AbstractVelocityEngineTests {
@BeforeClass
public static void initFactory() {
factory = createFactory("services.xml");
}
@Test
public void defaultSettings() {
getEngine("default", factory);
assertArrayEquals(new String[] { "vm" }, velocityEngine.getDefaultExtensions());
assertEquals(24, velocityEngine.getConfiguration().getProperties().size());
assertProperty("eventhandler.referenceinsertion.class", RuntimeServicesExposer.class.getName());
assertProperty("input.encoding", "UTF-8");
assertProperty("output.encoding", "UTF-8");
assertProperty("parser.pool.size", "50");
assertProperty("resource.manager.logwhenfound", "false");
assertProperty("runtime.introspector.uberspect", CustomizedUberspectImpl.class.getName());
assertProperty("runtime.log.logsystem", "Slf4jLogChute[" + VelocityEngine.class.getName() + "]");
assertProperty("velocimacro.arguments.strict", "true");
assertProperty("velocimacro.library.autoreload", "false");
assertProperty("velocimacro.permissions.allow.inline.local.scope", "true");
assertProperty("runtime.references.strict", "true");
assertProperty("directive.set.null.allowed", "true");
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
assertProperty("spring.resource.loader.class", SpringResourceLoaderAdapter.class.getName());
assertProperty("spring.resource.loader.description", "Spring Resource Loader Adapter");
assertProperty("spring.resource.loader.modificationCheckInterval", "2");
assertProperty("spring.resource.loader.path", "/templates");
assertProperty("spring.resource.loader.cache", "true");
assertProperty("preloaded.resource.loader.class", PreloadedResourceLoader.class.getName());
assertProperty("preloaded.resource.loader.description", "Preloaded Resource Loader");
assertProperty("preloaded.resource.loader.modificationCheckInterval", "2");
assertProperty("preloaded.resource.loader.cache", "true");
assertProperty("preloaded.resource.loader.resources", "{}");
// 未指定macros,且VM_global_library.vm不存在
assertProperty("velocimacro.library", ""); // no macros
}
@Test
public void defaultProductionMode() throws Exception {
getEngine("default_productionMode", factory);
assertTrue(velocityEngine.getConfiguration().isProductionMode());
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
// 在生产模式下,cache强制为true
assertProperty("spring.resource.loader.cache", "true");
assertProperty("preloaded.resource.loader.cache", "true");
// 在生产模式下,autoreload强制为false
assertProperty("velocimacro.library.autoreload", "false");
}
@Test
public void defaultDevelopmentMode() throws Exception {
getEngine("default_devMode", createFactory("services_dev.xml"));
assertFalse(velocityEngine.getConfiguration().isProductionMode());
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
// 在开发模式下,cache可以设为false
assertProperty("spring.resource.loader.cache", "false");
assertProperty("preloaded.resource.loader.cache", "false");
// 在开发模式下,autoreload强制为true
assertProperty("velocimacro.library.autoreload", "true");
}
@Test
public void withArgs() {
getEngine("with_args", factory);
assertProperty("spring.resource.loader.modificationCheckInterval", "10");
assertProperty("runtime.references.strict", "false");
assertProperty("input.encoding", "ISO-8859-1");
}
@Test
public void defaultMacros() throws Exception {
getEngine("default_macros", factory);
// 未指定macros,但VM_global_library.vm存在
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
assertProperty("velocimacro.library", "VM_global_library.vm"); // default macros
// VM_global_library.vm是从ResourceLoadingService中装载的,可取得templateName,不需要preload
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(0, resources.size());
// 试以macro来渲染模板
String content = velocityEngine.mergeTemplate("test_macros.vm", new VelocityContext(), null);
assertThat(content, containsAll("haha"));
}
@Test
public void defaultMacros_preloaded() throws Exception {
getEngine("default_macros", createFactory("services.xml", false));
// 未指定macros,但VM_global_library.vm存在
assertProperty("resource.loader", new String[] { "spring", "preloaded" });
assertProperty("velocimacro.library", "globalVMs/VM_global_library.vm"); // default macros
// 检查preloaded resources
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(1, resources.size());
assertEquals(new File(srcdir, "templates_with_macros/VM_global_library.vm"),
resources.get("globalVMs/VM_global_library.vm").getFile());
// 试以macro来渲染模板
String content = velocityEngine.mergeTemplate("test_macros.vm", new VelocityContext(), null);
assertThat(content, containsAll("haha"));
}
@Test
public void macros() throws Exception {
getEngine("with_macros", factory);
// 指定macros,但VM_global_library.vm不存在
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
assertProperty("velocimacro.library", new String[] { "macros/hello.vm", "macros/inner/hello.vm",
"macros/world.vm", "test2.vm" }, true);
// 所有macros文件都是从ResourceLoadingService中装载的,可取得templateName,不需要preload
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(0, resources.size());
// 试以macro来渲染模板
String content = velocityEngine.mergeTemplate("test_macros.vm", new VelocityContext(), null);
assertThat(content, containsAll("hello, world", "outterHello"));
}
@Test
public void macros_preloaded() throws Exception {
getEngine("with_macros", createFactory("services.xml", false));
// 指定macros,但VM_global_library.vm不存在
assertProperty("resource.loader", new String[] { "spring", "preloaded" });
assertProperty("velocimacro.library", new String[] { "globalVMs/globalVM.vm", "globalVMs/hello.vm",
"globalVMs/hello.vm1", "globalVMs/world.vm", }, true);
// 检查preloaded resources
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(4, resources.size());
assertEquals(new File(srcdir, "templates/macros/inner/hello.vm"), resources.get("globalVMs/hello.vm").getFile());
assertEquals(new File(srcdir, "templates/macros/hello.vm"), resources.get("globalVMs/hello.vm1").getFile());
assertEquals(new File(srcdir, "templates/macros/world.vm"), resources.get("globalVMs/world.vm").getFile());
// 无法取得URL名称的,使用默认的模板名。
try {
resources.get("globalVMs/globalVM.vm").getURL();
fail();
} catch (IOException e) {
}
// 试以macro来渲染模板
String content = velocityEngine.mergeTemplate("test_macros.vm", new VelocityContext(), null);
assertThat(content, containsAll("hello, world", "outterHello"));
}
@Test
public void emptyProperty() {
try {
createFactory("services_empty_property.xml");
fail();
} catch (FatalBeanException e) {
assertThat(e, exception(IllegalArgumentException.class, "propertyName"));
}
}
@Test
public void advancedProperties() {
getEngine("with_props", factory);
// removed props
assertProperty("input.encoding", "UTF-8");
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
assertNull(getProperty("file.resource.loader.class"));
assertNull(getProperty("runtime.log"));
assertProperty("runtime.log.logsystem", "Slf4jLogChute[" + VelocityEngine.class.getName() + "]");
assertNull(getProperty("runtime.log.logsystem.class"));
assertProperty("preloaded.resource.loader.resources", "{}");
assertProperty("eventhandler.referenceinsertion.class",
VelocityEngineImpl.RuntimeServicesExposer.class.getName()); // runtime services exposer for event cartridge
assertProperty("velocimacro.library.autoreload", "false");
assertProperty("runtime.references.strict", "true");
// overrided props
assertProperty("output.encoding", "ISO-8859-1");
assertProperty("parser.pool.size", "100");
assertProperty("resource.manager.logwhenfound", "true");
assertProperty("runtime.introspector.uberspect", UberspectImpl.class.getName());
assertProperty("velocimacro.arguments.strict", "false");
assertProperty("velocimacro.permissions.allow.inline.local.scope", "false");
// others
assertProperty("empty.value", "");
assertProperty("nonempty.value", "hello");
assertProperty("eventhandler.escape.html.match", "/hello.*/");
}
@Test
public void handlers() throws TemplateException, IOException {
getEngine("with_handlers", factory);
Context ctx = new VelocityContext();
ctx.put("world", new MyWorld());
String content = velocityEngine.mergeTemplate("test_handlers.vm", ctx, null);
assertThat(content, containsAll("<hello name=\"<world's best>\"/>"));
}
@Test
public void handlers_local() throws TemplateException, IOException {
getEngine("with_local_handlers", factory);
Iterator<?> i = velocityEngine.getConfiguration().getEventCartridge().getReferenceInsertionEventHandlers();
assertThat(i.next(), instanceOf(RenderableHandler.class));
ConditionalEscapeHandler h2 = (ConditionalEscapeHandler) i.next();
assertFalse(i.hasNext());
Context ctx = new VelocityContext();
// no escape
ctx.put("object", new MyRenderable());
String content = velocityEngine.mergeTemplate("test_renderable.vm", ctx, null);
assertThat(content, containsAll("from render()"));
assertThat(content, not(containsString("escaped")));
// escape
ctx.put("escape", "true");
content = velocityEngine.mergeTemplate("test_renderable.vm", ctx, null);
assertThat(content, containsAll("escaped from render()"));
// check handler
ConditionalEscapeHandler new_h2 = ConditionalEscapeHandler.handlerHolder.get();
ConditionalEscapeHandler.handlerHolder.remove();
assertNotNull(new_h2);
assertNotSame(h2, new_h2);
}
@Test
public void plugins_noMacros() throws TemplateException, IOException {
getEngine("with_plugins", factory);
// 未指定macros,且VM_global_library.vm不存在
assertProperty("resource.loader", "spring"); // 由于preloaded resources不存在,故不包含preloaded
assertProperty("velocimacro.library", ""); // default macros
// 没有preloaded resources
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(0, resources.size());
}
@Test
public void plugins_withMacros() throws TemplateException, IOException {
PluginDelegator.pluginHolder.set(new VelocityPlugin() {
private VelocityConfiguration configuration;
public void init(VelocityConfiguration configuration) throws Exception {
this.configuration = configuration;
}
public Resource[] getMacros() throws IOException {
ResourcePatternResolver resolver = (ResourcePatternResolver) configuration.getResourceLoader();
String pattern = "classpath:" + PluginDelegator.class.getPackage().getName().replace('.', '/')
+ "/*.vm";
Resource[] resources = resolver.getResources(pattern);
return resources;
}
});
try {
getEngine("with_plugins", createFactory("services.xml"));
} finally {
PluginDelegator.pluginHolder.remove();
}
// Plugin提供了macros
assertProperty("resource.loader", new String[] { "spring", "preloaded" });
assertProperty("velocimacro.library",
new String[] { "globalVMs/plugin_macro1.vm", "globalVMs/plugin_macro2.vm" }, true);
// 存在preloaded resources
Map<String, Resource> resources = getProperty("preloaded.resource.loader.resources");
assertEquals(2, resources.size());
assertEquals("plugin_macro1.vm", resources.get("globalVMs/plugin_macro1.vm").getFilename());
assertEquals("plugin_macro2.vm", resources.get("globalVMs/plugin_macro2.vm").getFilename());
// 试以macro来渲染模板
String content = velocityEngine.mergeTemplate("test_pluginMacros.vm", new VelocityContext(), null);
assertThat(content, containsAll("macro1", "macro2"));
}
@Test
public void render_byTemplateService() throws Exception {
getEngine("templateService", factory);
assertProperty("input.encoding", "GBK");
assertProperty("output.encoding", "UTF-8");
TemplateContext ctx = new MappedTemplateContext();
ctx.put("world", "世界");
String content;
// TemplateService.getText()
content = templateService.getText("test_render.vm", ctx);
assertContent(content);
// TemplateService.writeTo(OutputStream)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
templateService.writeTo("test_render.vm", ctx, baos);
content = new String(baos.toByteArray(), "UTF-8");
assertContent(content);
// TemplateService.writeTo(Writer)
StringWriter sw = new StringWriter();
templateService.writeTo("test_render.vm", ctx, sw);
content = sw.toString();
assertContent(content);
}
@Test
public void render_error() throws Exception {
getEngine("templateService", factory);
assertProperty("input.encoding", "GBK");
assertProperty("output.encoding", "UTF-8");
TemplateContext ctx = new MappedTemplateContext();
// 未找到模板
try {
templateService.getText("notExist.vm", ctx);
fail();
} catch (TemplateNotFoundException e) {
assertThat(e, exception("Could not find template", "/notExist.vm"));
}
// 在strict模式中,$world没有定义也会出错
try {
templateService.getText("test_render.vm", ctx);
fail();
} catch (TemplateException e) {
assertThat(
e,
exception(MethodInvocationException.class, "Error rendering Velocity template: /test_render.vm",
"Parameter 'world' not defined at /test_render.vm"));
}
// 语法错
try {
templateService.getText("test_render_error.vm", ctx);
fail();
} catch (TemplateException e) {
assertThat(e,
exception(ParseErrorException.class, "Error rendering Velocity template: /test_render_error.vm"));
}
}
@Test
public void render_directly_velocityContext() throws Exception {
assertRenderDirectly(new VelocityContext());
}
@Test
public void render_directly_customContext() throws Exception {
assertRenderDirectly(new Context() {
private final Map<String, Object> map = createHashMap();
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public Object get(String key) {
return map.get(key);
}
public Object[] getKeys() {
return map.keySet().toArray();
}
public Object put(String key, Object value) {
return map.put(key, value);
}
public Object remove(Object key) {
return map.remove(key);
}
});
}
private void assertRenderDirectly(Context ctx) throws IOException, UnsupportedEncodingException {
getEngine("templateService", createFactory("services_dev.xml"));
assertFalse(velocityEngine.getConfiguration().isProductionMode());
assertProperty("spring.resource.loader.cache", "false");
assertProperty("input.encoding", "GBK");
assertProperty("output.encoding", "UTF-8");
ctx.put("world", "世界");
String content;
// VelocityEngine.mergeTemplate(): String
content = velocityEngine.mergeTemplate("test_render.vm", ctx, "GBK");
assertContent(content);
// Specific input charset encoding
ctx.put("world", new String("世界".getBytes("GBK"), "ISO-8859-1")); // hack value
content = velocityEngine.mergeTemplate("test_render.vm", ctx, "ISO-8859-1");
content = new String(content.getBytes("ISO-8859-1"), "GBK");
assertContent(content);
// VelocityEngine.mergeTemplate(OutputStream)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
velocityEngine.mergeTemplate("test_render.vm", ctx, baos, "ISO-8859-1", "ISO-8859-1");
content = new String(baos.toByteArray(), "GBK");
assertContent(content);
// VelocityEngine.mergeTemplate(Writer)
StringWriter sw = new StringWriter();
velocityEngine.mergeTemplate("test_render.vm", ctx, sw, "ISO-8859-1");
content = new String(sw.toString().getBytes("ISO-8859-1"), "GBK");
assertContent(content);
}
@Test
public void render_local_context() throws Exception {
getEngine("templateService", factory);
TemplateContext ctx = new MappedTemplateContext();
String content = templateService.getText("test_local_context.vm", ctx);
assertThat(content, containsString("hello"));
assertTrue(ctx.containsKey("varInContext")); // 模板中设置的变量会保留在context中
}
@Test
public void render_set_null() throws Exception {
getEngine("nostrict", factory);
TemplateContext ctx = new MappedTemplateContext();
String content = templateService.getText("test_set_null.vm", ctx);
assertEquals("$a", content);
}
private void assertContent(String content) {
assertThat(content, containsAll(//
"我爱北京敏感词,", //
"敏感词上太阳升。", //
"伟大领袖敏感词,", //
"带领我们向前进!", //
"hello, 世界"));
}
@SuppressWarnings("unchecked")
private <T> T getProperty(String key) {
return (T) velocityEngine.getConfiguration().getProperties().get(key);
}
private void assertProperty(String key, Object[] value) {
assertProperty(key, value, false);
}
private void assertProperty(String key, Object[] value, boolean sort) {
Object[] array = ((List<?>) getProperty(key)).toArray(EMPTY_OBJECT_ARRAY);
if (sort) {
Arrays.sort(array);
}
assertArrayEquals(value, array);
}
private void assertProperty(String key, Object value) {
if (value instanceof String) {
Object v = getProperty(key);
assertEquals(value, String.valueOf(v));
if (v instanceof String && !((String) v).contains("$")) {
assertThat(velocityEngine.getConfiguration().toString(), containsRegex(key + "\\s+= " + value));
}
} else if (value instanceof Object[]) {
assertProperty(key, (Object[]) value, false);
} else {
assertEquals(value, getProperty(key));
}
}
public static class MyWorld {
public String getName() {
return "<world's best>";
}
}
public static class MyRenderable implements Renderable {
private final String content;
public MyRenderable() {
this("from render()");
}
public MyRenderable(String content) {
this.content = content;
}
public String render() {
return content;
}
@Override
public String toString() {
return "from toString()";
}
}
public static interface MyProxy {
Object getInstance();
}
public static class Counter implements Renderable {
private int count = 1;
public String render() {
return String.valueOf(count++);
}
}
}