/* * 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.VelocityConfiguration.*; import static com.alibaba.citrus.service.velocity.impl.SpringResourceLoaderAdapter.*; import static com.alibaba.citrus.util.Assert.*; 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.io.OutputStream; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import com.alibaba.citrus.service.AbstractService; import com.alibaba.citrus.service.configuration.ProductionModeAware; 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.velocity.VelocityEngine; import org.apache.velocity.Template; import org.apache.velocity.app.event.EventCartridge; import org.apache.velocity.app.event.ReferenceInsertionEventHandler; import org.apache.velocity.context.AbstractContext; import org.apache.velocity.context.Context; import org.apache.velocity.context.InternalEventContext; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.RuntimeServices; import org.apache.velocity.util.RuntimeServicesAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; /** * Velocity模板引擎服务。 * * @author Michael Zhou */ public class VelocityEngineImpl extends AbstractService<VelocityEngine> implements VelocityEngine, ResourceLoaderAware, ProductionModeAware { private final static String RUNTIME_SERVICES_KEY = "_runtime_services"; private final VelocityRuntimeInstance ri = new VelocityRuntimeInstance(); private final VelocityConfigurationImpl configuration = new VelocityConfigurationImpl(getLogger()); public RuntimeServices getRuntimeServices() { return ri; } public VelocityConfigurationImpl getConfiguration() { return configuration; } public void setResourceLoader(ResourceLoader loader) { configuration.setResourceLoader(loader); } public void setProductionMode(boolean productionMode) { configuration.setProductionMode(productionMode); } /** 初始化engine。 */ @Override protected void init() throws Exception { configuration.init(); getLogger().debug("Velocity Engine Configurations: {}", configuration); ri.setConfiguration(configuration.getProperties()); ri.setApplicationAttribute(SPRING_RESOURCE_LOADER_KEY, configuration.getResourceLoader()); ri.setProperty(EVENTHANDLER_REFERENCEINSERTION, RuntimeServicesExposer.class.getName()); ri.init(); // 初始化EventCartridge,以后不用再初始化了,以确保性能。 CloneableEventCartridge eventCartridge = configuration.getEventCartridge(); RuntimeServices rs = assertNotNull((RuntimeServices) ri.getProperty(RUNTIME_SERVICES_KEY), "RuntimeServices"); eventCartridge.initOnce(rs); } /** * 取得默认的模板名后缀列表。 * <p> * 当<code>TemplateService</code>没有指定到当前engine的mapping时,将取得本方法所返回的后缀名列表。 * </p> */ public String[] getDefaultExtensions() { return new String[] { "vm" }; } /** 判定模板是否存在。 */ public boolean exists(String templateName) { return ri.getLoaderNameForResource(templateName) != null; } /** 渲染模板,并以字符串的形式取得渲染的结果。 */ public String getText(String templateName, TemplateContext context) throws TemplateException, IOException { return mergeTemplate(templateName, new TemplateContextAdapter(context), null); } /** 渲染模板,并将渲染的结果送到字节输出流中。 */ public void writeTo(String templateName, TemplateContext context, OutputStream ostream) throws TemplateException, IOException { mergeTemplate(templateName, new TemplateContextAdapter(context), ostream, null, null); } /** 渲染模板,并将渲染的结果送到字符输出流中。 */ public void writeTo(String templateName, TemplateContext context, Writer writer) throws TemplateException, IOException { mergeTemplate(templateName, new TemplateContextAdapter(context), writer, null); } /** 渲染模板,并以字符串的形式取得渲染的结果。 */ public String mergeTemplate(String templateName, Context context, String inputEncoding) throws TemplateException, IOException { StringWriter writer = new StringWriter(); mergeTemplate(templateName, context, writer, inputEncoding); return writer.toString(); } /** 渲染模板,并将渲染的结果送到字节输出流中。 */ public void mergeTemplate(String templateName, Context context, OutputStream ostream, String inputEncoding, String outputEncoding) throws TemplateException, IOException { if (isEmpty(outputEncoding)) { outputEncoding = getDefaultOutputEncoding(); } OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(ostream, outputEncoding); } catch (UnsupportedEncodingException e) { error(templateName, e); } mergeTemplate(templateName, context, writer, inputEncoding); writer.flush(); // 确保内容被刷新到stream中。 } /** 渲染模板,并将渲染的结果送到字符输出流中。 */ public void mergeTemplate(String templateName, Context context, Writer writer, String inputEncoding) throws TemplateException, IOException { if (isEmpty(inputEncoding)) { inputEncoding = getDefaultInputEncoding(); } try { Context eventContext = attachEventCartridge(context); mergeTemplate(templateName, inputEncoding, eventContext, writer); } catch (Exception e) { error(templateName, e); } } /** Copied from org.apache.velocity.app.VelocityEngine。 */ private boolean mergeTemplate(String templateName, String encoding, Context context, Writer writer) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception { Template template = ri.getTemplate(templateName, encoding); if (template == null) { String msg = "VelocityEngine.mergeTemplate() was unable to load template '" + templateName + "'"; ri.getLog().error(msg); throw new ResourceNotFoundException(msg); } else { template.merge(context, writer); return true; } } private Context attachEventCartridge(Context context) { Context eventContext; if (context instanceof InternalEventContext) { eventContext = context; } else { // 将其包装成EventContext,确保event cartridge可以工作。 // 模板中的修改将保留在context中。 eventContext = new EventContext(context); } // 将event cartridge复制以后(如有必要)附到context中。 EventCartridge ec = configuration.getEventCartridge().getRuntimeInstance(); if (ec != null) { assertTrue(ec.attachToContext(eventContext), "Could not attach EventCartridge to velocity context"); } return eventContext; } // 以下两个为性能优化 private String defaultInputEncoding; private String defaultOutpuEncoding; /** 取得解析模板时的默认编码字符集。 */ protected String getDefaultInputEncoding() { if (defaultInputEncoding == null) { defaultInputEncoding = defaultIfNull(trimToNull((String) ri.getProperty(INPUT_ENCODING)), DEFAULT_CHARSET); } return defaultInputEncoding; } /** 取得输出模板时的默认编码字符集。 */ protected String getDefaultOutputEncoding() { if (defaultOutpuEncoding == null) { defaultOutpuEncoding = defaultIfNull(trimToNull((String) ri.getProperty(OUTPUT_ENCODING)), DEFAULT_CHARSET); } return defaultOutpuEncoding; } /** 处理异常,显示额外的信息。 */ private final void error(String templateName, Throwable e) throws TemplateException { String err = "Error rendering Velocity template: " + templateName; getLogger().error(err + ": " + e.getMessage()); if (e instanceof ResourceNotFoundException) { throw new TemplateNotFoundException(err, e); } if (e instanceof TemplateException) { throw (TemplateException) e; } throw new TemplateException(err, e); } /** 一个hack,用来取得runtime services实例。 */ public static class RuntimeServicesExposer implements ReferenceInsertionEventHandler, RuntimeServicesAware { public Object referenceInsert(String reference, Object value) { return value; } public void setRuntimeServices(RuntimeServices rs) { rs.getConfiguration().setProperty(RUNTIME_SERVICES_KEY, rs); } } /** 包装任意<code>Context</code>,使之支持EventCartridge。 */ private static class EventContext extends AbstractContext { private final Context context; public EventContext(Context context) { this.context = assertNotNull(context, "no context"); } @Override public Object internalGet(String key) { return context.get(key); } @Override public Object internalPut(String key, Object value) { return context.put(key, value); } @Override public boolean internalContainsKey(Object key) { return context.containsKey(key); } @Override public Object[] internalGetKeys() { return context.getKeys(); } @Override public Object internalRemove(Object key) { return context.remove(key); } @Override public String toString() { return context.toString(); } } }