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 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 net.paoding.rose.web.impl.module; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Resource; import javax.servlet.ServletContext; import net.paoding.rose.RoseConstants; import net.paoding.rose.scanner.ModuleResource; import net.paoding.rose.util.RoseStringUtil; import net.paoding.rose.util.SpringUtils; import net.paoding.rose.web.ControllerErrorHandler; import net.paoding.rose.web.ControllerInterceptor; import net.paoding.rose.web.InterceptorDelegate; import net.paoding.rose.web.OncePerRequestInterceptorDelegate; import net.paoding.rose.web.ParamValidator; import net.paoding.rose.web.advancedinterceptor.Ordered; import net.paoding.rose.web.annotation.Ignored; import net.paoding.rose.web.annotation.Interceptor; import net.paoding.rose.web.annotation.NotForSubModules; import net.paoding.rose.web.annotation.Path; import net.paoding.rose.web.paramresolver.ParamResolver; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.CannotLoadBeanClassException; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.util.ClassUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; /** * ?module?Module? * * @author [qieqie.wang@gmail.com] */ public class ModulesBuilderImpl implements ModulesBuilder { private Log logger = LogFactory.getLog(getClass()); public List<Module> build(List<ModuleResource> moduleResources, WebApplicationContext rootContext) throws Exception { // ???????? moduleResources = new ArrayList<ModuleResource>(moduleResources); Collections.sort(moduleResources); // ?? List<Module> modules = new ArrayList<Module>(moduleResources.size()); Map<ModuleResource, Module> modulesAsMap = new HashMap<ModuleResource, Module>(); // for (ModuleResource moduleResource : moduleResources) { final Module parentModule = (moduleResource.getParent() == null) ? null// : modulesAsMap.get(moduleResource.getParent()); final WebApplicationContext parentContext = (parentModule == null) ? rootContext// : parentModule.getApplicationContext(); final String namespace = "context@controllers" + moduleResource.getRelativePath().replace('/', '.'); // modulespring context final ServletContext servletContext = parentContext == null ? null // : parentContext.getServletContext(); final ModuleAppContext moduleContext = ModuleAppContext.createModuleContext(// parentContext, // moduleResource.getContextResources(), // moduleResource.getMessageBasenames(), // /*id*/moduleResource.getModuleUrl().toString(), // namespace// ); // ??...applicationContext registerBeanDefinitions(moduleContext, moduleResource.getModuleClasses()); // module final ModuleImpl module = new ModuleImpl(// parentModule, // moduleResource.getModuleUrl(), // moduleResource.getMappingPath(), // moduleResource.getRelativePath(), // moduleContext); // modulesAsMap.put(moduleResource, module); // servletContext if (servletContext != null) { String contextAttrKey = WebApplicationContext.class.getName() + "@" + moduleResource.getModuleUrl(); servletContext.setAttribute(contextAttrKey, moduleContext); } // Springweb??ParamValidatorParamResolver, ControllerInterceptor, ControllerErrorHandler List<ParamResolver> customerResolvers = findContextResolvers(moduleContext); // resolvers module.setCustomerResolvers(customerResolvers); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': apply resolvers " + customerResolvers); } // module List<InterceptorDelegate> interceptors = findInterceptors(moduleContext); for (Iterator<InterceptorDelegate> iter = interceptors.iterator(); iter.hasNext();) { InterceptorDelegate interceptor = iter.next(); ControllerInterceptor most = InterceptorDelegate.getMostInnerInterceptor(interceptor); if (!most.getClass().getName().startsWith("net.paoding.rose.web")) { // deny? if (moduleResource.getInterceptedDeny() != null) { if (RoseStringUtil.matches(moduleResource.getInterceptedDeny(), interceptor.getName())) { iter.remove(); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': remove interceptor by rose.properties: " + most.getClass().getName()); } continue; } } // allow? if (moduleResource.getInterceptedAllow() != null) { if (!RoseStringUtil.matches(moduleResource.getInterceptedAllow(), interceptor.getName())) { iter.remove(); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': remove interceptor by rose.properties: " + most.getClass().getName()); } continue; } } } } module.setControllerInterceptors(interceptors); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': apply intercetpors " + interceptors); } // validatormodule List<ParamValidator> validators = findContextValidators(moduleContext); module.setValidators(validators); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': apply global validators " + validators); } // errorhandler ControllerErrorHandler errorHandler = getContextErrorHandler(moduleContext); if (errorHandler != null) { if (Proxy.isProxyClass(errorHandler.getClass())) { module.setErrorHandler(errorHandler); } else { ErrorHandlerDispatcher dispatcher = new ErrorHandlerDispatcher(errorHandler); module.setErrorHandler(dispatcher); } if (logger.isInfoEnabled()) { logger.info("set errorHandler: " + module.getMappingPath() + " " + errorHandler); } } // controllers final ListableBeanFactory beanFactory = moduleContext.getBeanFactory(); for (String beanName : beanFactory.getBeanDefinitionNames()) { checkController(moduleContext, beanName, module); } // modules.add(module); } return modules; } private void throwExceptionIfDuplicatedNames(List<InterceptorDelegate> interceptors) { for (int i = 0; i < interceptors.size(); i++) { InterceptorDelegate interceptor = interceptors.get(i); for (int j = i + 1; j < interceptors.size(); j++) { // ?"??" InterceptorDelegate position = interceptors.get(j); if (position.getName().equals(interceptor.getName())) { // rose?interceptor? // ?????????? // @Component???? ControllerInterceptor duplicated1 = InterceptorDelegate.getMostInnerInterceptor(position); ControllerInterceptor duplicated2 = InterceptorDelegate.getMostInnerInterceptor(interceptor); throw new IllegalArgumentException("duplicated interceptor name for these two interceptors: '" + duplicated1.getClass() + "' and '" + duplicated2.getClass() + "'"); } } } } private boolean checkController(final XmlWebApplicationContext context, String beanName, ModuleImpl module) throws IllegalAccessException { AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) context.getBeanFactory() .getBeanDefinition(beanName); String beanClassName = beanDefinition.getBeanClassName(); String controllerSuffix = null; for (String suffix : RoseConstants.CONTROLLER_SUFFIXES) { if (beanClassName.length() > suffix.length() && beanClassName.endsWith(suffix)) { if (suffix.length() == 1 && Character .isUpperCase(beanClassName.charAt(beanClassName.length() - suffix.length() - 1))) { continue; } controllerSuffix = suffix; break; } } if (controllerSuffix == null) { if (beanDefinition.hasBeanClass()) { Class<?> beanClass = beanDefinition.getBeanClass(); if (beanClass.isAnnotationPresent(Path.class)) { throw new IllegalArgumentException( "@" + Path.class.getSimpleName() + " is only allowed in Resource/Controller, " + "is it a Resource/Controller? wrong spelling? : " + beanClassName); } } // ?l?r?uer?or??? if (beanClassName.endsWith("Controler") || beanClassName.endsWith("Controllor") || beanClassName.endsWith("Resouce") || beanClassName.endsWith("Resorce")) { // ?throw??? logger.error("", new IllegalArgumentException( "invalid class name end wrong spelling? : " + beanClassName)); } return false; } String[] controllerPaths = null; if (!beanDefinition.hasBeanClass()) { try { beanDefinition.resolveBeanClass(Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { throw new CannotLoadBeanClassException("", beanName, beanDefinition.getBeanClassName(), e); } } final Class<?> clazz = beanDefinition.getBeanClass(); final String controllerName = StringUtils.removeEnd(ClassUtils.getShortNameAsProperty(clazz), controllerSuffix); Path reqMappingAnnotation = clazz.getAnnotation(Path.class); if (reqMappingAnnotation != null) { controllerPaths = reqMappingAnnotation.value(); } if (controllerPaths != null) { // controllerPaths.length==0path?controller for (int i = 0; i < controllerPaths.length; i++) { if ("#".equals(controllerPaths[i])) { controllerPaths[i] = "/" + controllerName; } else if (controllerPaths[i].equals("/")) { controllerPaths[i] = ""; } else if (controllerPaths[i].length() > 0 && controllerPaths[i].charAt(0) != '/') { controllerPaths[i] = "/" + controllerPaths[i]; } if (controllerPaths[i].length() > 1 && controllerPaths[i].endsWith("/")) { if (controllerPaths[i].endsWith("//")) { throw new IllegalArgumentException("invalid path '" + controllerPaths[i] + "' for controller " + beanClassName + ": don't end with more than one '/'"); } controllerPaths[i] = controllerPaths[i].substring(0, controllerPaths[i].length() - 1); } } } else { // TODO: ?0.91.0?201007?? if (controllerName.equals("index") || controllerName.equals("home") || controllerName.equals("welcome")) { // ??IndexController/HomeController/WelcomeController@Path("") throw new IllegalArgumentException("please add @Path(\"\") to " + clazz.getName()); } else { controllerPaths = new String[] { "/" + controllerName }; } } // Controller??Context?? // Context??? Object controller = context.getBean(beanName); module.addController(// controllerPaths, clazz, controllerName, controller); if (Proxy.isProxyClass(controller.getClass())) { if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': add controller " + Arrays.toString(controllerPaths) + "= proxy of " + clazz.getName()); } } else { if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() // + "': add controller " + Arrays.toString(controllerPaths) + "= " + controller.getClass().getName()); } } return true; } private static final String AUTO_BEAN_NAME_PREFIX = "ModuleBuilder."; private void registerBeanDefinitions(XmlWebApplicationContext context, List<Class<?>> classes) { DefaultListableBeanFactory bf = (DefaultListableBeanFactory) context.getBeanFactory(); String[] definedClasses = new String[bf.getBeanDefinitionCount()]; String[] definitionNames = bf.getBeanDefinitionNames(); for (int i = 0; i < definedClasses.length; i++) { String name = definitionNames[i]; definedClasses[i] = bf.getBeanDefinition(name).getBeanClassName(); } for (Class<?> clazz : classes) { // ? if (!isCandidate(clazz)) { continue; } // bean String clazzName = clazz.getName(); if (ArrayUtils.contains(definedClasses, clazzName)) { if (logger.isDebugEnabled()) { logger.debug( "Ignores bean definition because it has been exist in context: " + clazz.getName()); } continue; } // String beanName = null; if (StringUtils.isEmpty(beanName) && clazz.isAnnotationPresent(Component.class)) { beanName = clazz.getAnnotation(Component.class).value(); } if (StringUtils.isEmpty(beanName) && clazz.isAnnotationPresent(Resource.class)) { beanName = clazz.getAnnotation(Resource.class).name(); } if (StringUtils.isEmpty(beanName) && clazz.isAnnotationPresent(Service.class)) { beanName = clazz.getAnnotation(Service.class).value(); } if (StringUtils.isEmpty(beanName)) { beanName = AUTO_BEAN_NAME_PREFIX + clazz.getName(); } bf.registerBeanDefinition(beanName, new AnnotatedGenericBeanDefinition(clazz)); } } private boolean isCandidate(Class<?> clazz) { if (clazz.isAnnotationPresent(Ignored.class)) { if (logger.isDebugEnabled()) { logger.debug("Ignores bean definition because it's present by @Ignored : " + clazz.getName()); } return false; } if (!Modifier.isPublic(clazz.getModifiers())) { if (logger.isDebugEnabled()) { logger.debug("Ignores bean definition because it's not a public class: " + clazz.getName()); } return false; } if (Modifier.isAbstract(clazz.getModifiers())) { if (logger.isDebugEnabled()) { logger.debug("Ignores bean definition because it's a abstract class: " + clazz.getName()); } return false; } if (clazz.getDeclaringClass() != null) { if (logger.isDebugEnabled()) { logger.debug("Ignores bean definition because it's a inner class: " + clazz.getName()); } return false; } return true; } /** ??? */ private ControllerErrorHandler getContextErrorHandler(XmlWebApplicationContext context) { ControllerErrorHandler errorHandler = null; String[] names = context.getBeanNamesForType(ControllerErrorHandler.class); for (int i = 0; errorHandler == null && i < names.length; i++) { errorHandler = (ControllerErrorHandler) context.getBean(names[i]); Class<?> userClass = ClassUtils.getUserClass(errorHandler); if (userClass.isAnnotationPresent(Ignored.class)) { logger.debug("Ignored controllerErrorHandler: " + errorHandler); errorHandler = null; continue; } } return errorHandler; } private List<ParamResolver> findContextResolvers(XmlWebApplicationContext context) { String[] resolverNames = SpringUtils.getBeanNames(context.getBeanFactory(), ParamResolver.class); ArrayList<ParamResolver> resolvers = new ArrayList<ParamResolver>(resolverNames.length); for (String beanName : resolverNames) { ParamResolver resolver = (ParamResolver) context.getBean(beanName); Class<?> userClass = ClassUtils.getUserClass(resolver); if (userClass.isAnnotationPresent(Ignored.class)) { if (logger.isDebugEnabled()) { logger.debug("Ignored context resolver:" + resolver); } continue; } if (userClass.isAnnotationPresent(NotForSubModules.class) && context.getBeanFactory().getBeanDefinition(beanName) == null) { if (logger.isDebugEnabled()) { logger.debug("Ignored context resolver (NotForSubModules):" + resolver); } continue; } resolvers.add(resolver); if (logger.isDebugEnabled()) { logger.debug("context resolver[" + resolver.getClass().getName()); } } return resolvers; } private List<InterceptorDelegate> findInterceptors(XmlWebApplicationContext context) { String[] interceptorNames = SpringUtils.getBeanNames(context.getBeanFactory(), ControllerInterceptor.class); ArrayList<InterceptorDelegate> interceptors = new ArrayList<InterceptorDelegate>(interceptorNames.length); for (String beanName : interceptorNames) { ControllerInterceptor interceptor = (ControllerInterceptor) context.getBean(beanName); Class<?> userClass = ClassUtils.getUserClass(interceptor); if (userClass.isAnnotationPresent(Ignored.class)) { if (logger.isDebugEnabled()) { logger.debug("Ignored interceptor (Ignored):" + interceptor); } continue; } if (userClass.isAnnotationPresent(NotForSubModules.class) && !context.getBeanFactory().containsBeanDefinition(beanName)) { if (logger.isDebugEnabled()) { logger.debug("Ignored interceptor (NotForSubModules):" + interceptor); } continue; } if (!userClass.getSimpleName().endsWith(RoseConstants.INTERCEPTOR_SUFFIX)) { logger.error("", new IllegalArgumentException("Interceptor must be end with '" + RoseConstants.INTERCEPTOR_SUFFIX + "': " + userClass.getName())); continue; } InterceptorBuilder builder = new InterceptorBuilder(interceptor); Interceptor annotation = userClass.getAnnotation(Interceptor.class); if (annotation != null) { builder.oncePerRequest(annotation.oncePerRequest()); } String interceporName; if (beanName.startsWith(AUTO_BEAN_NAME_PREFIX)) { interceporName = StringUtils.removeEnd(StringUtils.uncapitalize(userClass.getSimpleName()), RoseConstants.INTERCEPTOR_SUFFIX); } else { interceporName = StringUtils.removeEnd(beanName, RoseConstants.INTERCEPTOR_SUFFIX); } final String rose = "rose"; if (interceporName.startsWith(rose) && (interceporName.length() == rose.length() || Character.isUpperCase(interceporName.charAt(rose.length()))) && !userClass.getName().startsWith("net.paoding.rose.")) { throw new IllegalArgumentException("illegal interceptor name '" + interceporName + "' for " + userClass.getName() + ": don't starts with 'rose', it's reserved"); } builder.name(interceporName); InterceptorDelegate wrapper = builder.build(); interceptors.add(wrapper); if (logger.isDebugEnabled()) { int priority = 0; if (interceptor instanceof Ordered) { priority = ((Ordered) interceptor).getPriority(); } logger.debug("recognized interceptor[priority=" + priority + "]: " // \r\n + wrapper.getName() + "=" + userClass.getName()); } } Collections.sort(interceptors); throwExceptionIfDuplicatedNames(interceptors); return interceptors; } private List<ParamValidator> findContextValidators(XmlWebApplicationContext context) { String[] validatorNames = SpringUtils.getBeanNames(context.getBeanFactory(), ParamValidator.class); ArrayList<ParamValidator> globalValidators = new ArrayList<ParamValidator>(validatorNames.length); for (String beanName : validatorNames) { ParamValidator validator = (ParamValidator) context.getBean(beanName); Class<?> userClass = ClassUtils.getUserClass(validator); if (userClass.isAnnotationPresent(Ignored.class)) { if (logger.isDebugEnabled()) { logger.debug("Ignored context validator:" + validator); } continue; } if (userClass.isAnnotationPresent(NotForSubModules.class) && context.getBeanFactory().getBeanDefinition(beanName) == null) { if (logger.isDebugEnabled()) { logger.debug("Ignored context validator (NotForSubModules):" + validator); } continue; } globalValidators.add(validator); if (logger.isDebugEnabled()) { logger.debug("add context validator: " + userClass.getName()); } } return globalValidators; } public static class InterceptorBuilder { private boolean oncePerRequest; private String name; private ControllerInterceptor interceptor; public InterceptorBuilder(ControllerInterceptor interceptor) { this.interceptor = interceptor; } public InterceptorBuilder name(String name) { this.name = name; return this; } public InterceptorBuilder oncePerRequest(boolean oncePerRequest) { this.oncePerRequest = oncePerRequest; return this; } public InterceptorDelegate build() { ControllerInterceptor interceptor = this.interceptor; if (oncePerRequest) { interceptor = new OncePerRequestInterceptorDelegate(interceptor); } InterceptorDelegate wrapper = new InterceptorDelegate(interceptor); if (StringUtils.isBlank(wrapper.getName())) { wrapper.setName(name); } return wrapper; } } }