org.zodiac.template.velocity.impl.VelocityConfigurationImpl Maven / Gradle / Ivy
The newest version!
package org.zodiac.template.velocity.impl;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.zodiac.sdk.toolkit.constants.StringPool;
import org.zodiac.sdk.toolkit.util.AssertUtil;
import org.zodiac.sdk.toolkit.util.ToStringBuilder.MapBuilder;
import org.zodiac.sdk.toolkit.util.collection.CollUtil;
import org.zodiac.sdk.toolkit.util.file.FileToolUtil;
import org.zodiac.sdk.toolkit.util.lang.ArrayUtil;
import org.zodiac.sdk.toolkit.util.lang.ObjUtil;
import org.zodiac.sdk.toolkit.util.lang.StrUtil;
import org.apache.velocity.app.event.EventHandler;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.util.ExtProperties;
import org.slf4j.Logger;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.zodiac.template.velocity.VelocityConfiguration;
import org.zodiac.template.velocity.VelocityPlugin;
import org.zodiac.template.velocity.constants.VelocityTemplateConstants;
import org.zodiac.template.velocity.support.RenderableHandler;
/**
* Velocity engine的配置。
*
*/
public class VelocityConfigurationImpl implements VelocityConfiguration {
private final Logger log;
private final ExtProperties properties = new ExtProperties();
private final Map preloadedResources = CollUtil.map();
private final CloneableEventCartridge eventCartridge = new CloneableEventCartridge();
private Object[] plugins;
private ResourceLoader loader;
private boolean productionMode = true;
/*For resource loader.*/
private String path;
private boolean cacheEnabled = true;
private int modificationCheckInterval = VelocityTemplateConstants.DEFAULT_MODIFICATION_CHECK_SECONDS;
/*Strict ref.*/
private boolean strictReference = true;
/*Template charset encoding.*/
private Charset charset;
private int parserPoolSize = VelocityTemplateConstants.DEFAULT_PARSER_POOL_SIZE;
private boolean logWhenFoundTemplate = false;
private boolean exceptionWrongArgs = true;
private boolean inlineLocalScope = true;
/*Global macros.*/
private String[] macros;
public VelocityConfigurationImpl(Logger log) {
this.log = AssertUtil.assertNotNull(log, "log");
}
public ExtProperties getProperties() {
return properties;
}
public CloneableEventCartridge getEventCartridge() {
return eventCartridge;
}
public ResourceLoader getResourceLoader() {
return loader;
}
public void setResourceLoader(ResourceLoader loader) {
this.loader = loader;
}
public boolean isProductionMode() {
return productionMode;
}
/**
* 设置生产模式。默认为true
。
*
* @param productionMode 是否生产模式
*/
public void setProductionMode(boolean productionMode) {
this.productionMode = productionMode;
}
/**
* 设置搜索模板的根目录。默认为classpath:/templates
。
*
* @param path 路径
*/
public void setPath(String path) {
this.path = StrUtil.trimToNull(path);
}
/**
* 是否开启模板缓存。在生产模式下,该模式将被强行开启。
*
* @param cacheEnabled 是否开启缓存
*/
public void setCacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}
/**
* 设置检查模板被修改的间隔秒数。默认为2。
*
* @param modificationCheckInterval 秒数
*/
public void setModificationCheckInterval(int modificationCheckInterval) {
this.modificationCheckInterval = modificationCheckInterval;
}
/**
* 设置strict reference模式。默认为true
。
*
* @param strictReference strict reference
*/
public void setStrictReference(boolean strictReference) {
this.strictReference = strictReference;
}
public void setTemplateEncoding(Charset charset) {
this.charset = charset;
}
public void setParserPoolSize(int parserPoolSize) {
this.parserPoolSize = parserPoolSize;
}
public void setLogWhenFoundTemplate(boolean logWhenFoundTemplate) {
this.logWhenFoundTemplate = logWhenFoundTemplate;
}
public void setExceptionWrongArgs(boolean exceptionWrongArgs) {
this.exceptionWrongArgs = exceptionWrongArgs;
}
public void setInlineLocalScope(boolean inlineLocalScope) {
this.inlineLocalScope = inlineLocalScope;
}
/**
* 设置全局宏的名称,可包含通配符。
*
* @param macros 宏名称数组
*/
public void setGlobalMacros(String[] macros) {
this.macros = macros;
}
public void setPlugins(Object[] plugins) {
this.plugins = plugins;
}
/**
* 设置高级配置。
*
* @param properties 属性
*/
public void setAdvancedProperties(Map properties) {
this.properties.clear();
for (Map.Entry entry : properties.entrySet()) {
this.properties.setProperty(entry.getKey(), entry.getValue());
}
}
public void addPreloadedResource(String name, Resource resource) {
String _name = StrUtil.trimToNull(name);
if (null != _name && null != resource) {
this.preloadedResources.put(_name, resource);
}
}
public void addPreloadedResources(Map resources) {
if (CollUtil.isNotEmptyMap(resources)) {
this.preloadedResources.putAll(resources);
}
}
/**
* 初始化configuration。
*
* @throws Exception Exception
*/
public void init() throws Exception {
AssertUtil.assertNotNull(loader, "resourceLoader");
removeReservedProperties();
initPlugins();
initLogger();
initMacros();
/*依赖于initMacros的结果。*/
initResourceLoader();
initEventHandlers();
initMiscs();
}
private void addHandler(EventHandler handler) {
AssertUtil.assertTrue(eventCartridge.addEventHandler(handler), "Unknown event handler type: %s", handler.getClass());
}
/** 删除保留的properties,这些properties用户不能修改。 */
private void removeReservedProperties() {
Set keysToRemove = CollUtil.set();
/*Remove resource loader settings*/
keysToRemove.add(RuntimeConstants.RESOURCE_LOADERS);
for (Iterator> i = properties.getKeys(); i.hasNext();) {
Object key = i.next();
if (key instanceof String && ((String)key).contains(RuntimeConstants.RESOURCE_LOADER)) {
keysToRemove.add((String)key);
}
}
/*Remove log settings.*/
keysToRemove.add(RuntimeConstants.RUNTIME_LOG_INSTANCE);
keysToRemove.add(RuntimeConstants.RUNTIME_LOG_NAME);
keysToRemove.add(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID);
keysToRemove.add(RuntimeConstants.RUNTIME_LOG_METHOD_CALL_LOG_INVALID);
keysToRemove.add(RuntimeConstants.RUNTIME_LOG_TRACK_LOCATION);
/*Remove macros.*/
keysToRemove.add(RuntimeConstants.VM_LIBRARY);
/*Remove event handlers: 仅移除eventhandler.xxx.class,保留其它参数。*/
for (Iterator> i = properties.getKeys(); i.hasNext();) {
Object key = i.next();
if (key instanceof String && ((String)key).startsWith("eventhandler.")
&& ((String)key).endsWith(".class")) {
keysToRemove.add((String)key);
}
}
/*Remove others.*/
keysToRemove.add(RuntimeConstants.INPUT_ENCODING);
keysToRemove.add(RuntimeConstants.VM_LIBRARY_AUTORELOAD);
keysToRemove.add(RuntimeConstants.RUNTIME_REFERENCES_STRICT);
/*Do removing.*/
for (String key : keysToRemove) {
if (properties.containsKey(key)) {
log.warn("Removed reserved property: {} = {}", key, properties.get(key));
properties.clearProperty(key);
}
}
}
/* 初始化plugins。 */
private void initPlugins() throws Exception {
if (plugins != null) {
for (Object plugin : plugins) {
if (plugin instanceof VelocityPlugin) {
((VelocityPlugin)plugin).init(this);
}
}
}
}
/**
* 初始化resource loader。
*
* 固定使用ResourceLoadingService/Spring ResourceLoader来装载资源,由于以上机制已经包含足够的灵活性,所以不再允许用户在velocity层面配置resource loader。
*
*/
private void initResourceLoader() {
// path = defaultIfNull(path, "/templates");
path = ObjUtil.defaultIfNull(path, VelocityTemplateConstants.DEFAULT_RESOURCE_LOADER_PATH);
if (productionMode) {
cacheEnabled = true;
}
properties.setProperty(RuntimeConstants.RESOURCE_LOADERS, SpringResourceLoaderAdapter.RESOURCE_LOADER_NAME);
String prefix = null;
/*Spring resource loader.*/
//prefix = "spring." + RESOURCE_LOADER + ".";
prefix = String.format("%s.%s.", RuntimeConstants.RESOURCE_LOADER, SpringResourceLoaderAdapter.RESOURCE_LOADER_NAME);
properties.setProperty(prefix + "description", "Spring Resource Loader Adapter");
properties.setProperty(prefix + "class", SpringResourceLoaderAdapter.class.getName());
properties.setProperty(prefix + "path", path);
properties.setProperty(prefix + "cache", String.valueOf(cacheEnabled));
//properties.setProperty(prefix + "modificationCheckInterval", String.valueOf(modificationCheckInterval));
properties.setProperty(prefix + RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL, String.valueOf(modificationCheckInterval));
/*Preloaded resource loader.*/
//prefix = "preloaded." + RESOURCE_LOADER + ".";
prefix = String.format("%s.%s.", RuntimeConstants.RESOURCE_LOADER, PreloadedResourceLoader.RESOURCE_LOADER_NAME);
properties.setProperty(prefix + "description", "Preloaded Resource Loader");
properties.setProperty(prefix + "class", PreloadedResourceLoader.class.getName());
properties.setProperty(prefix + "cache", String.valueOf(cacheEnabled));
//properties.setProperty(prefix + "modificationCheckInterval", String.valueOf(modificationCheckInterval));
properties.setProperty(prefix + RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL, String.valueOf(modificationCheckInterval));
properties.setProperty(prefix + PreloadedResourceLoader.PRELOADED_RESOURCES_KEY, preloadedResources);
if (!preloadedResources.isEmpty()) {
properties.addProperty(RuntimeConstants.RESOURCE_LOADER, PreloadedResourceLoader.RESOURCE_LOADER_NAME);
}
}
/** 初始化日志系统。 */
private void initLogger() {}
/* 查找所有全局macros。 */
private void initMacros() throws Exception {
ResourcePatternResolver resolver;
if (loader instanceof ResourcePatternResolver) {
resolver = (ResourcePatternResolver)loader;
} else {
resolver = new PathMatchingResourcePatternResolver(loader);
}
if (macros != null) {
for (String macro : macros) {
resolveMacro(resolver, macro);
}
}
/*Velocity default: VM_global_library.vm*/
resolveMacro(resolver, RuntimeConstants.VM_LIBRARY_DEFAULT);
/*Plugin macros.*/
if (plugins != null) {
for (Object plugin : plugins) {
if (plugin instanceof VelocityPlugin) {
addMacroResources(null, ((VelocityPlugin)plugin).getMacros());
}
}
}
if (!properties.containsKey(RuntimeConstants.VM_LIBRARY)) {
properties.setProperty(RuntimeConstants.VM_LIBRARY, StringPool.EMPTY);
}
}
private void resolveMacro(ResourcePatternResolver resolver, String macro) {
String path = FileToolUtil.normalizeAbsolutePath(this.path + "/");
String pattern = FileToolUtil.normalizeAbsolutePath(path + macro);
Resource[] resources;
try {
resources = resolver.getResources(pattern);
} catch (IOException e) {
resources = null;
}
addMacroResources(path, resources);
}
private void addMacroResources(String path, Resource[] resources) {
if (resources != null) {
/*必须用vector,否则VelocimacroFactory老代码读不到值。*/
@SuppressWarnings("unchecked")
Set macros = CollUtil.set(properties.getVector(RuntimeConstants.VM_LIBRARY));
for (Resource resource : resources) {
if (resource.exists()) {
String templateName = null;
/*对于多数resource,如ServletResource,ResourceAdapter等,都可以从中取得原始的resourceName。*/
if (path != null && resource instanceof ContextResource) {
String resourceName = ((ContextResource)resource).getPathWithinContext();
if (resourceName.startsWith(path)) {
templateName = resourceName.substring(path.length());
}
}
/*对于不可取得resourceName的,使用特殊的装载机制。*/
if (templateName == null) {
templateName = getTemplateNameOfPreloadedResource(resource);
}
if (!macros.contains(templateName)) {
properties.addProperty(RuntimeConstants.VM_LIBRARY, templateName);
macros.add(templateName);
}
}
}
}
}
private String getTemplateNameOfPreloadedResource(Resource resource) {
URL url;
try {
url = resource.getURL();
} catch (IOException e) {
url = null;
}
String templateNameBase;
if (url != null) {
//templateNameBase = "globalVMs/" + StringUtils.getFilename(url.getPath());
templateNameBase = "globalVMs/" + FileToolUtil.getFileName(url.getPath());
} else {
templateNameBase = "globalVMs/globalVM.vm";
}
String templateName = templateNameBase;
/*防止加入重复的resource对象。*/
for (int i = 1;
preloadedResources.containsKey(templateName) && !resource.equals(preloadedResources.get(templateName));
i++) {
templateName = templateNameBase + i;
}
preloadedResources.put(templateName, resource);
return templateName;
}
private void initEventHandlers() {
/*准备eventCartridge,并设置默认的handler。*/
boolean hasRenderableHandler = false;
if (ArrayUtil.isNotEmptyArray(plugins)) {
for (Object plugin : plugins) {
if (plugin instanceof RenderableHandler) {
hasRenderableHandler = true;
break;
}
}
}
if (!hasRenderableHandler) {
addHandler(new RenderableHandler());
}
if (ArrayUtil.isNotEmptyArray(plugins)) {
for (Object plugin : plugins) {
if (plugin instanceof EventHandler) {
addHandler((EventHandler)plugin);
}
}
}
}
/* 初始化杂项。 */
private void initMiscs() {
if (charset == null) {
charset = VelocityTemplateConstants.DEFAULT_ENCODING;
}
setDefaultProperty(RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, String.valueOf(logWhenFoundTemplate));
setDefaultProperty(RuntimeConstants.INPUT_ENCODING, charset);
setDefaultProperty(VelocityTemplateConstants.OUTPUT_ENCODING, charset);
setDefaultProperty(RuntimeConstants.PARSER_POOL_SIZE,
String.valueOf(parserPoolSize > 0 ? parserPoolSize : VelocityTemplateConstants.DEFAULT_PARSER_POOL_SIZE));
setDefaultProperty(RuntimeConstants.UBERSPECT_CLASSNAME, CustomizedUberspectImpl.class.getName());
setDefaultProperty(RuntimeConstants.VM_ARGUMENTS_STRICT, String.valueOf(exceptionWrongArgs));
setDefaultProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, String.valueOf(inlineLocalScope));
/*Auto-reload macros.*/
properties.setProperty(RuntimeConstants.VM_LIBRARY_AUTORELOAD, productionMode ? StringPool.FALSE : StringPool.TRUE);
/*Strict ref.*/
properties.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, String.valueOf(strictReference));
}
/* 设置默认值。如果值已存在,则不覆盖。 */
private void setDefaultProperty(String key, Object value) {
if (!properties.containsKey(key)) {
properties.setProperty(key, value);
}
}
@Override
public String toString() {
return new MapBuilder().setSortKeys(true).setPrintCount(true).appendAll(properties).toString();
}
}