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 com.sinosoft.one.mvc; 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 com.sinosoft.one.mvc.adapter.ArraysEx; import com.sinosoft.one.mvc.scanner.ModuleResource; import com.sinosoft.one.mvc.scanner.ModuleResourceProvider; import com.sinosoft.one.mvc.scanner.ModuleResourceProviderImpl; import com.sinosoft.one.mvc.scanning.LoadScope; import com.sinosoft.one.mvc.scanning.context.MvcWebAppContext; import com.sinosoft.one.mvc.util.PrinteHelper; import com.sinosoft.one.mvc.web.RequestPath; import com.sinosoft.one.mvc.web.annotation.ReqMethod; import com.sinosoft.one.mvc.web.impl.mapping.ConstantMapping; import com.sinosoft.one.mvc.web.impl.mapping.Mapping; import com.sinosoft.one.mvc.web.impl.mapping.MappingNode; import com.sinosoft.one.mvc.web.impl.mapping.TreeBuilder; import com.sinosoft.one.mvc.web.impl.mapping.ignored.IgnoredPath; import com.sinosoft.one.mvc.web.impl.mapping.ignored.IgnoredPathEnds; import com.sinosoft.one.mvc.web.impl.mapping.ignored.IgnoredPathEquals; import com.sinosoft.one.mvc.web.impl.mapping.ignored.IgnoredPathRegexMatch; import com.sinosoft.one.mvc.web.impl.mapping.ignored.IgnoredPathStarts; import com.sinosoft.one.mvc.web.impl.module.Module; import com.sinosoft.one.mvc.web.impl.module.ModulesBuilder; import com.sinosoft.one.mvc.web.impl.module.ModulesBuilderImpl; import com.sinosoft.one.mvc.web.impl.thread.LinkedEngine; import com.sinosoft.one.mvc.web.impl.thread.RootEngine; import com.sinosoft.one.mvc.web.impl.thread.Mvc; import com.sinosoft.one.mvc.web.instruction.InstructionExecutor; import com.sinosoft.one.mvc.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; /** * Mvc Servlet?Spring?WEB? * <p> * Mvc web.xml???webMvc?? * Mvc??. * Mvc???Mvcweb?? * <p> * * Mvc?Servlet?web??? * <p> * Servlet???? * ?web?web.xmlServlet??webFilter * Filter?RequestResponse * Filter???????. * <p> * Mvc?web????uri? * Mvc???? * <p> * ??Mvc??FilterServletServletServlet? * ????? * Servlet??Servlet????web.xml? * (404500?) * <p> * * web.xml???FilterFilter?????Mvc??? * Mvc???mapping?Mvc? * <p> * * MvcFilter???? * * <pre> * <filter> * <filter-name>mvcFilter</filter-name> * <filter-class>com.sinosoft.one.mvc.MvcFilter</filter-class> * </filter> * <filter-mapping> * <filter-name>mvcFilter</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? * includeMvc?<br> * <p> * * Mvc<strong>"?->"</strong>Mvc? * ???Mvc? ????? * ??6?<strong>?? - ? - - - * -?"</strong>? * <p> * * <strong>?</strong>: <br> * TODO * <p> * * <strong>?</strong>: <br> * TODO * <P> * * <strong>??</strong>: <br> * ?? * ?Mvc?2??????????( * )??Mvc??? * <P> * * <strong></strong>: <br> * Mvc????Mvc????Mvc * ??????????? * <P> * * <strong></strong>: <br> * * */ public class MvcFilter 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(MvcConstants.VIEWS_PATH_WITH_END_SEP), new IgnoredPathEquals("/favicon.ico") }; /** * ?Mvc??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 = ArraysEx.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()} Mvc ? */ @Override protected final void initFilterBean() throws ServletException { try { long startTime = System.currentTimeMillis(); 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'"); } // Mvc ?? 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'"); } long endTime = System.currentTimeMillis(); // ??? printMvcInfos(endTime - startTime); // } catch (final Throwable e) { StringBuilder sb = new StringBuilder(1024); sb.append("[Mvc-").append(MvcVersion.getVersion()); sb.append("@Spring-").append(SpringVersion.getVersion()).append("]:"); sb.append(e.getMessage()); logger.error(sb.toString(), e); throw new NestedServletException(sb.toString(), e); } } /** * MvcFilter ???????? * ??Mvcweb?? */ 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?MvcFilter 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()); } supportsMvcpipe(httpRequest); // RequestPath?? final RequestPath requestPath = new RequestPath(httpRequest); // ???Mvctrue if (quicklyPass(requestPath)) { notMatched(filterChain, httpRequest, httpResponse, requestPath); return; } // matchedtrueMvc???? filter servlet boolean matched = false; try { // mvc Mvc final Mvc mvc = new Mvc(modules, mappingTree, httpRequest, httpResponse, requestPath); // ?????????false matched = mvc.start(); } catch (Throwable exception) { throwServletException(requestPath, exception); } // ?Mvc?WEB???try-catch? if (!matched) { notMatched(filterChain, httpRequest, httpResponse, requestPath); } } // @see com.sinosoft.one.mvc.web.portal.impl.PortalWaitInterceptor#waitForWindows protected void supportsMvcpipe(final HttpServletRequest httpRequest) { // ?mvcpipe??mvcpipe"Cannot forward after response has been committed" // @see com.sinosoft.one.mvc.web.portal.impl.PortalWaitInterceptor // String windowNames = (String)httpRequest.getAttribute(MvcConstants.WINDOW_ATTR+".names"); String windowName = (String) httpRequest.getAttribute(MvcConstants.WINDOW_ATTR + ".name"); if (StringUtils.isNotBlank(windowName)) { Object window = httpRequest.getAttribute(MvcConstants.WINDOW_ATTR + "." + windowName); if (window != null && window.getClass().getName().startsWith("com.sinosoft.one.mvc.web.portal")) { httpRequest.setAttribute(MvcConstants.PIPE_WINDOW_IN, Boolean.TRUE); if (logger.isDebugEnabled()) { try { logger.debug( "notify window '" + httpRequest.getAttribute("$$one-mvc-portal.window.name") + "'"); } catch (Exception e) { logger.error("", e); } } synchronized (window) { window.notifyAll(); httpRequest.removeAttribute(MvcConstants.WINDOW_ATTR + ".name"); } } } // Object window = httpRequest.getAttribute(MvcConstants.WINDOW_ATTR); } /** * 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 ...... ?? // mvcFilter?????rootContext????? // ?Listenerinit mvc context if (oldRootContext != null) { if (oldRootContext.getClass() != MvcWebAppContext.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 (MvcWebAppContext) oldRootContext; } MvcWebAppContext rootContext = new MvcWebAppContext(getServletContext(), load, false); String contextConfigLocation = this.contextConfigLocation; // applicationContext? if (StringUtils.isBlank(contextConfigLocation)) { String webxmlContextConfigLocation = getServletContext().getInitParameter("contextConfigLocation"); if (StringUtils.isBlank(webxmlContextConfigLocation)) { contextConfigLocation = MvcWebAppContext.DEFAULT_CONFIG_LOCATION; } else { contextConfigLocation = webxmlContextConfigLocation; } } rootContext.setConfigLocation(contextConfigLocation); rootContext.setId("mvc.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 mvc.root WebApplicationContext [" + rootContext + "] as ServletContext attribute with name [" + ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } return rootContext; } private List<Module> prepareModules(WebApplicationContext rootContext) throws Exception { // ??web?Mvc? 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; } /** * ???Mvctrue * * @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(); // mvc.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 mvc 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 printMvcInfos(long initTimeCost) { if (logger.isDebugEnabled()) { logger.debug(PrinteHelper.dumpModules(modules)); logger.debug("mapping tree:\n" + PrinteHelper.list(mappingTree)); } String msg = String.format("[init] mvc initialized, %s modules loaded, cost %sms! (version=%s)", modules.size(), initTimeCost, MvcVersion.getVersion()); logger.info(msg); getServletContext().log(msg); } }