net.paoding.rose.RoseFilter.java Source code

Java tutorial

Introduction

Here is the source code for net.paoding.rose.RoseFilter.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 net.paoding.rose;

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 net.paoding.rose.scanner.ModuleResource;
import net.paoding.rose.scanner.ModuleResourceProvider;
import net.paoding.rose.scanner.ModuleResourceProviderImpl;
import net.paoding.rose.scanning.LoadScope;
import net.paoding.rose.scanning.context.RoseWebAppContext;
import net.paoding.rose.util.PrinteHelper;
import net.paoding.rose.web.RequestPath;
import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.impl.mapping.ConstantMapping;
import net.paoding.rose.web.impl.mapping.Mapping;
import net.paoding.rose.web.impl.mapping.MappingNode;
import net.paoding.rose.web.impl.mapping.TreeBuilder;
import net.paoding.rose.web.impl.mapping.ignored.IgnoredPath;
import net.paoding.rose.web.impl.mapping.ignored.IgnoredPathEnds;
import net.paoding.rose.web.impl.mapping.ignored.IgnoredPathEquals;
import net.paoding.rose.web.impl.mapping.ignored.IgnoredPathRegexMatch;
import net.paoding.rose.web.impl.mapping.ignored.IgnoredPathStarts;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.module.ModulesBuilder;
import net.paoding.rose.web.impl.module.ModulesBuilderImpl;
import net.paoding.rose.web.impl.thread.LinkedEngine;
import net.paoding.rose.web.impl.thread.RootEngine;
import net.paoding.rose.web.impl.thread.Rose;
import net.paoding.rose.web.instruction.InstructionExecutor;
import net.paoding.rose.web.instruction.InstructionExecutorImpl;

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;

/**
 * Rose Servlet?Spring?WEB?
 * <p>
 * Rose web.xml???webRose??
 * Rose??.
 * Rose???Roseweb??
 * <p>
 * 
 * Rose?Servlet?web???
 * <p>
 * Servlet????
 * ?web?web.xmlServlet??webFilter
 * Filter?RequestResponse
 * Filter???????.
 * <p>
 * Rose?web????uri?
 * Rose????
 * <p>
 * ??Rose??FilterServletServletServlet?
 *  ?????
 * Servlet??Servlet????web.xml?
 * (404500?)
 * <p>
 * 
 * web.xml???FilterFilter?????Rose???
 *  Rose???mapping?Rose?
 * <p>
 * 
 * RoseFilter????
 * 
 * <pre>
 *    &lt;filter&gt;
 *       &lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
 *       &lt;filter-class&gt;net.paoding.rose.RoseFilter&lt;/filter-class&gt;
 *    &lt;/filter&gt;
 *    &lt;filter-mapping&gt;
 *       &lt;filter-name&gt;roseFilter&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?
 * includeRose?<br>
 * <p>
 * 
 * Rose<strong>"?->"</strong>Rose?
 * ???Rose? ?????
 * ??6?<strong>?? - ? -  -  - 
 * -?"</strong>?
 * <p>
 * 
 * <strong>?</strong>: <br>
 * TODO
 * <p>
 * 
 * <strong>?</strong>: <br>
 * TODO
 * <P>
 * 
 * <strong>??</strong>: <br>
 * ??
 * ?Rose?2??????????(
 * )??Rose???
 * <P>
 * 
 * <strong></strong>: <br>
 * Rose????Rose????Rose
 * ???????????
 * <P>
 * 
 * <strong></strong>: <br>
 * 
 * @author  [qieqie.wang@gmail.com]
 */
public class RoseFilter 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(RoseConstants.VIEWS_PATH_WITH_END_SEP), new IgnoredPathEquals("/favicon.ico") };

    /**
     * ?Rose??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()} Rose ?
     */

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

            if (logger.isDebugEnabled()) {
                StringBuilder sb = new StringBuilder();
                @SuppressWarnings("unchecked")
                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'");
            }

            //  Rose ??
            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'");
            }

            // ???
            printRoseInfos();

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

    /**
     *  RoseFilter ????????
     * ??Roseweb??
     */
    @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?RoseFilter
        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());
        }

        supportsRosepipe(httpRequest);

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

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

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

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

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

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

    // @see net.paoding.rose.web.portal.impl.PortalWaitInterceptor#waitForWindows
    protected void supportsRosepipe(final HttpServletRequest httpRequest) {
        // ?rosepipe??rosepipe"Cannot forward after response has been committed"
        // @see net.paoding.rose.web.portal.impl.PortalWaitInterceptor
        Object window = httpRequest.getAttribute(RoseConstants.WINDOW_ATTR);
        if (window != null && window.getClass().getName().startsWith("net.paoding.rose.web.portal")) {
            httpRequest.setAttribute(RoseConstants.PIPE_WINDOW_IN, Boolean.TRUE);
            if (logger.isDebugEnabled()) {
                try {
                    logger.debug("notify window '" + httpRequest.getAttribute("$$paoding-rose-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 ...... ??
        // roseFilter?????rootContext?????
        // ?Listenerinit rose context
        if (oldRootContext != null) {
            if (oldRootContext.getClass() != RoseWebAppContext.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 (RoseWebAppContext) oldRootContext;
        }

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

        String contextConfigLocation = this.contextConfigLocation;
        // applicationContext?
        if (StringUtils.isBlank(contextConfigLocation)) {
            String webxmlContextConfigLocation = getServletContext().getInitParameter("contextConfigLocation");
            if (StringUtils.isBlank(webxmlContextConfigLocation)) {
                contextConfigLocation = RoseWebAppContext.DEFAULT_CONFIG_LOCATION;
            } else {
                contextConfigLocation = webxmlContextConfigLocation;
            }
        }
        rootContext.setConfigLocation(contextConfigLocation);
        rootContext.setId("rose.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 rose.root WebApplicationContext [" + rootContext
                    + "] as ServletContext attribute with name [" + ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }

        return rootContext;
    }

    private List<Module> prepareModules(WebApplicationContext rootContext) throws Exception {
        // ??web?Rose?
        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;
    }

    /**
     * ???Rosetrue
     * 
     * @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(); // rose.root
                }
            } catch (Throwable e) {
                logger.error("", e);
                getServletContext().log("", e);
            }
        }

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

    protected void notMatched(//
            FilterChain filterChain, //
            HttpServletRequest httpRequest, //
            HttpServletResponse httpResponse, //
            RequestPath path)//
            throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("not rose uri: " + path.getUri());
        }
        // Filter
        filterChain.doFilter(httpRequest, httpResponse);
    }

    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 printRoseInfos() {
        if (logger.isDebugEnabled()) {
            logger.debug(PrinteHelper.dumpModules(modules));
            logger.debug("mapping tree:\n" + PrinteHelper.list(mappingTree));
        }

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