org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsControllerHelper.java

Source

/*
 * Copyright 2004-2005 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 org.codehaus.groovy.grails.web.servlet.mvc;

import grails.util.Environment;
import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import groovy.util.Proxy;

import java.io.IOException;
import java.security.AccessControlException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.map.CompositeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsControllerClass;
import org.codehaus.groovy.grails.plugins.GrailsPluginUtils;
import org.codehaus.groovy.grails.plugins.support.aware.GrailsApplicationAware;
import org.codehaus.groovy.grails.web.metaclass.ControllerDynamicMethods;
import org.codehaus.groovy.grails.web.servlet.DefaultGrailsApplicationAttributes;
import org.codehaus.groovy.grails.web.servlet.FlashScope;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.NoViewNameDefinedException;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.UnknownControllerException;
import org.codehaus.groovy.grails.web.util.WebUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

/**
 * Processes Grails controller requests and responses.
 *
 * @author Graeme Rocher
 * @author Stephane Maldini
 * @since 2.0
 */
public abstract class AbstractGrailsControllerHelper
        implements ApplicationContextAware, ServletContextAware, GrailsApplicationAware {

    protected GrailsApplication application;
    protected ApplicationContext applicationContext;
    protected ServletContext servletContext;
    protected GrailsApplicationAttributes grailsAttributes;

    private static final Log LOG = LogFactory.getLog(AbstractGrailsControllerHelper.class);
    private static final String PROPERTY_CHAIN_MODEL = "chainModel";
    private static final String FORWARD_IN_PROGRESS = "org.codehaus.groovy.grails.FORWARD_IN_PROGRESS";
    private static final String FORWARD_CALLED = "org.codehaus.groovy.grails.FORWARD_CALLED";
    private Collection<ActionResultTransformer> actionResultTransformers = Collections.emptyList();
    protected boolean developmentMode = Environment.isDevelopmentMode();

    public ServletContext getServletContext() {
        return servletContext;
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#getControllerClassByName(java.lang.String)
     */
    public GrailsControllerClass getControllerClassByName(String name) {
        return (GrailsControllerClass) application.getArtefact(ControllerArtefactHandler.TYPE, name);
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#getControllerClassByURI(java.lang.String)
     */
    public GrailsControllerClass getControllerClassByURI(String uri) {
        return (GrailsControllerClass) application.getArtefactForFeature(ControllerArtefactHandler.TYPE, uri);
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#getControllerInstance(org.codehaus.groovy.grails.commons.GrailsControllerClass)
     */
    public GroovyObject getControllerInstance(GrailsControllerClass controllerClass) {
        return (GroovyObject) applicationContext.getBean(controllerClass.getFullName());
    }

    /**
     * If in Proxy's are used in the Groovy context, unproxy (is that a word?) them by setting
     * the adaptee as the value in the map so that they can be used in non-groovy view technologies
     *
     * @param model The model as a map
     */
    private void removeProxiesFromModelObjects(Map<Object, Object> model) {
        for (Map.Entry<Object, Object> entry : model.entrySet()) {
            if (entry.getValue() instanceof Proxy) {
                entry.setValue(((Proxy) entry.getValue()).getAdaptee());
            }
        }
    }

    public ModelAndView handleURI(String uri, GrailsWebRequest request) {
        return handleURI(uri, request, Collections.EMPTY_MAP);
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#handleURI(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.Map)
     */
    @SuppressWarnings("rawtypes")
    public ModelAndView handleURI(final String originalUri, GrailsWebRequest grailsWebRequest, Map params) {
        Assert.notNull(originalUri, "Controller URI [" + originalUri + "] cannot be null!");

        HttpServletRequest request = grailsWebRequest.getCurrentRequest();
        HttpServletResponse response = grailsWebRequest.getCurrentResponse();

        String uri = originalUri;
        if (uri.endsWith("/")) {
            uri = uri.substring(0, uri.length() - 1);
        }

        // Step 2: lookup the controller in the application.
        GrailsControllerClass controllerClass = null;
        if (!WebUtils.isIncludeRequest(request) && request.getAttribute(FORWARD_IN_PROGRESS) == null) {

            Object attribute = grailsWebRequest.getAttribute(GrailsApplicationAttributes.GRAILS_CONTROLLER_CLASS,
                    WebRequest.SCOPE_REQUEST);
            if (attribute instanceof GrailsControllerClass) {
                controllerClass = (GrailsControllerClass) attribute;
                Boolean canUse = (Boolean) grailsWebRequest.getAttribute(
                        GrailsApplicationAttributes.GRAILS_CONTROLLER_CLASS_AVAILABLE, WebRequest.SCOPE_REQUEST);
                if (canUse == null) {
                    controllerClass = null;
                } else {
                    grailsWebRequest.removeAttribute(GrailsApplicationAttributes.GRAILS_CONTROLLER_CLASS_AVAILABLE,
                            WebRequest.SCOPE_REQUEST);
                }
            }
        }

        if (controllerClass == null) {
            controllerClass = getControllerClassByURI(uri);
        }

        if (controllerClass == null) {
            throw new UnknownControllerException("No controller found for URI [" + uri + "]!");
        }

        String actionName = controllerClass.getMethodActionName(uri);
        if (controllerClass.isFlowAction(actionName)) {
            // direct access to flow action not allowed
            return null;
        }
        grailsWebRequest.setActionName(actionName);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Processing request for controller action [" + actionName + "]");
        }
        // Step 3: load controller from application context.
        GroovyObject controller = getControllerInstance(controllerClass);

        if (!controllerClass.isHttpMethodAllowedForAction(controller, request.getMethod(), actionName)) {
            try {
                response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                return null;
            } catch (IOException e) {
                throw new ControllerExecutionException("I/O error sending 403 error", e);
            }
        }

        request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller);

        // Step 4: Set grails attributes in request scope
        request.setAttribute(GrailsApplicationAttributes.REQUEST_SCOPE_ID, grailsAttributes);

        // Step 5: get the view name for this URI.
        String viewName = controllerClass.getViewByURI(uri);

        boolean executeAction = invokeBeforeInterceptor(controller, actionName, controllerClass);
        // if the interceptor returned false don't execute the action
        if (!executeAction) {
            return null;
        }

        ModelAndView mv = executeAction(controller, actionName, viewName, grailsWebRequest, params);

        boolean returnModelAndView = invokeAfterInterceptor(controllerClass, controller, actionName, mv)
                && !response.isCommitted();
        return returnModelAndView ? mv : null;
    }

    protected abstract Object retrieveAction(GroovyObject controller, String actionName,
            HttpServletResponse response);

    /**
     * Invokes the action defined by the webRequest for the given arguments.
     *
     * @param controller The controller instance
     * @param actionName The current action
     * @param viewName The name of the view to delegate to if necessary
     * @param webRequest the current web Request
     * @param params A map of parameters
     * @return A Spring ModelAndView instance
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected ModelAndView executeAction(GroovyObject controller, String actionName, String viewName,
            GrailsWebRequest webRequest, Map params) {

        // Step 5a: Check if there is a before interceptor if there is execute it
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        HttpServletResponse response = webRequest.getCurrentResponse();
        HttpServletRequest request = webRequest.getCurrentRequest();
        try {
            // Step 6: get action from implementation
            Object action = retrieveAction(controller, actionName, response);

            // Step 7: process the action
            Object returnValue = null;
            try {
                returnValue = handleAction(controller, action, request, response, params);
                for (ActionResultTransformer actionResultTransformer : actionResultTransformers) {
                    returnValue = actionResultTransformer.transformActionResult(webRequest, viewName, returnValue);
                }
            } catch (Throwable t) {
                String pluginName = GrailsPluginUtils.getPluginName(controller.getClass());
                pluginName = pluginName != null ? "in plugin [" + pluginName + "]" : "";
                throw new ControllerExecutionException(
                        "Executing action [" + actionName + "] of controller [" + controller.getClass().getName()
                                + "] " + pluginName + " caused exception: " + t.getMessage(),
                        t);
            }

            // Step 8: determine return value type and handle accordingly
            Map chainModel = initChainModel(request);

            if (response.isCommitted()
                    || request.getAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED) != null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Response has been redirected, returning null model and view");
                }
                return null;
            }

            TokenResponseHandler handler = (TokenResponseHandler) request.getAttribute(TokenResponseHandler.KEY);
            if (handler != null && !handler.wasInvoked() && handler.wasInvalidToken()) {
                String uri = (String) request.getAttribute(SynchronizerTokensHolder.TOKEN_URI);
                if (uri == null) {
                    uri = WebUtils.getForwardURI(request);
                }
                try {
                    FlashScope flashScope = webRequest.getFlashScope();
                    flashScope.put("invalidToken", request.getParameter(SynchronizerTokensHolder.TOKEN_KEY));
                    response.sendRedirect(uri);
                    return null;
                } catch (IOException e) {
                    throw new ControllerExecutionException("I/O error sending redirect to URI: " + uri, e);
                }
            }

            if (request.getAttribute(FORWARD_CALLED) == null
                    && request.getAttribute(GrailsApplicationAttributes.ASYNC_STARTED) == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Action [" + actionName + "] executed with result [" + returnValue
                            + "] and view name [" + viewName + "]");
                }
                ModelAndView mv = handleActionResponse(controller, returnValue, webRequest, chainModel, actionName,
                        viewName);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Action [" + actionName + "] handled, created Spring model and view [" + mv + "]");
                }
                return mv;
            }

            return null;
        } finally {
            try {
                Thread.currentThread().setContextClassLoader(cl);
            } catch (AccessControlException e) {
                // not allowed by container, probably related to WAR deployment on AppEngine. Proceed.
            }
        }
    }

    private boolean invokeBeforeInterceptor(GroovyObject controller, String actionName,
            GrailsControllerClass controllerClass) {
        if (!(controllerClass.isInterceptedBefore(controller, actionName))) {
            return true;
        }

        Closure<?> beforeInterceptor = controllerClass.getBeforeInterceptor(controller);
        if (beforeInterceptor == null) {
            return true;
        }

        Object interceptorResult = beforeInterceptor.call();
        return interceptorResult instanceof Boolean ? (Boolean) interceptorResult : true;
    }

    @SuppressWarnings("rawtypes")
    private boolean invokeAfterInterceptor(GrailsControllerClass controllerClass, GroovyObject controller,
            String actionName, ModelAndView mv) {
        if (!controllerClass.isInterceptedAfter(controller, actionName)) {
            return true;
        }

        // Step 9: Check if there is after interceptor
        Object interceptorResult = null;
        Closure afterInterceptor = controllerClass.getAfterInterceptor(controller);
        Map model = new HashMap();
        if (mv != null) {
            model = mv.getModel() == null ? new HashMap() : mv.getModel();
        }
        switch (afterInterceptor.getMaximumNumberOfParameters()) {
        case 1:
            interceptorResult = afterInterceptor.call(new Object[] { model });
            break;
        case 2:
            interceptorResult = afterInterceptor.call(new Object[] { model, mv });
            break;
        default:
            throw new ControllerExecutionException("AfterInterceptor closure must accept one or two parameters");
        }

        return interceptorResult instanceof Boolean ? (Boolean) interceptorResult : true;
    }

    public GrailsApplicationAttributes getGrailsAttributes() {
        return grailsAttributes;
    }

    public Object handleAction(GroovyObject controller, Object action, HttpServletRequest request,
            HttpServletResponse response) {
        return handleAction(controller, action, request, response, Collections.EMPTY_MAP);
    }

    protected abstract Object invoke(GroovyObject controller, Object action);

    @SuppressWarnings("rawtypes")
    public Object handleAction(GroovyObject controller, Object action, HttpServletRequest request,
            HttpServletResponse response, Map params) {
        GrailsParameterMap paramsMap = (GrailsParameterMap) controller.getProperty("params");
        // if there are additional params add them to the params dynamic property
        if (params != null && !params.isEmpty()) {
            paramsMap.putAll(params);
        }
        Object returnValue = action == null ? null : invoke(controller, action);

        // Step 8: add any errors to the request
        request.setAttribute(GrailsApplicationAttributes.ERRORS,
                controller.getProperty(ControllerDynamicMethods.ERRORS_PROPERTY));

        return returnValue;
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#handleActionResponse(org.codehaus.groovy.grails.commons.GrailsControllerClass, java.lang.Object, java.lang.String, java.lang.String)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public ModelAndView handleActionResponse(GroovyObject controller, Object returnValue,
            GrailsWebRequest webRequest, Map chainModel, String closurePropertyName, String viewName) {

        boolean viewNameBlank = !StringUtils.hasLength(viewName);
        ModelAndView explicitModelAndView = (ModelAndView) controller
                .getProperty(ControllerDynamicMethods.MODEL_AND_VIEW_PROPERTY);

        if (!webRequest.isRenderView()) {
            return null;
        }

        if (explicitModelAndView != null) {
            return explicitModelAndView;
        }

        if (returnValue == null) {
            if (viewNameBlank) {
                return null;
            }
            return new ModelAndView(viewName, chainModel);
        }

        if (returnValue instanceof Map) {
            // remove any Proxy wrappers and set the adaptee as the value
            Map finalModel = new LinkedHashMap();
            if (!chainModel.isEmpty()) {
                finalModel.putAll(chainModel);
            }
            Map returnModel = (Map) returnValue;
            finalModel.putAll(returnModel);

            removeProxiesFromModelObjects(finalModel);
            return new ModelAndView(viewName, finalModel);
        }

        if (returnValue instanceof ModelAndView) {
            ModelAndView modelAndView = (ModelAndView) returnValue;

            // remove any Proxy wrappers and set the adaptee as the value
            Map modelMap = modelAndView.getModel();
            removeProxiesFromModelObjects(modelMap);

            if (!chainModel.isEmpty()) {
                modelAndView.addAllObjects(chainModel);
            }

            if (modelAndView.getView() == null && modelAndView.getViewName() == null) {
                if (viewNameBlank) {
                    throw new NoViewNameDefinedException(
                            "ModelAndView instance returned by and no view name defined by nor for closure on property ["
                                    + closurePropertyName + "] in controller [" + controller.getClass() + "]!");
                }

                modelAndView.setViewName(viewName);
            }
            return modelAndView;
        }

        return new ModelAndView(viewName, chainModel);
    }

    @SuppressWarnings("rawtypes")
    private Map initChainModel(HttpServletRequest request) {
        FlashScope fs = grailsAttributes.getFlashScope(request);
        if (fs.containsKey(PROPERTY_CHAIN_MODEL)) {
            Map chainModel = (Map) fs.get(PROPERTY_CHAIN_MODEL);
            if (chainModel == null) {
                chainModel = Collections.EMPTY_MAP;
            }
            return chainModel;
        }
        return Collections.EMPTY_MAP;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.actionResultTransformers = applicationContext.getBeansOfType(ActionResultTransformer.class).values();
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
        this.grailsAttributes = new DefaultGrailsApplicationAttributes(servletContext);
    }

    public void setGrailsApplication(GrailsApplication application) {
        this.application = application;
    }
}