com.laxser.blitz.web.impl.module.ModulesBuilderImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.laxser.blitz.web.impl.module.ModulesBuilderImpl.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 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.laxser.blitz.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 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;

import com.laxser.blitz.BlitzConstants;
import com.laxser.blitz.advancedinterceptor.Ordered;
import com.laxser.blitz.scanner.ModuleResource;
import com.laxser.blitz.util.BlitzStringUtil;
import com.laxser.blitz.util.SpringUtils;
import com.laxser.blitz.web.ControllerErrorHandler;
import com.laxser.blitz.web.ControllerInterceptor;
import com.laxser.blitz.web.InterceptorDelegate;
import com.laxser.blitz.web.OncePerRequestInterceptorDelegate;
import com.laxser.blitz.web.ParamValidator;
import com.laxser.blitz.web.annotation.Ignored;
import com.laxser.blitz.web.annotation.Interceptor;
import com.laxser.blitz.web.annotation.NotForSubModules;
import com.laxser.blitz.web.annotation.Path;
import com.laxser.blitz.web.paramresolver.ParamResolver;

/**
 * ?module?Module?
 * 
 *@author laxser  Date 2012-3-22 ?4:39:39
@contact [duqifan@gmail.com]
@ModulesBuilderImpl.java
    
 */
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 (BlitzStringUtil.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 (!BlitzStringUtil.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 : BlitzConstants.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(BlitzConstants.INTERCEPTOR_SUFFIX)) {
                logger.error("", new IllegalArgumentException("Interceptor must be end with '"
                        + BlitzConstants.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()),
                        BlitzConstants.INTERCEPTOR_SUFFIX);
            } else {
                interceporName = StringUtils.removeEnd(beanName, BlitzConstants.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;
        }
    }
}