Package com.alibaba.citrus.turbine.util

Source Code of com.alibaba.citrus.turbine.util.ControlTool$DefinitionParser

/*
* 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");
    }
}
TOP

Related Classes of com.alibaba.citrus.turbine.util.ControlTool$DefinitionParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.