/*
* 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.impl;
import static com.alibaba.citrus.service.velocity.impl.PreloadedResourceLoader.*;
import static com.alibaba.citrus.util.ArrayUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.FileUtil.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static org.apache.velocity.runtime.RuntimeConstants.*;
import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.alibaba.citrus.service.velocity.VelocityConfiguration;
import com.alibaba.citrus.service.velocity.VelocityPlugin;
import com.alibaba.citrus.service.velocity.support.RenderableHandler;
import com.alibaba.citrus.util.ToStringBuilder.MapBuilder;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.app.event.EventHandler;
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.springframework.util.StringUtils;
/**
* 代表一组velocity engine的配置。
*
* @author Michael Zhou
*/
public class VelocityConfigurationImpl implements VelocityConfiguration {
private final Logger log;
private final ExtendedProperties properties = new ExtendedProperties();
private final Map<String, Resource> preloadedResources = createHashMap();
private final CloneableEventCartridge eventCartridge = new CloneableEventCartridge();
private Object[] plugins;
private ResourceLoader loader;
private boolean productionMode = true;
// resource loader
private String path;
private boolean cacheEnabled = true;
private int modificationCheckInterval = 2;
// strict ref
private boolean strictReference = true;
// template charset encoding
private String charset;
// global macros
private String[] macros;
/** 创建一个velocity配置。 */
public VelocityConfigurationImpl(Logger log) {
this.log = assertNotNull(log, "log");
}
public ExtendedProperties getProperties() {
return properties;
}
public CloneableEventCartridge getEventCartridge() {
return eventCartridge;
}
public ResourceLoader getResourceLoader() {
return loader;
}
/** 设置resource loader。 */
public void setResourceLoader(ResourceLoader loader) {
this.loader = loader;
}
public boolean isProductionMode() {
return productionMode;
}
/** 设置生产模式。默认为<code>true</code>。 */
public void setProductionMode(boolean productionMode) {
this.productionMode = productionMode;
}
/** 设置搜索模板的根目录。默认为<code>/templates</code>。 */
public void setPath(String path) {
this.path = trimToNull(path);
}
/** 是否开启模板缓存。在生产模式下,该模式将被强行开启。 */
public void setCacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}
/** 设置检查模板被修改的间隔(秒)。默认为2秒。 */
public void setModificationCheckInterval(int modificationCheckInterval) {
this.modificationCheckInterval = modificationCheckInterval;
}
/** 设置strict reference模式。默认为<code>true</code>。 */
public void setStrictReference(boolean strictReference) {
this.strictReference = strictReference;
}
/** 设置模板的字符集编码。 */
public void setTemplateEncoding(String charset) {
this.charset = trimToNull(charset);
}
/** 设置全局宏的名称,可包含通配符。 */
public void setGlobalMacros(String[] macros) {
this.macros = macros;
}
/** 设置plugins。 */
public void setPlugins(Object[] plugins) {
this.plugins = plugins;
}
/** 设置高级配置。 */
public void setAdvancedProperties(Map<String, Object> configuration) {
this.properties.clear();
for (Map.Entry<String, Object> entry : configuration.entrySet()) {
this.properties.setProperty(entry.getKey(), entry.getValue());
}
}
/** 初始化configuration。 */
public void init() throws Exception {
assertNotNull(loader, "resourceLoader");
removeReservedProperties();
initPlugins();
initLogger();
initMacros();
initResourceLoader(); // 依赖于initMacros的结果
initEventHandlers();
initMiscs();
}
private void addHandler(EventHandler handler) {
assertTrue(eventCartridge.addEventHandler(handler), "Unknown event handler type: %s", handler.getClass());
}
/** 删除保留的properties,这些properties用户不能修改。 */
private void removeReservedProperties() {
Set<String> keysToRemove = createHashSet();
// Remove resource loader settings
keysToRemove.add(RESOURCE_LOADER);
for (Iterator<?> i = properties.getKeys(); i.hasNext(); ) {
Object key = i.next();
if (key instanceof String && ((String) key).contains(RESOURCE_LOADER)) {
keysToRemove.add((String) key);
}
}
// Remove log settings
keysToRemove.add(RUNTIME_LOG);
keysToRemove.add(RUNTIME_LOG_LOGSYSTEM);
keysToRemove.add(RUNTIME_LOG_LOGSYSTEM_CLASS);
// Remove macros
keysToRemove.add(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(INPUT_ENCODING);
keysToRemove.add(VM_LIBRARY_AUTORELOAD);
keysToRemove.add(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。
* <p>
* 固定使用ResourceLoadingService/Spring
* ResourceLoader来装载资源,由于以上机制已经包含足够的灵活性,所以不再允许用户在velocity层面配置resource
* loader。
* </p>
*/
private void initResourceLoader() {
path = defaultIfNull(path, "/templates");
if (productionMode) {
cacheEnabled = true;
}
properties.setProperty(RESOURCE_LOADER, "spring");
// Spring resource loader
String prefix = "spring." + RESOURCE_LOADER + ".";
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));
// Preloaded resource loader
prefix = "preloaded." + RESOURCE_LOADER + ".";
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 + PRELOADED_RESOURCES_KEY, preloadedResources);
if (!preloadedResources.isEmpty()) {
properties.addProperty(RESOURCE_LOADER, "preloaded");
}
}
/** 初始化日志系统。 */
private void initLogger() {
properties.setProperty(RUNTIME_LOG_LOGSYSTEM, new Slf4jLogChute(log));
}
/** 查找所有全局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, VM_LIBRARY_DEFAULT);
// Plugin macros
if (plugins != null) {
for (Object plugin : plugins) {
if (plugin instanceof VelocityPlugin) {
addMacroResources(null, ((VelocityPlugin) plugin).getMacros());
}
}
}
if (!properties.containsKey(VM_LIBRARY)) {
properties.setProperty(VM_LIBRARY, EMPTY_STRING);
}
}
private void resolveMacro(ResourcePatternResolver resolver, String macro) {
String path = normalizeAbsolutePath(this.path + "/");
String pattern = 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<String> macros = createHashSet(properties.getVector(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(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());
} 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 (!isEmptyArray(plugins)) {
for (Object plugin : plugins) {
if (plugin instanceof RenderableHandler) {
hasRenderableHandler = true;
break;
}
}
}
if (!hasRenderableHandler) {
addHandler(new RenderableHandler());
}
if (!isEmptyArray(plugins)) {
for (Object plugin : plugins) {
if (plugin instanceof EventHandler) {
addHandler((EventHandler) plugin);
}
}
}
}
/** 初始化杂项。 */
private void initMiscs() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
setDefaultProperty(RESOURCE_MANAGER_LOGWHENFOUND, "false");
setDefaultProperty(INPUT_ENCODING, charset);
setDefaultProperty(OUTPUT_ENCODING, DEFAULT_CHARSET);
setDefaultProperty(PARSER_POOL_SIZE, "50");
setDefaultProperty(UBERSPECT_CLASSNAME, CustomizedUberspectImpl.class.getName());
setDefaultProperty(VM_ARGUMENTS_STRICT, "true");
setDefaultProperty(VM_PERM_INLINE_LOCAL, "true");
setDefaultProperty(SET_NULL_ALLOWED, "true");
// auto-reload macros
if (productionMode) {
properties.setProperty(VM_LIBRARY_AUTORELOAD, "false");
} else {
properties.setProperty(VM_LIBRARY_AUTORELOAD, "true");
}
// strict ref
properties.setProperty(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();
}
}