/* * Copyright (c) 2002-2012 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.context; 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.FileUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import static com.alibaba.citrus.util.regex.PathNameWildcardCompiler.*; import static com.alibaba.citrus.webx.WebxConstant.*; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletContext; import com.alibaba.citrus.springext.util.SpringExtUtil; import com.alibaba.citrus.util.ToStringBuilder; import com.alibaba.citrus.util.ToStringBuilder.MapBuilder; import com.alibaba.citrus.webx.WebxComponent; import com.alibaba.citrus.webx.WebxComponents; import com.alibaba.citrus.webx.WebxController; import com.alibaba.citrus.webx.WebxRootController; import com.alibaba.citrus.webx.config.WebxConfiguration; import com.alibaba.citrus.webx.config.WebxConfiguration.ComponentConfig; import com.alibaba.citrus.webx.config.WebxConfiguration.ComponentsConfig; import com.alibaba.citrus.webx.config.impl.WebxConfigurationImpl; import com.alibaba.citrus.webx.config.impl.WebxConfigurationImpl.ComponentsConfigImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SourceFilteringListener; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.ClassUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.ServletContextResourcePatternResolver; /** * 用来装载webx components的装载器。 * * @author Michael Zhou */ public class WebxComponentsLoader extends ContextLoader { private final static Logger log = LoggerFactory.getLogger(WebxComponentsLoader.class); private String webxConfigurationName; private ServletContext servletContext; private WebApplicationContext componentsContext; private WebxComponentsImpl components; /** 取得context中<code>WebxConfiguration</code>的名称。 */ public String getWebxConfigurationName() { return webxConfigurationName == null ? "webxConfiguration" : webxConfigurationName; } /** 设置context中<code>WebxConfiguration</code>的名称。 */ public void setWebxConfigurationName(String webxConfigurationName) { this.webxConfigurationName = trimToNull(webxConfigurationName); } /** 取得在servlet context中保存component context的key。 */ public String getComponentContextAttributeName(String componentName) { return COMPONENT_CONTEXT_PREFIX + componentName; } public ServletContext getServletContext() { return servletContext; } /** 取得components。 */ public WebxComponents getWebxComponents() { return components; } @Override public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException, BeansException { this.servletContext = servletContext; init(); return super.initWebApplicationContext(servletContext); } protected void init() { setWebxConfigurationName(servletContext.getInitParameter("webxConfigurationName")); } @Override protected final Class<?> determineContextClass(ServletContext servletContext) throws ApplicationContextException { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName); } catch (ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex); } } else { return getDefaultContextClass(); } } /** * 取得默认的components <code>WebApplicationContext</code>实现类。 * <p> * 子类可以覆盖并修改此方法。 * </p> */ protected Class<? extends WebxComponentsContext> getDefaultContextClass() { return WebxComponentsContext.class; } /** 在componentsContext.refresh()之前被调用。 */ @Override protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext componentsContext) { this.componentsContext = componentsContext; if (componentsContext instanceof WebxComponentsContext) { ((WebxComponentsContext) componentsContext).setLoader(this); } } /** * 在创建beanFactory之初被调用。 * * @param webxComponentsContext */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // 由于初始化components依赖于webxConfiguration,而webxConfiguration可能需要由PropertyPlaceholderConfigurer来处理, // 此外,其它有一些BeanFactoryPostProcessors会用到components, // 因此components必须在PropertyPlaceholderConfigurer之后初始化,并在其它BeanFactoryPostProcessors之前初始化。 // // 下面创建的WebxComponentsCreator辅助类就是用来确保这个初始化顺序: // 1. PriorityOrdered - PropertyPlaceholderConfigurer // 2. Ordered - WebxComponentsCreator // 3. 普通 - 其它BeanFactoryPostProcessors BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(WebxComponentsCreator.class); builder.addConstructorArgValue(this); BeanDefinition componentsCreator = builder.getBeanDefinition(); componentsCreator.setAutowireCandidate(false); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; String name = SpringExtUtil.generateBeanName(WebxComponentsCreator.class.getName(), registry); registry.registerBeanDefinition(name, componentsCreator); } public static class WebxComponentsCreator implements BeanFactoryPostProcessor, Ordered { private final WebxComponentsLoader loader; public WebxComponentsCreator(WebxComponentsLoader loader) { this.loader = assertNotNull(loader, "WebxComponentsLoader"); } public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (loader.components == null) { WebxComponentsImpl components = loader.createComponents(loader.getParentConfiguration(), beanFactory); AbstractApplicationContext wcc = (AbstractApplicationContext) components.getParentApplicationContext(); wcc.addApplicationListener(new SourceFilteringListener(wcc, components)); loader.components = components; } } public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } /** 初始化所有components。 */ public void finishRefresh() { components.getWebxRootController().onFinishedProcessContext(); for (WebxComponent component : components) { logInBothServletAndLoggingSystem("Initializing Spring sub WebApplicationContext: " + component.getName()); WebxComponentContext wcc = (WebxComponentContext) component.getApplicationContext(); WebxController controller = component.getWebxController(); wcc.refresh(); controller.onFinishedProcessContext(); } logInBothServletAndLoggingSystem("WebxComponents: initialization completed"); } private void logInBothServletAndLoggingSystem(String msg) { servletContext.log(msg); log.info(msg); } /** 初始化components。 */ private WebxComponentsImpl createComponents(WebxConfiguration parentConfiguration, ConfigurableListableBeanFactory beanFactory) { ComponentsConfig componentsConfig = getComponentsConfig(parentConfiguration); // 假如isAutoDiscoverComponents==true,试图自动发现components Map<String, String> componentNamesAndLocations = findComponents(componentsConfig, getServletContext()); // 取得特别指定的components Map<String, ComponentConfig> specifiedComponents = componentsConfig.getComponents(); // 实际要初始化的comonents,为上述两种来源的并集 Set<String> componentNames = createTreeSet(); componentNames.addAll(componentNamesAndLocations.keySet()); componentNames.addAll(specifiedComponents.keySet()); // 创建root controller WebxRootController rootController = componentsConfig.getRootController(); if (rootController == null) { rootController = (WebxRootController) BeanUtils.instantiateClass(componentsConfig.getRootControllerClass()); } // 创建并将components对象置入resolvable dependencies,以便注入到需要的bean中 WebxComponentsImpl components = new WebxComponentsImpl(componentsContext, componentsConfig.getDefaultComponent(), rootController, parentConfiguration); beanFactory.registerResolvableDependency(WebxComponents.class, components); // 初始化每个component for (String componentName : componentNames) { ComponentConfig componentConfig = specifiedComponents.get(componentName); String componentPath = null; WebxController controller = null; if (componentConfig != null) { componentPath = componentConfig.getPath(); controller = componentConfig.getController(); } if (controller == null) { controller = (WebxController) BeanUtils.instantiateClass(componentsConfig.getDefaultControllerClass()); } WebxComponentImpl component = new WebxComponentImpl(components, componentName, componentPath, componentName.equals(componentsConfig.getDefaultComponent()), controller, getWebxConfigurationName()); components.addComponent(component); prepareComponent(component, componentNamesAndLocations.get(componentName)); } return components; } private void prepareComponent(WebxComponentImpl component, String componentLocation) { String componentName = component.getName(); WebxComponentContext wcc = new WebxComponentContext(component); wcc.setServletContext(getServletContext()); wcc.setNamespace(componentName); wcc.addApplicationListener(new SourceFilteringListener(wcc, component)); if (componentLocation != null) { wcc.setConfigLocation(componentLocation); } component.setApplicationContext(wcc); // 将context保存在servletContext中 String attrName = getComponentContextAttributeName(componentName); getServletContext().setAttribute(attrName, wcc); log.debug("Published WebApplicationContext of component {} as ServletContext attribute with name [{}]", componentName, attrName); } /** 查找component名称。 */ private Map<String, String> findComponents(ComponentsConfig componentsConfig, ServletContext servletContext) { String locationPattern = componentsConfig.getComponentConfigurationLocationPattern(); String[] prefixAndPattern = checkComponentConfigurationLocationPattern(locationPattern); String prefix = prefixAndPattern[0]; String pathPattern = prefixAndPattern[1]; Map<String, String> componentNamesAndLocations = createTreeMap(); if (componentsConfig.isAutoDiscoverComponents()) { try { ResourcePatternResolver resolver = new ServletContextResourcePatternResolver(servletContext); Resource[] componentConfigurations = resolver.getResources(locationPattern); Pattern pattern = compilePathName(pathPattern); if (componentConfigurations != null) { for (Resource resource : componentConfigurations) { String path = resource.getURL().getPath(); Matcher matcher = pattern.matcher(path); assertTrue(matcher.find(), "unknown component configuration file: %s", path); String componentName = trimToNull(matcher.group(1)); if (componentName != null) { componentNamesAndLocations.put(componentName, prefix + pathPattern.replace("*", componentName)); } } } } catch (IOException e) { throw new RuntimeException(e); } } return componentNamesAndLocations; } /** * 检查componentConfigurationLocationPattern是否符合以下要求: * <ol> * <li>非空。</li> * <li>包含且只包含一个<code>*</code>。</li> * <li>支持<code>classpath*:</code>前缀。</li> * </ol> * <p> * 返回数组:[前缀, 不包含<code>classpath*:</code>的路径]。 * </p> */ private String[] checkComponentConfigurationLocationPattern(String componentConfigurationLocationPattern) { if (componentConfigurationLocationPattern != null) { // 允许并剔除classpath*:前缀。 boolean classpath = componentConfigurationLocationPattern.startsWith("classpath*:"); String pathPattern = componentConfigurationLocationPattern; if (classpath) { pathPattern = componentConfigurationLocationPattern.substring("classpath*:".length()).trim(); } // 检查路径。 int index = pathPattern.indexOf("*"); if (index >= 0) { index = pathPattern.indexOf("*", index + 1); if (index < 0) { if (pathPattern.startsWith("/")) { pathPattern = pathPattern.substring(1); } return new String[] { classpath ? "classpath:" : EMPTY_STRING, pathPattern }; } } } throw new IllegalArgumentException("Invalid componentConfigurationLocationPattern: " + componentConfigurationLocationPattern); } /** 从parent configuration中取得components配置。 */ private ComponentsConfig getComponentsConfig(WebxConfiguration parentConfiguration) { ComponentsConfig componentsConfig = assertNotNull(parentConfiguration, "parentConfiguration") .getComponentsConfig(); if (componentsConfig == null) { // create default components configuration componentsConfig = new ComponentsConfigImpl(); } return componentsConfig; } /** 从parent context中取得<code>WebxConfiguration</code>。 */ private WebxConfiguration getParentConfiguration() { try { return (WebxConfiguration) componentsContext.getBean(getWebxConfigurationName()); } catch (BeansException e) { // create default configuration WebxConfigurationImpl parentConfiguration = new WebxConfigurationImpl(); parentConfiguration.setApplicationContext(componentsContext); try { parentConfiguration.afterPropertiesSet(); } catch (RuntimeException ee) { throw ee; } catch (Exception ee) { throw new RuntimeException(ee); } return parentConfiguration; } } private static class WebxComponentsImpl implements WebxComponents, ApplicationListener { private final WebxConfiguration parentConfiguration; private final WebApplicationContext parentContext; private final Map<String, WebxComponent> components; private final RootComponent rootComponent; private final String defaultComponentName; private final WebxRootController rootController; public WebxComponentsImpl(WebApplicationContext parentContext, String defaultComponentName, WebxRootController rootController, WebxConfiguration parentConfiguration) { this.parentConfiguration = assertNotNull(parentConfiguration, "no parent webx-configuration"); this.parentContext = parentContext; this.components = createHashMap(); this.rootComponent = new RootComponent(); this.defaultComponentName = defaultComponentName; this.rootController = assertNotNull(rootController, "no rootController"); rootController.init(this); } public WebxConfiguration getParentWebxConfiguration() { return parentConfiguration; } private void addComponent(WebxComponent component) { components.put(component.getName(), component); } public WebxComponent getComponent(String componentName) { if (componentName == null) { return rootComponent; } else { return components.get(componentName); } } public String[] getComponentNames() { String[] names = components.keySet().toArray(new String[components.size()]); Arrays.sort(names); return names; } public WebxComponent getDefaultComponent() { return defaultComponentName == null ? null : components.get(defaultComponentName); } public Iterator<WebxComponent> iterator() { return components.values().iterator(); } public WebxComponent findMatchedComponent(String path) { if (!path.startsWith("/")) { path = "/" + path; } WebxComponent defaultComponent = getDefaultComponent(); WebxComponent matched = null; // 前缀匹配componentPath。 for (WebxComponent component : this) { if (component == defaultComponent) { continue; } String componentPath = component.getComponentPath(); if (!path.startsWith(componentPath)) { continue; } // path刚好等于componentPath,或者path以componentPath/为前缀 if (path.length() == componentPath.length() || path.charAt(componentPath.length()) == '/') { matched = component; break; } } // fallback to default component if (matched == null) { matched = defaultComponent; } return matched; } public WebxRootController getWebxRootController() { return rootController; } public WebApplicationContext getParentApplicationContext() { return parentContext; } public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { // autowire and init root controller autowireAndInitialize(rootController, getParentApplicationContext(), AbstractBeanDefinition.AUTOWIRE_AUTODETECT, "webxRootController"); rootController.onRefreshContext(); } } @Override public String toString() { MapBuilder mb = new MapBuilder(); mb.append("parentContext", parentContext); mb.append("defaultComponentName", defaultComponentName); mb.append("components", components); mb.append("rootController", rootController); return new ToStringBuilder().append("WebxComponents").append(mb).toString(); } /** 这是一个特殊的component实现,对应于root context。 */ private class RootComponent implements WebxComponent { public WebxComponents getWebxComponents() { return WebxComponentsImpl.this; } public String getName() { return null; } public String getComponentPath() { return EMPTY_STRING; } public WebxConfiguration getWebxConfiguration() { return getParentWebxConfiguration(); } public WebxController getWebxController() { unsupportedOperation("RootComponent.getWebxController()"); return null; } public WebApplicationContext getApplicationContext() { return getParentApplicationContext(); } @Override public String toString() { return WebxComponentsImpl.this.toString(); } } } private static class WebxComponentImpl implements WebxComponent, ApplicationListener { private final WebxComponents components; private final String name; private final String componentPath; private final WebxController controller; private final String webxConfigurationName; private WebApplicationContext context; public WebxComponentImpl(WebxComponents components, String name, String path, boolean defaultComponent, WebxController controller, String webxConfigurationName) { this.components = assertNotNull(components, "components"); this.name = assertNotNull(name, "componentName"); this.controller = assertNotNull(controller, "controller"); this.webxConfigurationName = assertNotNull(webxConfigurationName, "webxConfigurationName"); // 规格化path,去除尾部的/;空路径则设为null path = trimToNull(normalizeAbsolutePath(path, true)); if (defaultComponent) { assertTrue(path == null, "default component \"%s\" should not have component path \"%s\"", name, path); this.componentPath = EMPTY_STRING; } else if (path != null) { this.componentPath = path; } else { this.componentPath = "/" + name; } controller.init(this); } public WebxComponents getWebxComponents() { return components; } public String getName() { return name; } public String getComponentPath() { return componentPath; } public WebxController getWebxController() { return controller; } public WebxConfiguration getWebxConfiguration() { return (WebxConfiguration) context.getBean(webxConfigurationName); } public WebApplicationContext getApplicationContext() { return context; } private void setApplicationContext(WebApplicationContext context) { this.context = context; } public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) { // autowire and init controller autowireAndInitialize(controller, getApplicationContext(), AbstractBeanDefinition.AUTOWIRE_AUTODETECT, "webxController." + getName()); controller.onRefreshContext(); } } @Override public String toString() { MapBuilder mb = new MapBuilder(); mb.append("name", name); mb.append("path", componentPath); mb.append("controller", controller); mb.append("context", context); return new ToStringBuilder().append("WebxComponent").append(mb).toString(); } } }