Java tutorial
/* * 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> * <filter> * <filter-name>roseFilter</filter-name> * <filter-class>net.paoding.rose.RoseFilter</filter-class> * </filter> * <filter-mapping> * <filter-name>roseFilter</filter-name> * <url-pattern>/*</url-pattern> * <dispatcher>REQUEST</dispatcher> * <dispatcher>FORWARD</dispatcher> * <dispatcher>INCLUDE</dispatcher> * </filter-mapping> * </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: "com.renren.myapp, com.renren.yourapp" 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); } }