com.laxser.blitz.BlitzFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.laxser.blitz.BlitzFilter.java

Source

/*
 * Copyright 2007-2009 the original author or authors.
 *
 * 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 i 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.laxser.blitz;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.SpringVersion;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.NestedServletException;

import com.laxser.blitz.scanner.ModuleResource;
import com.laxser.blitz.scanner.ModuleResourceProvider;
import com.laxser.blitz.scanner.ModuleResourceProviderImpl;
import com.laxser.blitz.scanning.LoadScope;
import com.laxser.blitz.scanning.context.BlitzWebAppContext;
import com.laxser.blitz.util.PrinteHelper;
import com.laxser.blitz.web.RequestPath;
import com.laxser.blitz.web.annotation.ReqMethod;
import com.laxser.blitz.web.impl.mapping.ConstantMapping;
import com.laxser.blitz.web.impl.mapping.Mapping;
import com.laxser.blitz.web.impl.mapping.MappingNode;
import com.laxser.blitz.web.impl.mapping.TreeBuilder;
import com.laxser.blitz.web.impl.mapping.ignored.IgnoredPath;
import com.laxser.blitz.web.impl.mapping.ignored.IgnoredPathEnds;
import com.laxser.blitz.web.impl.mapping.ignored.IgnoredPathEquals;
import com.laxser.blitz.web.impl.mapping.ignored.IgnoredPathRegexMatch;
import com.laxser.blitz.web.impl.mapping.ignored.IgnoredPathStarts;
import com.laxser.blitz.web.impl.module.Module;
import com.laxser.blitz.web.impl.module.ModulesBuilder;
import com.laxser.blitz.web.impl.module.ModulesBuilderImpl;
import com.laxser.blitz.web.impl.thread.LinkedEngine;
import com.laxser.blitz.web.impl.thread.RootEngine;
import com.laxser.blitz.web.impl.thread.Blitz;
import com.laxser.blitz.web.instruction.InstructionExecutor;
import com.laxser.blitz.web.instruction.InstructionExecutorImpl;

/**
 * Blitz Servlet?Spring?WEB?
 * <p>
 * Blitz web.xml???webBlitz??
 * Blitz??.
 * Blitz???Blitzweb??
 * <p>
 * 
 * Blitz?Servlet?web???
 * <p>
 * Servlet????
 * ?web?web.xmlServlet??webFilter
 * Filter?RequestResponse
 * Filter???????.
 * <p>
 * Blitz?web????uri?
 * Blitz????
 * <p>
 * ??Blitz??FilterServletServletServlet?
 *  ?????
 * Servlet??Servlet????web.xml?
 * (404500?)
 * <p>
 * 
 * web.xml???FilterFilter?????Blitz???
 *  Blitz???mapping?Blitz?
 * <p>
 * 
 * BlitzFilter????
 * 
 * <pre>
 *    &lt;filter&gt;
 *       &lt;filter-name&gt;BlitzFilter&lt;/filter-name&gt;
 *       &lt;filter-class&gt;net.paoding.Blitz.BlitzFilter&lt;/filter-class&gt;
 *    &lt;/filter&gt;
 *    &lt;filter-mapping&gt;
 *       &lt;filter-name&gt;BlitzFilter&lt;/filter-name&gt;
 *       &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
 *       &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
 *       &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
 *       &lt;dispatcher&gt;INCLUDE&lt;/dispatcher&gt;
 *    &lt;/filter-mapping&gt;
 * </pre>
 * 
 * 1) <strong>filter-mapping</strong> ?Filter Mapping?<br>
 * 2) ? <strong>FORWARD?INCLUDE</strong>  dispatcher ?forward?
 * includeBlitz?<br>
 * <p>
 * 
 * Blitz<strong>"?->"</strong>Blitz?
 * ???Blitz? ?????
 * ??6?<strong>?? - ? -  -  - 
 * -?"</strong>?
 * <p>
 * 
 * <strong>?</strong>: <br>
 * TODO
 * <p>
 * 
 * <strong>?</strong>: <br>
 * TODO
 * <P>
 * 
 * <strong>??</strong>: <br>
 * ??
 * ?Blitz?2??????????(
 * )??Blitz???
 * <P>
 * 
 * <strong></strong>: <br>
 * Blitz????Blitz????Blitz
 * ???????????
 * <P>
 * 
 * <strong></strong>: <br>
 * 
 * 
 */
public class BlitzFilter extends GenericFilterBean {

    private static final String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;

    /** applicationContext? */
    private String contextConfigLocation;

    private InstructionExecutor instructionExecutor = new InstructionExecutorImpl();

    private List<Module> modules;

    private MappingNode mappingTree;

    private Class<? extends ModuleResourceProvider> moduleResourceProviderClass = ModuleResourceProviderImpl.class;

    private Class<? extends ModulesBuilder> modulesBuilderClass = ModulesBuilderImpl.class;

    private LoadScope load = new LoadScope("", "controllers");

    private IgnoredPath[] ignoredPaths = new IgnoredPath[] {
            new IgnoredPathStarts(BlitzConstants.VIEWS_PATH_WITH_END_SEP), new IgnoredPathEquals("/favicon.ico") };

    /**
     * ?Blitz??applicationContext?
     */
    public void setContextConfigLocation(String contextConfigLocation) {
        if (StringUtils.isBlank(contextConfigLocation)) {
            throw new IllegalArgumentException("contextConfigLocation");
        }
        this.contextConfigLocation = contextConfigLocation;
    }

    public void setInstructionExecutor(InstructionExecutor instructionExecutor) {
        this.instructionExecutor = instructionExecutor;
    }

    public void setModuleResourceProviderClass(
            Class<? extends ModuleResourceProvider> moduleResourceProviderClass) {
        this.moduleResourceProviderClass = moduleResourceProviderClass;
    }

    public void setModulesBuilderClass(Class<? extends ModulesBuilder> modulesBuilderClass) {
        this.modulesBuilderClass = modulesBuilderClass;
    }

    /**
     * <pre>
     * like: &quot;com.renren.myapp, com.renren.yourapp&quot; etc
     * </pre>
     * 
     * @param load
     */
    public void setLoad(String load) {
        this.load = new LoadScope(load, "controllers");
    }

    /**
     * @see #quicklyPass(RequestPath)
     * @param ignoredPathStrings
     */
    public void setIgnoredPaths(String[] ignoredPathStrings) {
        List<IgnoredPath> list = new ArrayList<IgnoredPath>(ignoredPathStrings.length + 2);
        for (String ignoredPath : ignoredPathStrings) {
            ignoredPath = ignoredPath.trim();
            if (StringUtils.isEmpty(ignoredPath)) {
                continue;
            }
            if (ignoredPath.equals("*")) {
                list.add(new IgnoredPathEquals(""));
                list.add(new IgnoredPathStarts("/"));
                break;
            }
            if (ignoredPath.startsWith("regex:")) {
                list.add(new IgnoredPathRegexMatch(ignoredPath.substring("regex:".length())));
            } else {
                if (ignoredPath.length() > 0 && !ignoredPath.startsWith("/") && !ignoredPath.startsWith("*")) {
                    ignoredPath = "/" + ignoredPath;
                }
                if (ignoredPath.endsWith("*")) {
                    list.add(new IgnoredPathStarts(ignoredPath.substring(0, ignoredPath.length() - 1)));
                } else if (ignoredPath.startsWith("*")) {
                    list.add(new IgnoredPathEnds(ignoredPath.substring(1)));
                } else {
                    list.add(new IgnoredPathEquals(ignoredPath));
                }
            }
        }
        IgnoredPath[] ignoredPaths = Arrays.copyOf(this.ignoredPaths, this.ignoredPaths.length + list.size());
        for (int i = this.ignoredPaths.length; i < ignoredPaths.length; i++) {
            ignoredPaths[i] = list.get(i - this.ignoredPaths.length);
        }
        this.ignoredPaths = ignoredPaths;
    }

    /**
     *  {@link GenericFilterBean#initFilterBean()} Blitz ?
     */

    @Override
    protected final void initFilterBean() throws ServletException {
        try {
            if (logger.isInfoEnabled()) {
                logger.info("[init] call 'init/rootContext'");
            }

            if (logger.isDebugEnabled()) {
                StringBuilder sb = new StringBuilder();
                Enumeration<String> iter = getFilterConfig().getInitParameterNames();
                while (iter.hasMoreElements()) {
                    String name = (String) iter.nextElement();
                    sb.append(name).append("='").append(getFilterConfig().getInitParameter(name)).append("'\n");
                }
                logger.debug("[init] parameters: " + sb);
            }

            WebApplicationContext rootContext = prepareRootApplicationContext();

            if (logger.isInfoEnabled()) {
                logger.info("[init] exits from 'init/rootContext'");
                logger.info("[init] call 'init/module'");
            }

            //  Blitz ??
            this.modules = prepareModules(rootContext);

            if (logger.isInfoEnabled()) {
                logger.info("[init] exits from 'init/module'");
                logger.info("[init] call 'init/mappingTree'");
            }

            // ???(Engine)
            this.mappingTree = prepareMappingTree(modules);

            if (logger.isInfoEnabled()) {
                logger.info("[init] exits from 'init/mappingTree'");
                logger.info("[init] exits from 'init'");
            }

            // ???
            printBlitzInfos();

            //
        } catch (final Throwable e) {
            StringBuilder sb = new StringBuilder(1024);
            sb.append("[Blitz-").append(BlitzVersion.getVersion());
            sb.append("@Spring-").append(SpringVersion.getVersion()).append("]:");
            sb.append(e.getMessage());
            logger.error(sb.toString(), e);
            throw new NestedServletException(sb.toString(), e);
        }
    }

    /**
     *  BlitzFilter ????????
     * ??Blitzweb??
     */
    @Override
    public void doFilter(ServletRequest request, final ServletResponse response, final FilterChain filterChain)
            throws IOException, ServletException {
        // cast
        final HttpServletRequest httpRequest = (HttpServletRequest) request;
        final HttpServletResponse httpResponse = (HttpServletResponse) response;

        // DEBUG?BlitzFilter
        if (logger.isDebugEnabled()) {
            StringBuffer sb = httpRequest.getRequestURL();
            String query = httpRequest.getQueryString();
            if (query != null && query.length() > 0) {
                sb.append("?").append(query);
            }
            logger.debug(httpRequest.getMethod() + " " + sb.toString());
        }

        supportsBlitzpipe(httpRequest);

        // RequestPath??
        final RequestPath requestPath = new RequestPath(httpRequest);

        //  ???Blitztrue
        if (quicklyPass(requestPath)) {
            notMatched(filterChain, httpRequest, httpResponse, requestPath);
            return;
        }

        // matchedtrueBlitz???? flter  servlet
        boolean matched = false;
        try {
            // Blitz Blitz?
            final Blitz Blitz = new Blitz(modules, mappingTree, httpRequest, httpResponse, requestPath);

            // ?????????false
            matched = Blitz.start();

        } catch (Throwable exception) {
            throwServletException(requestPath, exception);
        }

        // ?Blitz?WEB???try-catch?
        if (!matched) {
            notMatched(filterChain, httpRequest, httpResponse, requestPath);
        }
    }

    // @see net.paoding.Blitz.web.portal.impl.PortalWaitInterceptor#waitForWindows
    protected void supportsBlitzpipe(final HttpServletRequest httpRequest) {
        // ?Blitzpipe??Blitzpipe"Cannot forward after response has been committed"
        // @see net.paoding.Blitz.web.portal.impl.PortalWaitInterceptor
        Object window = httpRequest.getAttribute(BlitzConstants.WINDOW_ATTR);
        if (window != null && window.getClass().getName().startsWith("com.laxser.blitz.web.portal")) {
            httpRequest.setAttribute(BlitzConstants.PIPE_WINDOW_IN, Boolean.TRUE);
            if (logger.isDebugEnabled()) {
                try {
                    logger.debug("notify window '" + httpRequest.getAttribute("$$blitz-portal.window.name") + "'");
                } catch (Exception e) {
                    logger.error("", e);
                }
            }
            synchronized (window) {
                window.notifyAll();
            }
        }
    }

    /**
     *  ApplicationContext WEB-INF?WEB-INF/classes?
     * jarspring???? ApplicationContext 
     * 
     * @return
     * @throws IOException
     */
    private WebApplicationContext prepareRootApplicationContext() throws IOException {

        if (logger.isInfoEnabled()) {
            logger.info("[init/rootContext] starting ...");
        }

        ApplicationContext oldRootContext = (ApplicationContext) getServletContext()
                .getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        // web.xml?springrootcontext ...... ??
        // BlitzFilter?????rootContext?????
        // ?Listenerinit Blitz context
        if (oldRootContext != null) {
            if (oldRootContext.getClass() != BlitzWebAppContext.class) {
                throw new IllegalStateException(
                        "Cannot initialize context because there is already a root application context present - "
                                + "check whether you have multiple ContextLoader* definitions in your web.xml!");
            }
            if (logger.isInfoEnabled()) {
                logger.info("[init/rootContext] the root context exists:" + oldRootContext);
            }
            return (BlitzWebAppContext) oldRootContext;
        }

        BlitzWebAppContext rootContext = new BlitzWebAppContext(getServletContext(), load, false);

        String contextConfigLocation = this.contextConfigLocation;
        // applicationContext?
        if (StringUtils.isBlank(contextConfigLocation)) {
            String webxmlContextConfigLocation = getServletContext().getInitParameter("contextConfigLocation");
            if (StringUtils.isBlank(webxmlContextConfigLocation)) {
                contextConfigLocation = BlitzWebAppContext.DEFAULT_CONFIG_LOCATION;
            } else {
                contextConfigLocation = webxmlContextConfigLocation;
            }
        }
        rootContext.setConfigLocation(contextConfigLocation);
        rootContext.setId("Blitz.root");
        rootContext.refresh();

        if (logger.isInfoEnabled()) {
            logger.info("[init/rootContext] exits");
        }

        /* enable: WebApplicationContextUtils.getWebApplicationContext() */
        getServletContext().setAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootContext);

        if (logger.isInfoEnabled()) {
            logger.info("[init/rootContext] Published Blitz.root WebApplicationContext [" + rootContext
                    + "] as ServletContext attribute with name [" + ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }

        return rootContext;
    }

    private List<Module> prepareModules(WebApplicationContext rootContext) throws Exception {
        // ??web?Blitz?
        if (logger.isInfoEnabled()) {
            logger.info("[init/mudule] starting ...");
        }

        ModuleResourceProvider provider = moduleResourceProviderClass.newInstance();

        if (logger.isInfoEnabled()) {
            logger.info("[init/module] using provider: " + provider);
            logger.info("[init/module] call 'moduleResource': to find all module resources.");
            logger.info("[init/module] load " + load);
        }
        List<ModuleResource> moduleResources = provider.findModuleResources(load);

        if (logger.isInfoEnabled()) {
            logger.info("[init/mudule] exits 'moduleResource'");
        }

        ModulesBuilder modulesBuilder = modulesBuilderClass.newInstance();

        if (logger.isInfoEnabled()) {
            logger.info("[init/module] using modulesBuilder: " + modulesBuilder);
            logger.info("[init/module] call 'moduleBuild': to build modules.");
        }

        List<Module> modules = modulesBuilder.build(moduleResources, rootContext);

        if (logger.isInfoEnabled()) {
            logger.info("[init/module] exits from 'moduleBuild'");
            logger.info("[init/mudule] found " + modules.size() + " modules.");
        }

        return modules;
    }

    private MappingNode prepareMappingTree(List<Module> modules) {
        Mapping rootMapping = new ConstantMapping("");
        MappingNode mappingTree = new MappingNode(rootMapping);
        LinkedEngine rootEngine = new LinkedEngine(null, new RootEngine(instructionExecutor), mappingTree);
        mappingTree.getMiddleEngines().addEngine(ReqMethod.ALL, rootEngine);

        TreeBuilder treeBuilder = new TreeBuilder();
        treeBuilder.create(mappingTree, modules);

        return mappingTree;
    }

    /**
     * ???Blitztrue
     * 
     * @param requestPath
     * @return
     */
    private boolean quicklyPass(final RequestPath requestPath) {
        for (IgnoredPath p : ignoredPaths) {
            if (p.hit(requestPath)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy() {
        WebApplicationContext rootContext = WebApplicationContextUtils
                .getWebApplicationContext(getServletContext());
        if (rootContext != null) {
            try {
                if (rootContext instanceof AbstractApplicationContext) {
                    ((AbstractApplicationContext) rootContext).close(); // Blitz.root
                }
            } catch (Throwable e) {
                logger.error("", e);
                getServletContext().log("", e);
            }
        }

        try {
            mappingTree.destroy();
        } catch (Throwable e) {
            logger.error("", e);
            getServletContext().log("", e);
        }
        super.destroy();
    }

    /**
     * Blitz ? Blitz???BlitzUriBlitz?
     * ?resp reqFilterChainFilter?
     * 
     * @param filterChain
     * @param httpRequest
     * @param httpResponse
     * @param path
     * @throws IOException
     * @throws ServletException
     */
    protected void notMatched(//
            FilterChain filterChain, //
            HttpServletRequest httpRequest, //
            HttpServletResponse httpResponse, //
            RequestPath path)//
            throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("not a Blitz uri: " + path.getUri());
        }
        // Filter
        filterChain.doFilter(httpRequest, httpResponse);
    }

    /**
     * ?
     * @param requestPath
     * @param exception
     * @throws ServletException
     */
    private void throwServletException(RequestPath requestPath, Throwable exception) throws ServletException {
        String msg = requestPath.getMethod() + " " + requestPath.getUri();
        ServletException servletException;
        if (exception instanceof ServletException) {
            servletException = (ServletException) exception;
        } else {
            servletException = new NestedServletException(msg, exception);
        }
        logger.error(msg, exception);
        getServletContext().log(msg, exception);
        throw servletException;
    }

    private void printBlitzInfos() {
        if (logger.isDebugEnabled()) {
            logger.debug(PrinteHelper.dumpModules(modules));
            logger.debug("Mapping tree:\n" + PrinteHelper.list(mappingTree));
        }

        String msg = String.format("[init] Blitz initialized, %s modules loaded! (version=%s)", modules.size(),
                BlitzVersion.getVersion());
        logger.info(msg);
        getServletContext().log(msg);
    }
}