Package com.alibaba.citrus.webx.support

Source Code of com.alibaba.citrus.webx.support.AbstractWebxRootController$InternalRequestHandlerMapping

/*
* Copyright 2010 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.webx.support;

import static com.alibaba.citrus.service.requestcontext.util.RequestContextUtil.*;
import static com.alibaba.citrus.springext.util.SpringExtUtil.*;
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.ExceptionUtil.*;
import static com.alibaba.citrus.util.FileUtil.*;
import static com.alibaba.citrus.util.ServletUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static java.util.Collections.*;
import static org.springframework.beans.factory.config.AutowireCapableBeanFactory.*;

import java.io.IOException;
import java.util.Comparator;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.PropertiesLoaderUtils;

import com.alibaba.citrus.service.pipeline.Pipeline;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.RequestContextChainingService;
import com.alibaba.citrus.service.requestcontext.buffered.BufferedRequestContext;
import com.alibaba.citrus.service.requestcontext.lazycommit.LazyCommitRequestContext;
import com.alibaba.citrus.service.requestcontext.util.RequestContextUtil;
import com.alibaba.citrus.util.ClassLoaderUtil;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.webx.BadRequestException;
import com.alibaba.citrus.webx.ResourceNotFoundException;
import com.alibaba.citrus.webx.WebxComponents;
import com.alibaba.citrus.webx.WebxException;
import com.alibaba.citrus.webx.WebxRootController;
import com.alibaba.citrus.webx.config.WebxConfiguration;
import com.alibaba.citrus.webx.handler.ErrorHandlerMapping;
import com.alibaba.citrus.webx.handler.RequestHandler;
import com.alibaba.citrus.webx.handler.RequestHandlerContext;
import com.alibaba.citrus.webx.handler.RequestHandlerMapping;
import com.alibaba.citrus.webx.handler.RequestHandlerNameAware;
import com.alibaba.citrus.webx.handler.impl.MainHandler;
import com.alibaba.citrus.webx.handler.impl.error.DetailedErrorHandler;
import com.alibaba.citrus.webx.handler.impl.error.PipelineErrorHandler;
import com.alibaba.citrus.webx.handler.impl.error.SendErrorHandler;
import com.alibaba.citrus.webx.servlet.PassThruSupportable;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper.ExceptionCodeMapping;
import com.alibaba.citrus.webx.util.RequestURIFilter;
import com.alibaba.citrus.webx.util.WebxUtil;

public abstract class AbstractWebxRootController implements WebxRootController, PassThruSupportable {
    protected final Logger log = LoggerFactory.getLogger(getClass());

    /** ��request�б���request context owner�ļ����� */
    private static final String REQUEST_CONTEXT_OWNER_KEY = "_request_context_owner_";

    /** ����ע��request handler���ļ����� */
    private static final String REQUEST_HANDLER_LOCATION = "META-INF/webx.internal-request-handlers";

    /** Errorҳ���ǰ׺�� */
    private static final String ERROR_PREFIX = "error";

    private WebxComponents components;
    private InternalRequestHandlerMapping internalHandlerMapping;
    private RequestContextChainingService requestContexts;
    private RequestURIFilter passthruFilter;

    public WebxComponents getComponents() {
        return components;
    }

    public WebxConfiguration getWebxConfiguration() {
        return getComponents().getParentWebxConfiguration();
    }

    public ServletContext getServletContext() {
        return getComponents().getParentApplicationContext().getServletContext();
    }

    public void setPassthruFilter(RequestURIFilter passthru) {
        this.passthruFilter = passthru;
    }

    /**
     * �˷����ڴ���controllerʱ�����á�
     */
    public void init(WebxComponents components) {
        this.components = components;
    }

    /**
     * �˷����ڴ�����ˢ��WebApplicationContextʱ�����á�
     */
    public void onRefreshContext() throws BeansException {
        initWebxConfiguration();
        initInternalRequestHandler();
        initRequestContexts();
    }

    private void initWebxConfiguration() {
        WebxConfiguration webxConfiguration = getWebxConfiguration();

        log.debug("Initializing Webx root context in {} mode, according to <webx-configuration>",
                webxConfiguration.isProductionMode() ? "production" : "development");
    }

    private void initInternalRequestHandler() {
        internalHandlerMapping = new InternalRequestHandlerMapping();
    }

    private void initRequestContexts() {
        requestContexts = getWebxConfiguration().getRequestContexts();

        log.debug("Using RequestContextChainingService: {}", requestContexts);
    }

    public void onFinishedProcessContext() {
    }

    public final void service(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws Exception {
        RequestContext requestContext = null;

        try {
            requestContext = assertNotNull(getRequestContext(request, response), "could not get requestContext");

            if (checkRequest(requestContext)) {
                request = requestContext.getRequest();
                response = requestContext.getResponse();

                RequestHandlerContext ctx = internalHandlerMapping.getRequestHandler(request, response);

                if (ctx == null) {
                    // ���������passthru filter�����ж�request�Ƿ�passthru��
                    // ������Ҫ��passthru��request��ִ��handleRequest����ֱ�ӷ��ء�
                    // �ù��������ڽ���webx������ͨ��filter����filter chain�Ľ������IJ��ֽ���ʹ��webx���ṩ��request contexts��
                    boolean requestProcessed = false;

                    if (passthruFilter == null || !passthruFilter.matches(request)) {
                        requestProcessed = handleRequest(requestContext);
                    }

                    if (!requestProcessed) {
                        giveUpControl(requestContext, chain);
                    }
                } else {
                    ctx.getRequestHandler().handleRequest(ctx);
                }
            }
        } catch (Throwable e) {
            // �����쳣e�Ĺ��̣�
            //
            // 1. ���ȵ���errorHandler�����쳣e��errorHandler�������ѺõĴ���ҳ�档
            //    errorHandlerҲ�����¼��־ �� �����Ҫ�Ļ���
            // 2. Handler����ֱ�Ӱ��쳣�׻���������servlet engine�ͻ�ӹ�����쳣��ͨ������ʾweb.xml��ָ���Ĵ���ҳ�档
            //    ��������£�errorHandler����Ҫ�����¼��־��
            // 3. ���粻��errorHandler���������쳣����servlet engine�ͻ�ӹ�����쳣��ͨ������ʾweb.xml��ָ���Ĵ���ҳ�档
            //    ��������£������쳣���ᱻ��¼����־�С�
            try {
                try {
                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                } catch (Exception ee) {
                    // ignore this exception
                }

                clearBuffer(requestContext, response);

                // ȡ�ò�ִ��errorHandler
                RequestHandlerContext ctx = internalHandlerMapping.getRequestHandler(request, response, e);

                assertNotNull(ctx, "Could not get exception handler for exception: %s", e);

                // ��¼��־
                ctx.getLogger().error("Error occurred while process request " + request.getRequestURI(), e);

                try {
                    // ����error������̣�����componentΪ�����root component��
                    WebxUtil.setCurrentComponent(request, components.getComponent(null));
                    ctx.getRequestHandler().handleRequest(ctx);
                } finally {
                    WebxUtil.setCurrentComponent(request, null);
                }
            } catch (Throwable ee) {
                // �����������
                // 1. ee causedBy e�����������errorHandler���⽫�쳣�����׳���ת����servlet engine������
                // 2. ee��e�޹أ����������errorHandler������ִ��󡣶��������������Ҫ��¼��־��
                if (!getCauses(ee).contains(e)) {
                    log.error("Another exception occurred while handling exception " + e.getClass().getSimpleName()
                            + ": " + e.getMessage(), ee);
                }

                clearBuffer(requestContext, response);

                if (e instanceof ServletException) {
                    throw (ServletException) e;
                } else if (e instanceof IOException) {
                    throw (IOException) e;
                } else if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                } else if (e instanceof Error) {
                    throw (Error) e;
                } else {
                    throw new ServletException(e);
                }
            }
        } finally {
            if (requestContext != null) {
                try {
                    commitRequestContext(requestContext);
                } catch (Exception e) {
                    log.error("Exception occurred while commit rundata", e);
                }
            }
        }
    }

    /**
     * �������ƣ�������Ȩ���ظ�servlet engine��
     */
    private void giveUpControl(RequestContext requestContext, FilterChain chain) throws IOException, ServletException {
        // 1. �ر�buffering
        BufferedRequestContext brc = findRequestContext(requestContext, BufferedRequestContext.class);

        if (brc != null) {
            try {
                brc.setBuffering(false);
            } catch (IllegalStateException e) {
                // getInputStream��getWriter�Ѿ��������ˣ����ܸ���buffering������
            }
        }

        // 2. ȡ��contentType������
        try {
            requestContext.getResponse().setContentType(null);
        } catch (Exception e) {
            // ignored, �п����е�servlet engine��֧��null����
        }

        // ����filter chain
        chain.doFilter(requestContext.getRequest(), requestContext.getResponse());
    }

    /**
     * ���request���������<code>true</code>�����һ���������󣬷���������������
     */
    protected boolean checkRequest(RequestContext requestContext) {
        LazyCommitRequestContext lcrc = findRequestContext(requestContext, LazyCommitRequestContext.class);

        if (lcrc != null) {
            return !lcrc.isRedirected();
        } else {
            return true;
        }
    }

    /**
     * ��������
     */
    protected abstract boolean handleRequest(RequestContext requestContext) throws Exception;

    /**
     * ���buffer��
     */
    private void clearBuffer(RequestContext requestContext, HttpServletResponse response) {
        // �п������ڴ���requestContextʱ������ʱrequestContextΪ�ա�
        if (requestContext != null) {
            response = requestContext.getResponse();
        }

        if (!response.isCommitted()) {
            response.resetBuffer();
        }
    }

    /**
     * ȡ��request context����
     */
    private RequestContext getRequestContext(HttpServletRequest request, HttpServletResponse response) {
        RequestContext requestContext = RequestContextUtil.getRequestContext(request);

        if (requestContext == null) {
            requestContext = requestContexts.getRequestContext(getServletContext(), request, response);

            request.setAttribute(REQUEST_CONTEXT_OWNER_KEY, this);
        }

        return requestContext;
    }

    /**
     * �ύrequest context��
     */
    private void commitRequestContext(RequestContext requestContext) {
        if (this == requestContext.getRequest().getAttribute(REQUEST_CONTEXT_OWNER_KEY)) {
            requestContext.getRequest().removeAttribute(REQUEST_CONTEXT_OWNER_KEY);
            requestContexts.commitRequestContext(requestContext);
        }
    }

    /**
     * ����webx�ڲ�����������Ϣ��
     */
    private class InternalRequestHandlerContext extends RequestHandlerContext {
        private final RequestHandler handler;

        public InternalRequestHandlerContext(HttpServletRequest request, HttpServletResponse response,
                                             String internalBaseURL, String baseURL, String resourceName,
                                             RequestHandler handler) {
            super(request, response, AbstractWebxRootController.this.getServletContext(), internalBaseURL, baseURL,
                    resourceName);
            this.handler = handler;
        }

        @Override
        public RequestHandler getRequestHandler() {
            return handler;
        }

        @Override
        public Logger getLogger() {
            return log;
        }
    }

    /**
     * ��������webx�ڲ������mapping��
     */
    private class InternalRequestHandlerMapping implements RequestHandlerMapping, ErrorHandlerMapping {
        private final Pattern homepagePattern = Pattern.compile("(^|\\?|&)home(=|&|$)");
        private final boolean productionMode;
        private String internalPathPrefix;
        private RequestHandler mainHandler;
        private RequestHandler errorHandler;
        private Map<String, RequestHandler> internalHandlers = emptyMap();

        public InternalRequestHandlerMapping() {
            productionMode = getWebxConfiguration().isProductionMode();

            // ��mapping�ŵ�application context�У��Ա�ע�뵽handler�С�
            ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) components
                    .getParentApplicationContext()).getBeanFactory();

            beanFactory.registerResolvableDependency(RequestHandlerMapping.class, this);

            // internalPathPrefix
            internalPathPrefix = getWebxConfiguration().getInternalPathPrefix();
            internalPathPrefix = normalizeAbsolutePath(internalPathPrefix, true); // ��񻯳�/internal

            if (isEmpty(internalPathPrefix)) {
                throw new IllegalArgumentException("Invalid internalPathPrefix: "
                        + getWebxConfiguration().getInternalPathPrefix());
            }

            // ��������ʼ��errorHandler
            // ��production mode�£�����config��ָ����exception pipeline����ִ��֮��
            // ����sendError����web.xml��ָ���Ĵ���ҳ��������
            if (productionMode) {
                Pipeline exceptionPipeline = getWebxConfiguration().getExceptionPipeline();

                if (exceptionPipeline == null) {
                    log.debug("No exceptionPipeline configured in <webx-configuration>.");
                    errorHandler = new SendErrorHandler();
                } else {
                    errorHandler = new PipelineErrorHandler(exceptionPipeline);
                }
            }

            // �ڿ�����ģʽ�£���ʾ��ϸ����ҳ�档
            else {
                errorHandler = new DetailedErrorHandler();
                ((DetailedErrorHandler) errorHandler).setName(ERROR_PREFIX);
            }

            autowireAndInitialize(errorHandler, components.getParentApplicationContext(), AUTOWIRE_NO, ERROR_PREFIX);
            log.debug("Using Exception Handler: {}.", errorHandler.getClass().getName());

            // ֻ�ڿ�����ģʽ����ʾ��ҳ������handlers
            if (!productionMode) {
                // ��META-INF/webx.internal-request-handlers��������error handler��main handler
                internalHandlers = loadInternalHandlers(REQUEST_HANDLER_LOCATION);

                // ��������ʼ��mainHandler
                mainHandler = new MainHandler();
                ((MainHandler) mainHandler).setName(EMPTY_STRING);
                autowireAndInitialize(mainHandler, components.getParentApplicationContext(), AUTOWIRE_NO, ERROR_PREFIX);
            }
        }

        public String[] getRequestHandlerNames() {
            return internalHandlers.keySet().toArray(new String[internalHandlers.size()]);
        }

        public RequestHandlerContext getRequestHandler(HttpServletRequest request, HttpServletResponse response) {
            String baseURL = getBaseURL(request);
            String path = getResourcePath(request).replace(' ', '+'); // ���հ׻���+����ΪinternalHandlers��key��������հס�
            String internalBaseURL = baseURL + internalPathPrefix;

            // �����/��ҳ������mainHandler���ڣ�����ģʽ����������ڲ���ҳ
            if (mainHandler != null && (EMPTY_STRING.equals(path) || "/".equals(path))) {
                // ���Dz�����ָ����?home
                String qs = request.getQueryString();

                if (isEmpty(qs) || !homepagePattern.matcher(qs).find()) {
                    return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL, path,
                            mainHandler);
                }
            }

            // �����/internal
            if (startsWithElement(path, internalPathPrefix)) {
                path = removeStartElement(path, internalPathPrefix);

                // �����/error��������ģʽ�Ž���
                if (errorHandler != null && !productionMode && startsWithElement(path, ERROR_PREFIX)) {
                    path = removeStartElement(path, ERROR_PREFIX);
                    return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL + "/"
                            + ERROR_PREFIX, path, errorHandler);
                }

                // internalHandlers��ע���ǰ׺
                for (Map.Entry<String, RequestHandler> entry : internalHandlers.entrySet()) {
                    String prefix = entry.getKey();

                    if (startsWithElement(path, prefix)) {
                        RequestHandler handler = entry.getValue();
                        path = removeStartElement(path, prefix);

                        return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL
                                + "/" + prefix, path, handler);
                    }
                }

                // Ĭ����main page������
                if (mainHandler != null) {
                    return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL, path,
                            mainHandler);
                }

                // ���δƥ��
                throw new ResourceNotFoundException(request.getRequestURI());
            }

            return null;
        }

        public RequestHandlerContext getRequestHandler(HttpServletRequest request, HttpServletResponse response,
                                                       Throwable exception) {
            // servletName == ""
            ErrorHandlerHelper helper = ErrorHandlerHelper.getInstance(request);

            helper.init(EMPTY_STRING, exception, exceptionCodeMapping);
            response.setStatus(helper.getStatusCode());

            String internalBaseURL = getBaseURL(request) + internalPathPrefix;

            return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL + "/"
                    + ERROR_PREFIX, "", errorHandler);
        }

        /**
         * �൱��������ʽ��<code>^element/|^element$</code>��
         */
        private boolean startsWithElement(String path, String element) {
            if (path.equals(element)) {
                return true;
            }

            if (path.startsWith(element) && path.charAt(element.length()) == '/') {
                return true;
            }

            return false;
        }

        /**
         * ��ȥ��ͷ��<code>^element/|^element$</code>��
         */
        private String removeStartElement(String path, String element) {
            if (path.equals(element)) {
                return EMPTY_STRING;
            }

            return path.substring(element.length() + 1);
        }

        private Map<String, RequestHandler> loadInternalHandlers(String location) {
            ClassLoader loader = components.getParentApplicationContext().getClassLoader();
            Properties handlerNames;

            try {
                handlerNames = PropertiesLoaderUtils.loadAllProperties(location, loader);
            } catch (IOException e) {
                throw new WebxException("Could not load " + location, e);
            }

            // װ��handlers
            Map<String, RequestHandler> handlers = createTreeMap(new Comparator<String>() {
                public int compare(String s1, String s2) {
                    int lenDiff = s2.length() - s1.length();

                    if (lenDiff != 0) {
                        return lenDiff; // �Ȱ����Ƴ��ȵ�����
                    } else {
                        return s1.compareTo(s2); // �ٰ���ĸ˳������
                    }
                }
            });

            for (Map.Entry<?, ?> entry : handlerNames.entrySet()) {
                String name = normalizeRelativePath((String) entry.getKey(), true); // ��񻯣�xxx/yyy/zzz
                String handlerClass = trimToNull((String) entry.getValue());

                // ���Կյ�ֵ
                if (!isEmpty(name) && handlerClass != null) {
                    if (ERROR_PREFIX.equals(name)) {
                        log.warn("Ignored request handler with reserved name [" + ERROR_PREFIX + "]: " + handlerClass);
                        continue;
                    }

                    try {
                        Object handler = ClassLoaderUtil.newInstance(handlerClass, loader);

                        if (handler instanceof RequestHandlerNameAware) {
                            ((RequestHandlerNameAware) handler).setName(name);
                        }

                        autowireAndInitialize(handler, components.getParentApplicationContext(), AUTOWIRE_NO, name);

                        try {
                            handlers.put(name, RequestHandler.class.cast(handler));
                        } catch (ClassCastException e) {
                            // �����һ��handler����Ҳ���˳���
                            log.error("Declared internal request handler must implement InternalRequestHandler: "
                                    + name + "=" + handlerClass, e);
                        }
                    } catch (Exception e) {
                        // �����һ��handler����Ҳ���˳���
                        log.error("Could not create internal request handler: " + name + "=" + handlerClass, e);
                    }
                }
            }

            if (log.isDebugEnabled()) {
                log.debug(new ToStringBuilder().append("loading internal request handlers:").append(handlers)
                        .toString());
            }

            return handlers;
        }

        /**
         * Exception��statusCode��ӳ�䡣
         */
        private final ExceptionCodeMapping exceptionCodeMapping = new ExceptionCodeMapping() {
            public int getExceptionCode(Throwable exception) {
                if (exception instanceof ResourceNotFoundException) {
                    return HttpServletResponse.SC_NOT_FOUND;
                } else if (exception instanceof BadRequestException) {
                    return HttpServletResponse.SC_BAD_REQUEST;
                }

                return 0;
            }
        };
    }
}
TOP

Related Classes of com.alibaba.citrus.webx.support.AbstractWebxRootController$InternalRequestHandlerMapping

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.