/* * 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.turbine.util; import static com.alibaba.citrus.springext.util.DomUtil.*; import static com.alibaba.citrus.springext.util.SpringExtUtil.*; import static com.alibaba.citrus.turbine.TurbineConstant.*; import static com.alibaba.citrus.turbine.util.ControlTool.ErrorDetailLevel.*; 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.ObjectUtil.*; import static com.alibaba.citrus.util.StringEscapeUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; import java.util.Formatter; import java.util.HashMap; import java.util.LinkedList; import java.util.Set; import javax.servlet.http.HttpServletRequest; import com.alibaba.citrus.service.configuration.ProductionModeAware; import com.alibaba.citrus.service.mappingrule.MappingRuleService; import com.alibaba.citrus.service.moduleloader.Module; import com.alibaba.citrus.service.moduleloader.ModuleLoaderService; import com.alibaba.citrus.service.pull.ToolFactory; import com.alibaba.citrus.service.requestcontext.buffered.BufferedRequestContext; import com.alibaba.citrus.service.template.Renderable; import com.alibaba.citrus.service.template.TemplateService; import com.alibaba.citrus.springext.support.BeanSupport; import com.alibaba.citrus.springext.support.parser.AbstractSingleBeanDefinitionParser; import com.alibaba.citrus.turbine.Context; import com.alibaba.citrus.turbine.TurbineRunDataInternal; import com.alibaba.citrus.turbine.support.ContextAdapter; import com.alibaba.citrus.turbine.support.MappedContext; import com.alibaba.citrus.util.ExceptionUtil; import com.alibaba.citrus.webx.WebxComponents; import com.alibaba.citrus.webx.WebxException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.context.ApplicationContext; import org.w3c.dom.Element; /** * 设置和显示一个control module的tool。 * * @author Michael Zhou */ public class ControlTool extends ControlToolConfiguration implements Renderable { private static final Logger log = LoggerFactory.getLogger(ControlTool.class); private final ErrorHandler errorHandler; private LinkedList<ControlParameters> controlParameterStack = createLinkedList(); public ControlTool() { this((ErrorHandler) null); } public ControlTool(boolean productionMode) { this(productionMode ? ErrorDetailLevel.messageOnly : ErrorDetailLevel.stackTrace); } public ControlTool(ErrorDetailLevel errorDetailLevel) { this(errorDetailLevel == null ? null : errorDetailLevel.getHandler()); } public ControlTool(ErrorHandler errorHandler) { if (errorHandler == null) { errorHandler = messageOnly.getHandler(); } this.errorHandler = errorHandler; } public ErrorHandler getErrorHandler() { return errorHandler; } /** * 设置control的模板。此方法和<code>setModule</code>只能执行其一,否则将忽略后者。 * * @param template control模板名 * @return <code>ControlTool</code>本身,以方便模板中的操作 */ public ControlTool setTemplate(String template) { ControlParameters params = getControlParameters(); if (params.module == null) { params.template = template; } return this; } /** * 设置control的模块。此方法和<code>setTemplate</code>只能执行其一,否则将忽略后者。 * * @param module control模块名 * @return <code>ControlTool</code>本身,以方便模板中的操作 */ public ControlTool setModule(String module) { ControlParameters params = getControlParameters(); if (params.template == null) { params.module = module; } return this; } /** * 设置control的参数。 * <p> * 这些参数将被保存在一个一次性的<code>Map</code>中,当render成功以后,该map就被丢弃,以便再次调用该control。 * </p> * * @param name 属性名 * @param value 对象 * @return <code>ControlTool</code>本身,以方便模板中的操作 */ public ControlTool setParameter(String name, Object value) { ControlParameters params = getControlParameters(); params.put(name, value); return this; } /** 当screen结束时,导出指定名称的变量,使调用者可以访问。 */ public ControlTool export(String... vars) { ControlParameters params = getControlParameters(); params.exportVars = createHashSet(vars); return this; } /** * 渲染对象。 * * @return 渲染的结果 */ public String render() { assertInitialized(); ControlParameters params = getControlParameters(); String componentName; String target = null; String content; boolean isTemplate; try { if (params.template != null) { componentName = parseComponentName(params.template); target = parseLocalName(params.template); isTemplate = true; } else if (params.module != null) { componentName = parseComponentName(params.module); target = parseLocalName(params.module); isTemplate = false; } else { throw new IllegalArgumentException("Neither template nor module name was specified to render a control"); } // controlTool支持跨component调用,现在取得指定component下的service。 ModuleLoaderService moduleLoaderService = getService("moduleLoaderService", componentName, this.moduleLoaderService, ModuleLoaderService.class); MappingRuleService mappingRuleService = getService("mappingRuleService", componentName, this.mappingRuleService, MappingRuleService.class); TemplateService templateService = getService("templateService", componentName, this.templateService, TemplateService.class); // 取得实际的template/module名称 String templateName = null; String moduleName = null; if (isTemplate) { templateName = target; moduleName = mappingRuleService.getMappedName(CONTROL_MODULE, target); } else { moduleName = mappingRuleService.getMappedName(CONTROL_MODULE_NO_TEMPLATE, target); } // 执行control module Module controlModule; if (templateName == null) { // templateName未指定时,必须有module,如没有则抛出ModuleNotFoundException controlModule = moduleLoaderService.getModule(CONTROL_MODULE, moduleName); } else { // 当指定了templateName时,可以没有的control module,而单单渲染模板。 // 这样就实现了page-driven,即先写模板,必要时再写一个module class与之对应。 controlModule = moduleLoaderService.getModuleQuiet(CONTROL_MODULE, moduleName); } if (log.isTraceEnabled()) { if (templateName != null) { log.trace("Rendering control: template=" + templateName + ", control=" + moduleName); } else { log.trace("Rendering control without template: control=" + moduleName); } } // 设置参数 this.bufferedRequestContext.pushBuffer(); try { controlParameterStack.addFirst(new ControlParameters()); // 支持control的嵌套 TurbineRunDataInternal rundata = (TurbineRunDataInternal) TurbineUtil.getTurbineRunData(this.request); Context contextForControl = createContextForControl(params, componentName); rundata.pushContext(contextForControl, templateName); try { if (controlModule != null) { controlModule.execute(); } // Control module可以通过注入ControlParameters接口来修改template。 String templateOverriden = rundata.getControlTemplate(); if (!isEquals(templateOverriden, templateName)) { log.debug("Control template has been changed by module: " + templateName + " -> " + templateOverriden); templateName = templateOverriden; } if (templateName != null) { templateName = mappingRuleService.getMappedName(CONTROL_TEMPLATE, templateName); } if (templateName != null) { templateService.writeTo(templateName, new ContextAdapter(contextForControl), rundata .getResponse().getWriter()); } } finally { rundata.popContext(); } } finally { controlParameterStack.removeFirst(); content = this.bufferedRequestContext.popCharBuffer(); } } catch (Exception e) { content = null; try { content = errorHandler.handleException(target, e); } catch (RuntimeException ee) { } // 如果handler返回空,则抛出异常,否则输出handler返回的内容。 if (content == null) { throw new WebxException("Failed to execute control module: " + target, e); } else { log.error("Failed to execute control module: " + target, e); } } finally { // 清除环境,以便重用 params.template = null; params.module = null; params.exportVars = null; params.clear(); } return content; } /** 假如template或module的名称前有诸如“componentName:”前缀,则返回此componentName,否则返回null。 */ private String parseComponentName(String name) { int index = name == null ? -1 : name.indexOf(":"); if (index >= 0) { return trimToNull(name.substring(0, index)); } return null; } /** 取得不包含“componentName:”前缀的template或module的名称。 */ private String parseLocalName(String name) { int index = name == null ? -1 : name.indexOf(":"); if (index >= 0) { return trimToNull(name.substring(index + 1)); } else { return trimToNull(name); } } private <T> T getService(String name, String componentName, T defaultService, Class<T> serviceType) { if (componentName == null) { return defaultService; } ApplicationContext context = assertNotNull(this.components.getComponent(componentName), "invalid prefix \"%s:\", component does not exist", componentName).getApplicationContext(); try { return serviceType.cast(context.getBean(name, serviceType)); } catch (BeansException e) { throw new IllegalArgumentException(String.format("Could not get service: \"%s:%s\"", componentName, serviceType.getSimpleName()), e); } } private Context createContextForControl(ControlParameters params, String componentName) { // get parent context TurbineRunDataInternal rundata = (TurbineRunDataInternal) TurbineUtil.getTurbineRunData(this.request); Context screenContext = rundata.getContext(componentName); final Context callerContext = rundata.getCurrentContext(); final Set<String> exportVars = params.exportVars == null ? Collections.<String>emptySet() : params.exportVars; // create control context MappedContext context = new MappedContext(screenContext) { @Override protected void internalPut(String key, Object value) { if (isExport(key)) { callerContext.put(key, value); } super.internalPut(key, value); } @Override protected void internalRemove(String key) { if (isExport(key)) { callerContext.remove(key); } super.internalRemove(key); } private boolean isExport(String key) { return callerContext != null && (exportAll || exportVars.contains(key)); } }; // add all params context.getMap().putAll(params); return context; } /** 取得栈顶的control参数。 */ protected ControlParameters getControlParameters() { if (controlParameterStack.isEmpty()) { controlParameterStack.addFirst(new ControlParameters()); } return controlParameterStack.getFirst(); } /** 代表一次control的调用参数。 */ protected static class ControlParameters extends HashMap<String, Object> { private static final long serialVersionUID = 3256721796996084529L; private String module; private String template; private Set<String> exportVars; public ControlParameters() { super(4); } } /** 打印异常的详细程度。 */ public static enum ErrorDetailLevel { /** 抛出异常。 */ throwException, /** 不输出任何错误内容。 */ quiet, /** 只输出异常信息。 */ messageOnly, /** 输出异常的详细信息。 */ stackTrace, /** 将异常打印在HTML注释中。 */ comment; private final ErrorHandler handler = new DefaultErrorHandler(this); public ErrorHandler getHandler() { return handler; } } /** 用来处理control执行过程中的异常。 */ public static interface ErrorHandler { String handleException(String controlTarget, Exception e); } public static class ThrowError implements ErrorHandler { public String handleException(String controlTarget, Exception e) { return null; } } public static class DefaultErrorHandler implements ErrorHandler { private String errorTagClass; private ErrorDetailLevel detailLevel; public DefaultErrorHandler() { } public DefaultErrorHandler(ErrorDetailLevel detailLevel) { this.detailLevel = detailLevel; } public String getErrorTagClass() { return defaultIfNull(errorTagClass, "webx.error"); } public void setErrorTagClass(String errorTagClass) { this.errorTagClass = trimToNull(errorTagClass); } public ErrorDetailLevel getDetailLevel() { return detailLevel == null ? messageOnly : detailLevel; } public void setDetailLevel(ErrorDetailLevel detailLevel) { this.detailLevel = detailLevel; } public String handleException(String controlTarget, Exception e) { ErrorDetailLevel detailLevel = getDetailLevel(); switch (detailLevel) { case throwException: return null; case quiet: return EMPTY_STRING; default: break; } StringWriter buf = new StringWriter(); PrintWriter pw = new PrintWriter(buf); Formatter fmt = new Formatter(pw); fmt.format("<!-- control failed: target=%s, exceptionType=%s -->", controlTarget, e.getClass().getName()); switch (detailLevel) { case messageOnly: fmt.format("<div class=\"%s\">", getErrorTagClass()); String msg = e.getMessage(); if (isEmpty(msg)) { msg = e.getClass().getSimpleName(); } pw.append(escapeHtml(msg)); // !!重要:escapeHtml pw.append("</div>"); break; case stackTrace: fmt.format("<div class=\"%s\">", getErrorTagClass()); pw.append(ExceptionUtil.getStackTraceForHtmlComment(e)); // !!重要:escapeHtml pw.append("</div>"); break; case comment: pw.append("<!-- stacktrace: \n"); pw.append(ExceptionUtil.getStackTraceForHtmlComment(e)); // !!重要:escapeHtml pw.append("-->"); break; default: unreachableCode(detailLevel.name()); } pw.flush(); return buf.toString(); } } public static class DefinitionParser extends AbstractSingleBeanDefinitionParser<Factory> { @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String errorDetailLevel = trimToNull(element.getAttribute("detailLevel")); if (errorDetailLevel != null) { builder.addPropertyValue("errorDetailLevel", errorDetailLevel); } else { Element errorHandlerElement = theOnlySubElement(element, and(sameNs(element), name("errorHandler"))); if (errorHandlerElement != null) { builder.addPropertyValue("errorHandler", parseBean(errorHandlerElement, parserContext, builder.getRawBeanDefinition())); } } String exportAll = trimToNull(element.getAttribute("exportAll")); if (exportAll != null) { builder.addPropertyValue("exportAll", exportAll); } } } /** pull tool factory。 */ public static class Factory extends ControlToolConfiguration implements ToolFactory, ProductionModeAware { private ErrorDetailLevel errorDetailLevel; private ErrorHandler errorHandler; private boolean productionMode = true; private boolean exportAll; public void setProductionMode(boolean productionMode) { this.productionMode = productionMode; } public void setErrorDetailLevel(ErrorDetailLevel errorDetailLevel) { this.errorDetailLevel = errorDetailLevel; } public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } public void setExportAll(boolean exportAll) { this.exportAll = exportAll; } public boolean isSingleton() { return false; } public Object createTool() throws Exception { ControlTool tool; if (errorDetailLevel != null) { tool = new ControlTool(errorDetailLevel); } else if (errorHandler != null) { tool = new ControlTool(errorHandler); } else { tool = new ControlTool(productionMode); } tool.init(components, moduleLoaderService, mappingRuleService, templateService, request, bufferedRequestContext); tool.exportAll = exportAll; tool.afterPropertiesSet(); return tool; } } } class ControlToolConfiguration extends BeanSupport { protected WebxComponents components; protected ModuleLoaderService moduleLoaderService; protected MappingRuleService mappingRuleService; protected TemplateService templateService; protected HttpServletRequest request; protected BufferedRequestContext bufferedRequestContext; protected boolean exportAll; @Autowired protected void init(WebxComponents components, ModuleLoaderService moduleLoaderService, MappingRuleService mappingRuleService, TemplateService templateService, HttpServletRequest request, BufferedRequestContext bufferedRequestContext) { this.components = components; this.moduleLoaderService = moduleLoaderService; this.mappingRuleService = mappingRuleService; this.templateService = templateService; this.request = request; this.bufferedRequestContext = bufferedRequestContext; } @Override protected void init() { assertNotNull(components, "no components"); assertNotNull(moduleLoaderService, "no moduleLoaderService"); assertNotNull(mappingRuleService, "no mappingRuleService"); assertNotNull(templateService, "no templateService"); assertNotNull(request, "no request"); assertNotNull(bufferedRequestContext, "no bufferedRequestContext"); } }