Package org.mojavemvc.core

Source Code of org.mojavemvc.core.MappedControllerDatabase

/*
* Copyright (C) 2011-2013 Mojavemvc.org
*
* 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 org.mojavemvc.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.cglib.reflect.FastClass;

import org.bigtesting.routd.Router;
import org.mojavemvc.annotations.Action;
import org.mojavemvc.annotations.AfterAction;
import org.mojavemvc.annotations.AfterConstruct;
import org.mojavemvc.annotations.BeforeAction;
import org.mojavemvc.annotations.DELETEAction;
import org.mojavemvc.annotations.DefaultAction;
import org.mojavemvc.annotations.DefaultController;
import org.mojavemvc.annotations.Entity;
import org.mojavemvc.annotations.Expects;
import org.mojavemvc.annotations.GETAction;
import org.mojavemvc.annotations.HEADAction;
import org.mojavemvc.annotations.Init;
import org.mojavemvc.annotations.InterceptedBy;
import org.mojavemvc.annotations.OPTIONSAction;
import org.mojavemvc.annotations.POSTAction;
import org.mojavemvc.annotations.PUTAction;
import org.mojavemvc.annotations.ParamPath;
import org.mojavemvc.annotations.Returns;
import org.mojavemvc.annotations.SingletonController;
import org.mojavemvc.annotations.StatefulController;
import org.mojavemvc.annotations.StatelessController;
import org.mojavemvc.annotations.TRACEAction;
import org.mojavemvc.aop.RequestContext;
import org.mojavemvc.exception.ConfigurationException;
import org.mojavemvc.marshalling.DefaultEntityMarshaller;
import org.mojavemvc.marshalling.EntityMarshaller;
import org.mojavemvc.util.ParamPathHelper;
import org.mojavemvc.views.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A single instance of this class is meant to exist in the front controller
* context, to be shared by multiple threads. The only methods that can be
* called during requests are read methods. This class is immutable once
* created, and thus is thread-safe.
*
* @author Luis Antunes
*/
public class MappedControllerDatabase implements ControllerDatabase {

    private static final Logger logger = LoggerFactory.getLogger("org.mojavemvc");

    /*
     * a map of the controller variable names to their classes (eg. "index" ->
     * org.mojavemvc.tests.IndexController)
     */
    private final Map<String, Class<?>> controllerClassesMap = new HashMap<String, Class<?>>();

    /*
     * a map of the controller classes to a map of their action names to method
     * signatures eg. org.mojavemvc.tests.IndexController -> ["some-action" ->
     * ActionSignature[ "someAction", [] ]]
     */
    private final Map<Class<?>, Map<String, ActionSignature>> controllerClassToActionMap = new HashMap<Class<?>, Map<String, ActionSignature>>();

    /*
     * a map of the controller classes to their @BeforeAction methods eg.
     * org.mojavemvc.tests.IndexController ->
     * ActionSignature["doSomethingBefore"]
     */
    private final Map<Class<?>, ActionSignature> controllerClassToBeforeActionMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a map of the controller classes to their @AfterAction methods eg.
     * org.mojavemvc.tests.IndexController ->
     * ActionSignature["doSomethingAfter"]
     */
    private final Map<Class<?>, ActionSignature> controllerClassToAfterActionMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a map of the controller classes to their @DefaultAction methods eg.
     * org.mojavemvc.tests.IndexController -> ActionSignature[ "someAction", []
     * ]]
     */
    private final Map<Class<?>, ActionSignature> controllerClassToDefaultActionMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a map of the controller classes to their @AfterConstruct methods eg.
     * org.mojavemvc.tests.IndexController -> ActionSignature["init"]
     */
    private final Map<Class<?>, ActionSignature> controllerClassToAfterConstructMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a set of singleton controllers that are marked for creation during init
     */
    private final Set<Class<?>> initControllers = new HashSet<Class<?>>();

    /*
     * the application-wide default controller class, if any, as determined by
     * the
     *
     * @DefaultController annotation
     */
    private Class<?> defaultControllerClass = null;

    /*
     * a map of the controller classes to a collection of their interceptor
     * classes eg. org.mojavemvc.tests.IndexController -> Class[]
     */
    private final Map<Class<?>, List<Class<?>>> controllerClassToInterceptorsMap = new HashMap<Class<?>, List<Class<?>>>();

    /*
     * a map of the controller classes to a collection of its default action
     * interceptor classes eg. org.mojavemvc.tests.IndexController -> Class[]
     */
    private final Map<Class<?>, List<Class<?>>> controllerClassToDefaultActionInterceptorsMap = new HashMap<Class<?>, List<Class<?>>>();

    /*
     * a map of the interceptor classes to their @BeforeAction methods eg.
     * org.mojavemvc.tests.Interceptor -> ActionSignature["doSomethingBefore"]
     */
    private final Map<Class<?>, ActionSignature> interceptorClassToBeforeActionMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a map of the interceptor classes to their @AfterAction methods eg.
     * org.mojavemvc.tests.Interceptor -> Method["doSomethingAfter"]
     */
    private final Map<Class<?>, ActionSignature> interceptorClassToAfterActionMap = new HashMap<Class<?>, ActionSignature>();

    /*
     * a map of the controller classes to a map of their action names to
     * interceptor classes eg. org.mojavemvc.tests.IndexController ->
     * ["some-action" -> Interceptor[]]
     */
    private final Map<Class<?>, Map<String, List<Class<?>>>> controllerClassToActionInterceptorsMap = new HashMap<Class<?>, Map<String, List<Class<?>>>>();

    /*
     * a map of the controller classes to a map of their HttpMethods to
     * interceptor classes eg. org.mojavemvc.tests.IndexController ->
     * [HttpMethod.GET -> Interceptor[]]
     */
    private final Map<Class<?>, Map<HttpMethod, List<Class<?>>>> controllerClassToHttpMethodActionInterceptorsMap = new HashMap<Class<?>, Map<HttpMethod, List<Class<?>>>>();

    /*
     * a map of controller and interceptor classes to their FastClass
     * counter-part
     */
    private final Map<Class<?>, FastClass> classToFastClassMap = new HashMap<Class<?>, FastClass>();

    /*
     * a map of controller classes to a map of their HTTP methods to
     * corresponding HttpMethod Action eg. org.mojavemvc.tests.IndexController
     * -> [HttpMethod.GET - > ActionSignature]
     */
    private final Map<Class<?>, Map<HttpMethod, ActionSignature>> controllerClassToHttpMethodMap = new HashMap<Class<?>, Map<HttpMethod, ActionSignature>>();
   
    /*
     * the router to configure during initialization
     */
    private final Router router;

    /*
     * the map of global entity marshallers
     * key: content type
     * val: marhsaller instance
     */
    private final Map<String, EntityMarshaller> entityMarshallerMap;
   
    /**
     * Construct a controller database based on the given Set of controller
     * Classes.
     *
     * @param controllerClasses
     */
    public MappedControllerDatabase(Set<Class<?>> controllerClasses, Router router,
            Map<String, EntityMarshaller> entityMarshallerMap) {

        this.router = router;
        this.entityMarshallerMap = entityMarshallerMap;
        init(controllerClasses);
    }

    /**
     * Get the Router for the application.
     *
     * @return the router for the application
     */
    public Router getRouter() {
        return router;
    }
   
    /**
     * Get the controller Class associated with the given controller variable
     * name.
     *
     * @param controllerVariable
     * @return the controller class associated with the variable name
     */
    public Class<?> getControllerClass(String controllerVariable) {

        return controllerClassesMap.get(controllerVariable);
    }

    /**
     * Get the ActionSignature associated with the given controller class.
     * ActionSignature is a thread-safe class.
     *
     * @param controllerClass
     *            the controller class
     * @param action
     *            the request action
     * @return the ActionSignature associated with the controller class
     */
    public ActionSignature getActionMethodSignature(Class<?> controllerClass, String action) {

        Map<String, ActionSignature> actionMap = controllerClassToActionMap.get(controllerClass);
        if (actionMap == null) {
            throw new ConfigurationException("no actions defined for " + controllerClass.getName());
        }
        return actionMap.get(action);
    }

    /**
     * Get the ActionSignature annotated with @BeforeAction for the given
     * controller class. ActionSignature is thread-safe.
     *
     * @param controllerClass
     *            the controller class
     * @return the ActionSignature, or null if there is no ActionSignature
     *         annotated with @BeforeAction
     */
    public ActionSignature getBeforeActionMethodFor(Class<?> controllerClass) {

        return controllerClassToBeforeActionMap.get(controllerClass);
    }

    /**
     * Get the ActionSignature annotated with @AfterAction for the given
     * controller class. ActionSignature is thread-safe.
     *
     * @param controllerClass
     *            the controller class
     * @return the ActionSignature, or null if there is no ActionSignature
     *         annotated with @AfterAction
     */
    public ActionSignature getAfterActionMethodFor(Class<?> controllerClass) {

        return controllerClassToAfterActionMap.get(controllerClass);
    }

    /**
     * Get the ActionSignature annotated with @DefaultAction for the given
     * controller class. ActionSignature is thread-safe.
     *
     * @param controllerClass
     *            the controller class
     * @return the ActionSignature, or null if there is no method annotated with @DefaultAction
     */
    public ActionSignature getDefaultActionMethodFor(Class<?> controllerClass) {

        return controllerClassToDefaultActionMap.get(controllerClass);
    }

    /**
     * Get the ActionSignature annotated with @AfterConstruct for the given
     * controller class. ActionSignature is thread-safe.
     *
     * @param controllerClass
     *            the controller class
     * @return the ActionSignature, or null if there is no ActionSignature
     *         annotated with @AfterConstruct
     */
    public ActionSignature getAfterConstructMethodFor(Class<?> controllerClass) {

        return controllerClassToAfterConstructMap.get(controllerClass);
    }

    /**
     * Get an unmodifiable set of singleton controller classes that are marked
     * for creation during the FrontController init().
     *
     * @return the singleton controller class
     */
    public Set<Class<?>> getInitControllers() {

        return Collections.unmodifiableSet(initControllers);
    }

    /**
     * Get an unmodifiable list of Classes that represent interceptors for the
     * given class, in the order in which they are declared in the @InterceptedBy
     * annotation.
     *
     * @param controllerClass
     *            the controller class
     * @return the classes of interceptors associated with the given class
     */
    public List<Class<?>> getInterceptorsFor(Class<?> controllerClass) {

        return controllerClassToInterceptorsMap.get(controllerClass);
    }

    /**
     * Get the ActionSignature annotated with @AfterAction for the given
     * interceptor class. ActionSignature is thread-safe.
     *
     * @param interceptorClass
     *            the interceptor class
     * @return the ActionSignature, or null if there is no ActionSignature
     *         annotated with @AfterAction
     */
    public ActionSignature getAfterActionMethodForInterceptor(Class<?> interceptorClass) {

        return interceptorClassToAfterActionMap.get(interceptorClass);
    }

    /**
     * Get the ActionSignature annotated with @BeforeAction for the given
     * interceptor class. Method is thread-safe.
     *
     * @param interceptorClass
     *            the interceptor class
     * @return the ActionSignature, or null if there is no ActionSignature
     *         annotated with @BeforeAction
     */
    public ActionSignature getBeforeActionMethodForInterceptor(Class<?> interceptorClass) {

        return interceptorClassToBeforeActionMap.get(interceptorClass);
    }

    /**
     * Get the default controller for the application, if specified.
     *
     * @return the default controller class, or null if no @DefaultController is
     *         specified
     */
    public Class<?> getDefaultControllerClass() {

        return defaultControllerClass;
    }

    /**
     * Get an unmodifiable list of Classes that represent interceptors for the
     * given action method in the given class, in the order in which they are
     * declared in the @InterceptedBy annotation.
     *
     * @param controllerClass
     *            the controller class
     * @param action
     *            the action being invoked in the request
     * @return an unmodifiable List of interceptor classes in the order in which
     *         they are declared, or null if there are no interceptors
     */
    public List<Class<?>> getInterceptorsForAction(Class<?> controllerClass, String action) {

        Map<String, List<Class<?>>> actionInterceptorsMap = controllerClassToActionInterceptorsMap.get(controllerClass);
        if (actionInterceptorsMap == null) {
            return null;
        }
        return actionInterceptorsMap.get(action);
    }

    /**
     * Get an unmodifiable list of Classes that represent interceptors for the
     * default action method in the given class, in the order in which they are
     * declared in the @InterceptedBy annotation.
     *
     * @param controllerClass
     *            the controller class
     * @return an unmodifiable List of interceptor classes in the order in which
     *         they are declared
     */
    public List<Class<?>> getInterceptorsForDefaultAction(Class<?> controllerClass) {

        return controllerClassToDefaultActionInterceptorsMap.get(controllerClass);
    }

    /**
     * Get an unmodifiable list of Classes that represent interceptors for the
     * given HTTP method action in the given class, in the order in which they
     * are declared in the @InterceptedBy annotation.
     *
     * @param controllerClass
     *            the controller class
     * @param httpMethod
     *            the HTTP method being invoked in the request
     * @return an unmodifiable List of interceptor classes in the order in which
     *         they are declared, or null if there are no interceptors
     */
    public List<Class<?>> getInterceptorsForHttpMethodAction(Class<?> controllerClass, HttpMethod httpMethod) {

        Map<HttpMethod, List<Class<?>>> httpMethodActionInterceptorsMap = controllerClassToHttpMethodActionInterceptorsMap
                .get(controllerClass);
        if (httpMethodActionInterceptorsMap == null) {
            return null;
        }
        return httpMethodActionInterceptorsMap.get(httpMethod);
    }

    /**
     * Get the cached FastClass version of the given controller or interceptor
     * class. FastClass is thread-safe.
     *
     * @param clazz
     *            the controller or interceptor class
     * @return the FastClass version of the class
     */
    public FastClass getFastClass(Class<?> clazz) {

        return classToFastClassMap.get(clazz);
    }

    /**
     * Get the HTTP method ActionSignature associated with the given controller
     * class. ActionSignature is a thread-safe class.
     *
     * @param controllerClass
     *            the controller class
     * @param httpMethod
     *            the HTTP method
     * @return the ActionSignature associated with the controller class, or null
     *         if there is no Action for the given HttpMethod
     */
    public ActionSignature getHttpMethodActionSignature(Class<?> controllerClass, HttpMethod httpMethod) {

        Map<HttpMethod, ActionSignature> actionMap = controllerClassToHttpMethodMap.get(controllerClass);
        if (actionMap != null) {

            return actionMap.get(httpMethod);
        }
        return null;
    }

    /*--------------------------private methods----------------------------------------*/

    private void init(Set<Class<?>> controllerClasses) {

        for (Class<?> controllerClass : controllerClasses) {

            logger.debug("found controller class: " + controllerClass.getName());

            Annotation controllerAnnotation = getControllerAnnotation(controllerClass);

            String controllerVariable = getControllerVariable(controllerClass, controllerAnnotation);

            addControllerClass(controllerVariable, controllerClass);
        }
    }

    private Annotation getControllerAnnotation(Class<?> controllerClass) {

        Annotation[] controllerAnnotations = controllerClass.getAnnotations();
        Annotation controllerAnnotation = null;
        for (Annotation annot : controllerAnnotations) {

            if (annot instanceof StatelessController || annot instanceof StatefulController
                    || annot instanceof SingletonController) {

                if (controllerAnnotation != null) {
                    throw new ConfigurationException("controller " + controllerClass.getName()
                            + " is annotated with multiple controller annotations");
                }

                controllerAnnotation = annot;
            }
        }

        if (controllerAnnotation == null) {
            throw new ConfigurationException("controller " + controllerClass.getName()
                    + " not annotated with a controller annotation");
        }

        return controllerAnnotation;
    }

    private String getControllerVariable(Class<?> controllerClass, Annotation controllerAnnotation) {

        String controllerVariable = null;

        if (controllerAnnotation instanceof StatelessController) {
            controllerVariable = ((StatelessController) controllerAnnotation).value();
        } else if (controllerAnnotation instanceof StatefulController) {
            controllerVariable = ((StatefulController) controllerAnnotation).value();
        } else if (controllerAnnotation instanceof SingletonController) {
            controllerVariable = ((SingletonController) controllerAnnotation).value();
        }

        if (controllerVariable == null || controllerVariable.trim().length() == 0) {
            /*
             * if no value is provided in the annotation, map the controller to
             * its simple class name
             */
            controllerVariable = controllerClass.getSimpleName();
        }

        return controllerVariable;
    }

    private void addControllerClass(String controllerVariable, Class<?> controllerClass) {

        /*
         * check if this controller variable already exists; if so raise an
         * exception
         */
        Class<?> existing = controllerClassesMap.get(controllerClass);
        if (existing != null) {
            throw new ConfigurationException("a controller variable with the value " + controllerVariable
                    + " already exists");
        }
        controllerClassesMap.put(controllerVariable, controllerClass);
        checkForInitController(controllerClass);
        boolean isDefaultController = checkForDefaultController(controllerClass);
        setActionMethodIndicesFor(controllerClass, controllerVariable, isDefaultController);
        setInterceptorsFor(controllerClass);
    }

    private void checkForInitController(Class<?> controllerClass) {

        Annotation annot = controllerClass.getAnnotation(Init.class);
        if (annot != null) {
            /* do some validation */
            annot = controllerClass.getAnnotation(SingletonController.class);
            if (annot == null) {
                throw new ConfigurationException("only a @" + SingletonController.class.getSimpleName()
                        + " can be annotated with @" + Init.class.getSimpleName());
            }
            initControllers.add(controllerClass);
        }
    }

    /*
     * return true if controller class is a default controller, false otherwise
     */
    private boolean checkForDefaultController(Class<?> controllerClass) {

        Annotation annot = controllerClass.getAnnotation(DefaultController.class);
        if (annot != null) {
            if (defaultControllerClass != null) {
                throw new ConfigurationException("only one controller class can be annotated with @"
                        + DefaultController.class.getSimpleName());
            }
            defaultControllerClass = controllerClass;
            return true;
        }
        return false;
    }

    private void setActionMethodIndicesFor(Class<?> controllerClass, String controllerVariable,
            boolean isDefaultController) {

        Map<String, ActionSignature> actionMap = new HashMap<String, ActionSignature>();

        Map<String, List<Class<?>>> actionInterceptorsMap = new HashMap<String, List<Class<?>>>();

        Map<HttpMethod, ActionSignature> httpMethodActionMap = new EnumMap<HttpMethod, ActionSignature>(
                HttpMethod.class);

        Map<HttpMethod, List<Class<?>>> httpMethodActionInterceptorsMap = new EnumMap<HttpMethod, List<Class<?>>>(
                HttpMethod.class);

        Method[] methods = getAllMethodsIn(controllerClass);
       
        FastClass fastClass = FastClass.create(controllerClass);
        classToFastClassMap.put(controllerClass, fastClass);

        for (int i = 0; i < methods.length; i++) {

            Annotation ann = methods[i].getAnnotation(Action.class);
            if (ann != null) {
                Action actionAnn = (Action) ann;
                String action = actionAnn.value();
                if (action == null || action.trim().length() == 0) {
                    /*
                     * if no value is provided in the annotation, map the action
                     * to its method name
                     */
                    action = methods[i].getName();
                }
                addActionSignature(action, methods[i], actionMap, fastClass, controllerVariable, isDefaultController);
                setInterceptorsForAction(controllerClass, action, methods[i], actionInterceptorsMap);
                continue;
            }

            if (addHttpMethodActionSignature(controllerClass, methods[i], fastClass, httpMethodActionMap,
                    httpMethodActionInterceptorsMap, controllerVariable, isDefaultController)) {
                continue;
            }

            ann = methods[i].getAnnotation(BeforeAction.class);
            if (ann != null) {
                addBeforeOrAfterActionSignature(controllerClassToBeforeActionMap, BeforeAction.class, controllerClass,
                        methods[i], fastClass);
                continue;
            }

            ann = methods[i].getAnnotation(AfterAction.class);
            if (ann != null) {
                addBeforeOrAfterActionSignature(controllerClassToAfterActionMap, AfterAction.class, controllerClass,
                        methods[i], fastClass);
                continue;
            }

            ann = methods[i].getAnnotation(DefaultAction.class);
            if (ann != null) {
                addDefaultActionSignature(controllerClassToDefaultActionMap, DefaultAction.class, controllerClass,
                        methods[i], fastClass, controllerVariable, isDefaultController);
                setInterceptorsForDefaultAction(controllerClass, methods[i]);
                continue;
            }

            ann = methods[i].getAnnotation(AfterConstruct.class);
            if (ann != null) {
                addAfterContructSignature(controllerClassToAfterConstructMap, AfterConstruct.class, controllerClass,
                        methods[i], fastClass);
            }
        }

        /*
         * validate that there is at least one action or after-construct method
         * in this controller
         */
        if (actionMap.isEmpty() && httpMethodActionMap.isEmpty()
                && controllerClassToDefaultActionMap.get(controllerClass) == null
                && controllerClassToAfterConstructMap.get(controllerClass) == null) {

            throw new ConfigurationException("controller " + controllerClass.getName()
                    + " does not contain any actions or after-construct methods");
        }

        controllerClassToActionMap.put(controllerClass, actionMap);
        controllerClassToActionInterceptorsMap.put(controllerClass, actionInterceptorsMap);
        controllerClassToHttpMethodMap.put(controllerClass, httpMethodActionMap);
        controllerClassToHttpMethodActionInterceptorsMap.put(controllerClass, httpMethodActionInterceptorsMap);
    }
   
    private Method[] getAllMethodsIn(Class<?> clazz) {
       
        List<Method> methodsList = new ArrayList<Method>();
        methodsList.addAll(Arrays.asList(clazz.getDeclaredMethods()));       
        Class<?> superClass = clazz.getSuperclass();       
        while (superClass != Object.class) {
            methodsList.addAll(Arrays.asList(superClass.getDeclaredMethods()));
            superClass = superClass.getSuperclass();
        }
        return methodsList.toArray(new Method[methodsList.size()]);
    }

    private boolean addHttpMethodActionSignature(Class<?> controllerClass, Method actionMethod, FastClass fastClass,
            Map<HttpMethod, ActionSignature> httpMethodActionMap,
            Map<HttpMethod, List<Class<?>>> httpMethodActionInterceptorsMap,
            String controllerVariable, boolean isDefaultController) {

        boolean annotationFound = false;

        if (actionMethod.isAnnotationPresent(GETAction.class)) {

            addHttpMethodActionSignature(HttpMethod.GET, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.GET, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(POSTAction.class)) {

            addHttpMethodActionSignature(HttpMethod.POST, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.POST, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(PUTAction.class)) {

            addHttpMethodActionSignature(HttpMethod.PUT, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.PUT, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(OPTIONSAction.class)) {

            addHttpMethodActionSignature(HttpMethod.OPTIONS, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.OPTIONS, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(HEADAction.class)) {

            addHttpMethodActionSignature(HttpMethod.HEAD, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.HEAD, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(TRACEAction.class)) {

            addHttpMethodActionSignature(HttpMethod.TRACE, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.TRACE, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        if (actionMethod.isAnnotationPresent(DELETEAction.class)) {

            addHttpMethodActionSignature(HttpMethod.DELETE, actionMethod, fastClass,
                    httpMethodActionMap, controllerVariable, isDefaultController);
            setInterceptorsForHttpMethodAction(controllerClass, HttpMethod.DELETE, actionMethod,
                    httpMethodActionInterceptorsMap);
            annotationFound = true;
        }

        return annotationFound;
    }

    private void addHttpMethodActionSignature(HttpMethod httpMethod, Method actionMethod,
            FastClass fastClass, Map<HttpMethod, ActionSignature> httpMethodActionMap,
            String controllerVariable, boolean isDefaultController) {

        validateActionReturnType(actionMethod, fastClass.getJavaClass().getName());

        ActionSignature existingActionSignature = httpMethodActionMap.get(httpMethod);
        if (existingActionSignature != null) {

            throw new ConfigurationException("mulitple action methods for " + httpMethod + " found; "
                    + "only one action method per HTTP method type is allowed in " +
                    fastClass.getJavaClass().getName());
        }
       
        EntityMarshaller paramMarshaller = getParamEntityMarshaller(actionMethod,
                fastClass.getJavaClass().getName());
        EntityMarshaller viewMarshaller = getViewEntityMarshaller(actionMethod,
                fastClass.getJavaClass().getName());

        int fastIndex = fastClass.getIndex(actionMethod.getName(), actionMethod.getParameterTypes());

        ActionSignature sig = new HttpMethodActionSignature(httpMethod, fastIndex, actionMethod.getName(),
                actionMethod.getParameterTypes(), actionMethod.getParameterAnnotations(),
                actionMethod.getDeclaredAnnotations(), paramMarshaller, viewMarshaller);
        httpMethodActionMap.put(httpMethod, sig);
       
        addRoute(actionMethod, fastClass.getJavaClass().getName(), controllerVariable,
                null, isDefaultController);
    }

    private void addActionSignature(String action, Method method,
            Map<String, ActionSignature> actionMap, FastClass fastClass,
            String controllerVariable, boolean isDefaultController) {

        validateActionReturnType(method, fastClass.getJavaClass().getName());
       
        EntityMarshaller paramMarshaller = getParamEntityMarshaller(method, fastClass.getJavaClass().getName());
        EntityMarshaller viewMarshaller = getViewEntityMarshaller(method, fastClass.getJavaClass().getName());

        int fastIndex = fastClass.getIndex(method.getName(), method.getParameterTypes());

        ActionSignature sig = new BaseActionSignature(fastIndex, method.getName(), method.getParameterTypes(),
                method.getParameterAnnotations(), method.getDeclaredAnnotations(),
                paramMarshaller, viewMarshaller);
        actionMap.put(action, sig);
       
        addRoute(method, fastClass.getJavaClass().getName(), controllerVariable, action, isDefaultController);
    }

    private EntityMarshaller getParamEntityMarshaller(Method method, String className) {
       
        EntityMarshaller paramMarshaller = new DefaultEntityMarshaller();
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        List<Entity> entityAnnotations = new ArrayList<Entity>();
        for (Annotation[] annotationsForParam : paramAnnotations) {
            for (Annotation annotation : annotationsForParam) {
                if (annotation instanceof Entity) {
                    entityAnnotations.add((Entity)annotation);
                }
            }
        }
        if (!entityAnnotations.isEmpty()) {
           
            if (entityAnnotations.size() > 1) {
                throw new ConfigurationException("action " + method.getName() + " in controller "
                        + className + " has more than one " + Entity.class.getName() + " annotation");
            }
           
            Expects expectsAnn = method.getAnnotation(Expects.class);
            if (expectsAnn == null) {
                throw new ConfigurationException("action " + method.getName() + " in controller "
                        + className + " has an " + Entity.class.getName() + " annotation, but no "
                        + Expects.class.getName() + " exists");
            }
            String contentType = expectsAnn.value();
            EntityMarshaller marshaller = entityMarshallerMap.get(contentType);
            if (marshaller != null) {
                paramMarshaller = marshaller;
            } else {
                logger.error("could not find parameter entity marshaller for content type "
                        + contentType + " for action " + method.getName() + " in controller "
                        + className);
            }
        }
        return paramMarshaller;
    }
   
    private EntityMarshaller getViewEntityMarshaller(Method method, String className) {
       
        EntityMarshaller viewMarshaller = new DefaultEntityMarshaller();
       
        Returns returnsAnnotation = method.getAnnotation(Returns.class);
        if (returnsAnnotation != null) {
            String contentType = returnsAnnotation.value();
            EntityMarshaller marshaller = entityMarshallerMap.get(contentType);
            if (marshaller != null) {
                viewMarshaller = marshaller;
            } else {
                logger.error("could not find view entity marshaller for content type "
                        + contentType + " for action " + method.getName() + " in controller "
                        + className);
            }
        }
       
        return viewMarshaller;
    }

    private void validateActionReturnType(Method actionMethod, String className) {

        Class<?> returnType = actionMethod.getReturnType();
        if (returnType.equals(Void.TYPE)) {

            throw new ConfigurationException("action " + actionMethod.getName() + " in controller "
                    + className + " must return " + View.class.getName() + " or one of its subtypes, " +
                            "or an object to be marshalled, but not void");
           
        } else if (!View.class.isAssignableFrom(returnType) &&
                actionMethod.getAnnotation(Returns.class) == null) {
           
            throw new ConfigurationException("action " + actionMethod.getName() + " in controller "
                    + className + " does not return a " + View.class.getName() + " or one of its subtypes " +
                            "and does not specify a @" + Returns.class.getName() + " annotation");
           
        } else if (View.class.isAssignableFrom(returnType) &&
                actionMethod.getAnnotation(Returns.class) != null) {
           
            throw new ConfigurationException("action " + actionMethod.getName() + " in controller "
                    + className + " returns a " + View.class.getName() + " or one of its subtypes " +
                            "and specifies a @" + Returns.class.getName() + " annotation");
        }
    }
   
    private void addRoute(Method method, String controllerClassName,
            String controllerVariable, String actionVariable, boolean isDefaultController) {
       
        String paramPath = getParamPathIfExists(method, controllerClassName);
       
        MojaveRoute route = new MojaveRoute(controllerVariable, actionVariable, paramPath);
        logger.debug("adding route " + route);
        router.add(route);
        if (isDefaultController) {
            route = new MojaveRoute(null, actionVariable, paramPath);
            logger.debug("adding route " + route);
            router.add(route);
        }
    }

    private String getParamPathIfExists(Method method, String controllerName) {
       
        String paramPath = null;
        ParamPath paramPathAnn = method.getAnnotation(ParamPath.class);
        if (paramPathAnn != null) {
            paramPath = paramPathAnn.value();
            validateParamPath(paramPath, method, controllerName);
        }
        return paramPath;
    }
   
    private void validateParamPath(String paramPath, Method method, String controllerName) {
       
        if (paramPath == null || paramPath.trim().length() == 0) {
            throw new ConfigurationException("Param path for method " + method.getName() +
                    " in controller " + controllerName + " is empty");
        }
       
        String[] params = ParamPathHelper.getParamNamesFrom(paramPath);
        String[] methodParams = ParamPathHelper.getParamNamesFrom(method);

        if (params.length > 0) {
            Set<String> paramsSet = new HashSet<String>(Arrays.asList(params));
            Set<String> methodParamsSet = new HashSet<String>(Arrays.asList(methodParams));
            if (!methodParamsSet.containsAll(paramsSet)) {
                throw new ConfigurationException("Param path " + paramPath + " for method " + method.getName() +
                        " in controller " + controllerName + " contains params not found in the method params");
            }
        }
    }
   
    private void setInterceptorsFor(Class<?> controllerClass) {

        Annotation annot = controllerClass.getAnnotation(InterceptedBy.class);
        if (annot != null) {

            List<Class<?>> processedInterceptors = processInterceptors(annot, controllerClass);

            controllerClassToInterceptorsMap.put(controllerClass, Collections.unmodifiableList(processedInterceptors));
        }
    }

    private void setInterceptorsForAction(Class<?> controllerClass, String action, Method method,
            Map<String, List<Class<?>>> actionInterceptorsMap) {

        Annotation annot = method.getAnnotation(InterceptedBy.class);
        if (annot != null) {

            List<Class<?>> processedInterceptors = processInterceptors(annot, controllerClass, method);

            actionInterceptorsMap.put(action, Collections.unmodifiableList(processedInterceptors));
        }
    }

    private void setInterceptorsForHttpMethodAction(Class<?> controllerClass, HttpMethod httpMethod, Method method,
            Map<HttpMethod, List<Class<?>>> httpMethodActionInterceptorsMap) {

        Annotation annot = method.getAnnotation(InterceptedBy.class);
        if (annot != null) {

            List<Class<?>> processedInterceptors = processInterceptors(annot, controllerClass, method);

            httpMethodActionInterceptorsMap.put(httpMethod, Collections.unmodifiableList(processedInterceptors));
        }
    }

    private void setInterceptorsForDefaultAction(Class<?> controllerClass, Method method) {

        Annotation annot = method.getAnnotation(InterceptedBy.class);
        if (annot != null) {

            List<Class<?>> processedInterceptors = processInterceptors(annot, controllerClass, method);

            controllerClassToDefaultActionInterceptorsMap.put(controllerClass,
                    Collections.unmodifiableList(processedInterceptors));
        }
    }

    private List<Class<?>> processInterceptors(Annotation inteceptedByAnnot, Class<?> controllerClass) {

        return processInterceptors(inteceptedByAnnot, controllerClass, null);
    }

    private List<Class<?>> processInterceptors(Annotation inteceptedByAnnot, Class<?> controllerClass, Method method) {

        List<Class<?>> foundInterceptors = new ArrayList<Class<?>>();
        Class<?>[] interceptors = ((InterceptedBy) inteceptedByAnnot).value();
        for (Class<?> interceptor : interceptors) {

            if (foundInterceptors.contains(interceptor)) {

                String message = "interceptor " + interceptor.getName() + " has already been declared";
                if (method != null) {
                    message += " in " + method.getName();
                }
                message += " in " + controllerClass.getName();
                throw new ConfigurationException(message);
            }

            addMethodsForInterceptor(interceptor);

            foundInterceptors.add(interceptor);
        }
        return foundInterceptors;
    }

    private void addMethodsForInterceptor(Class<?> interceptorClass) {

        /*
         * only add the methods in this interceptor if it hasn't been already
         * processed
         */
        if (!interceptorClassToAfterActionMap.containsKey(interceptorClass)
                && !interceptorClassToBeforeActionMap.containsKey(interceptorClass)) {

            Method[] methods = getAllMethodsIn(interceptorClass);
            FastClass fastClass = FastClass.create(interceptorClass);
            classToFastClassMap.put(interceptorClass, fastClass);

            for (int i = 0; i < methods.length; i++) {

                Annotation ann = methods[i].getAnnotation(BeforeAction.class);
                if (ann != null) {
                    addBeforeOrAfterActionSignature(interceptorClassToBeforeActionMap, BeforeAction.class,
                            interceptorClass, methods[i], fastClass);
                    continue;
                }

                ann = methods[i].getAnnotation(AfterAction.class);
                if (ann != null) {
                    addBeforeOrAfterActionSignature(interceptorClassToAfterActionMap, AfterAction.class,
                            interceptorClass, methods[i], fastClass);
                }
            }

            ActionSignature before = interceptorClassToBeforeActionMap.get(interceptorClass);
            ActionSignature after = interceptorClassToAfterActionMap.get(interceptorClass);

            if (before == null && after == null) {

                throw new ConfigurationException("interceptor " + interceptorClass.getName()
                        + " must have at least a @" + BeforeAction.class.getSimpleName() + " or a @"
                        + AfterAction.class.getSimpleName());
            }
        }
    }

    private void addBeforeOrAfterActionSignature(Map<Class<?>, ActionSignature> map, Class<?> annotationClass,
            Class<?> clazz, Method method, FastClass fastClass) {

        validateActionOccursOnlyOnce(map, annotationClass, clazz);
        validateMethodAcceptsOnlyInterceptorArgs(annotationClass, clazz, method);

        int fastIndex = fastClass.getIndex(method.getName(), method.getParameterTypes());

        ActionSignature sig = new BaseActionSignature(fastIndex, method.getName(), method.getParameterTypes(),
                new Annotation[][] {}, method.getDeclaredAnnotations());

        map.put(clazz, sig);
    }

    private void validateMethodAcceptsOnlyInterceptorArgs(Class<?> annotationClass,
            Class<?> clazz, Method method) {
       
        if (method.getParameterTypes().length > 0) {
            if (method.getParameterTypes().length != 1 ||
                    !(method.getParameterTypes()[0].equals(RequestContext.class))) {
               
                throw new ConfigurationException("a @" + annotationClass.getSimpleName()
                        + " method cannot take arguments in " + clazz.getName());
            }
        }
    }

    private void addAfterContructSignature(Map<Class<?>, ActionSignature> map, Class<?> annotationClass,
            Class<?> controllerClass, Method method, FastClass fastClass) {

        validateActionOccursOnlyOnce(map, annotationClass, controllerClass);
        validateMethodDoesNotAcceptArguments(annotationClass, controllerClass, method);

        int fastIndex = fastClass.getIndex(method.getName(), method.getParameterTypes());

        ActionSignature sig = new BaseActionSignature(fastIndex, method.getName(), method.getParameterTypes(),
                new Annotation[][] {}, method.getDeclaredAnnotations());

        map.put(controllerClass, sig);
    }

    private void validateMethodDoesNotAcceptArguments(Class<?> annotationClass,
            Class<?> controllerClass, Method method) {
       
        if (method.getParameterTypes().length > 0) {
            throw new ConfigurationException("a @" + annotationClass.getSimpleName()
                    + " method cannot take arguments in " + controllerClass.getName());
        }
    }

    private void validateActionOccursOnlyOnce(Map<Class<?>, ActionSignature> map, Class<?> annotationClass,
            Class<?> controllerClass) {
       
        ActionSignature m = map.get(controllerClass);
        if (m != null) {
            throw new ConfigurationException("there can be only one @" + annotationClass.getSimpleName()
                    + " method in " + controllerClass.getName());
        }
    }
   
    private void addDefaultActionSignature(Map<Class<?>, ActionSignature> map, Class<?> annotationClass,
            Class<?> controllerClass, Method method, FastClass fastClass,
            String controllerVariable, boolean isDefaultController) {

        validateActionOccursOnlyOnce(map, annotationClass, controllerClass);
        validateActionReturnType(method, controllerClass.getName());
       
        EntityMarshaller paramMarshaller = getParamEntityMarshaller(method, controllerClass.getName());
        EntityMarshaller viewMarshaller = getViewEntityMarshaller(method, controllerClass.getName());

        int fastIndex = fastClass.getIndex(method.getName(), method.getParameterTypes());

        ActionSignature sig = new DefaultActionSignature(fastIndex, method.getName(), method.getParameterTypes(),
                method.getParameterAnnotations(), method.getDeclaredAnnotations(),
                paramMarshaller, viewMarshaller);

        map.put(controllerClass, sig);
       
        addRoute(method, controllerClass.getName(), controllerVariable, null, isDefaultController);
    }
}
TOP

Related Classes of org.mojavemvc.core.MappedControllerDatabase

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.