Java tutorial
/* * 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.GrailsUtil; import groovy.lang.Closure; import groovy.lang.GroovyObject; import groovy.lang.MissingPropertyException; import groovy.util.Proxy; import org.apache.commons.beanutils.BeanMap; import org.apache.commons.collections.map.CompositeMap; import org.apache.commons.lang.StringUtils; 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.web.metaclass.ControllerDynamicMethods; import org.codehaus.groovy.grails.web.metaclass.ForwardMethod; 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.codehaus.groovy.grails.plugins.GrailsPluginUtils; import org.springframework.context.ApplicationContext; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.LinkedHashMap; /** * <p>This is a helper class that does the main job of dealing with Grails web requests * * @author Graeme Rocher * @since 0.1 * * Created: 12-Jan-2006 */ public class SimpleGrailsControllerHelper implements GrailsControllerHelper { private GrailsApplication application; private ApplicationContext applicationContext; private Map chainModel = Collections.EMPTY_MAP; private ServletContext servletContext; private GrailsApplicationAttributes grailsAttributes; private GrailsWebRequest webRequest; private static final Log LOG = LogFactory.getLog(SimpleGrailsControllerHelper.class); private static final char SLASH = '/'; private static final String PROPERTY_CHAIN_MODEL = "chainModel"; private String id; private String controllerName; private String actionName; private String controllerActionURI; public SimpleGrailsControllerHelper(GrailsApplication application, ApplicationContext context, ServletContext servletContext) { super(); this.application = application; this.applicationContext = context; this.servletContext = servletContext; this.grailsAttributes = new DefaultGrailsApplicationAttributes(this.servletContext); } public ServletContext getServletContext() { return this.servletContext; } /* (non-Javadoc) * @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#getControllerClassByName(java.lang.String) */ public GrailsControllerClass getControllerClassByName(String name) { return (GrailsControllerClass) this.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) this.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) this.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 entry : model.entrySet()) { if (entry.getValue() instanceof Proxy) { entry.setValue(((Proxy) entry.getValue()).getAdaptee()); } } } public ModelAndView handleURI(String uri, GrailsWebRequest webRequest) { return handleURI(uri, webRequest, 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) */ public ModelAndView handleURI(String uri, GrailsWebRequest webRequest, Map params) { if (uri == null) { throw new IllegalArgumentException("Controller URI [" + uri + "] cannot be null!"); } HttpServletRequest request = webRequest.getCurrentRequest(); HttpServletResponse response = webRequest.getCurrentResponse(); configureStateForWebRequest(webRequest, request); if (uri.endsWith("/")) { uri = uri.substring(0, uri.length() - 1); } // if the id is blank check if its a request parameter // Step 2: lookup the controller in the application. GrailsControllerClass controllerClass = getControllerClassByURI(uri); if (controllerClass == null) { throw new UnknownControllerException("No controller found for URI [" + uri + "]!"); } actionName = controllerClass.getClosurePropertyName(uri); webRequest.setActionName(actionName); if (LOG.isDebugEnabled()) { LOG.debug("Processing request for controller [" + controllerName + "], action [" + actionName + "], and id [" + id + "]"); } controllerActionURI = SLASH + controllerName + SLASH + actionName + SLASH; // 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, this.grailsAttributes); // Step 5: get the view name for this URI. String viewName = controllerClass.getViewByURI(uri); boolean executeAction = invokeBeforeInterceptor(controller, controllerClass); // if the interceptor returned false don't execute the action if (!executeAction) return null; ModelAndView mv = executeAction(controller, controllerClass, viewName, request, response, params); boolean returnModelAndView = invokeAfterInterceptor(controllerClass, controller, mv) && !response.isCommitted(); return returnModelAndView ? mv : null; } /** * Invokes the action defined by the webRequest for the given arguments * * @param controller The controller instance * @param controllerClass The GrailsControllerClass that defines the conventions within the controller * @param viewName The name of the view to delegate to if necessary * @param request The HttpServletRequest object * @param response The HttpServletResponse object * @param params A map of parameters * @return A Spring ModelAndView instance */ protected ModelAndView executeAction(GroovyObject controller, GrailsControllerClass controllerClass, String viewName, HttpServletRequest request, HttpServletResponse response, Map params) { // Step 5a: Check if there is a before interceptor if there is execute it ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { // Step 6: get closure from closure property Closure action; try { action = (Closure) controller.getProperty(actionName); } catch (MissingPropertyException mpe) { try { response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } catch (IOException e) { throw new ControllerExecutionException("I/O error sending 404 error", e); } } // Step 7: process the action Object returnValue = null; try { returnValue = handleAction(controller, action, request, response, params); } catch (Throwable t) { GrailsUtil.deepSanitize(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 initChainModel(controller); if (response.isCommitted()) { if (LOG.isDebugEnabled()) { LOG.debug("Response has been redirected, returning null model and view"); } return null; } else { TokenResponseHandler handler = (TokenResponseHandler) request .getAttribute(TokenResponseHandler.KEY); if (handler != null && !handler.wasInvoked() && handler.wasInvalidToken()) { String uri = (String) request.getAttribute(SynchronizerToken.URI); if (uri == null) { uri = WebUtils.getForwardURI(request); } try { FlashScope flashScope = webRequest.getFlashScope(); flashScope.put("invalidToken", request.getParameter(SynchronizerToken.KEY)); response.sendRedirect(uri); return null; } catch (IOException e) { throw new ControllerExecutionException("I/O error sending redirect to URI: " + uri, e); } } else if (request.getAttribute(ForwardMethod.CALLED) == null) { if (LOG.isDebugEnabled()) { LOG.debug("Action [" + actionName + "] executed with result [" + returnValue + "] and view name [" + viewName + "]"); } ModelAndView mv = handleActionResponse(controller, returnValue, actionName, viewName); if (LOG.isDebugEnabled()) { LOG.debug( "Action [" + actionName + "] handled, created Spring model and view [" + mv + "]"); } return mv; } else { return null; } } } finally { try { Thread.currentThread().setContextClassLoader(cl); } catch (java.security.AccessControlException e) { // not allowed by container, probably related to WAR deployment on AppEngine. Proceed. } } } private boolean invokeBeforeInterceptor(GroovyObject controller, GrailsControllerClass controllerClass) { boolean executeAction = true; if (controllerClass.isInterceptedBefore(controller, actionName)) { Closure beforeInterceptor = controllerClass.getBeforeInterceptor(controller); if (beforeInterceptor != null) { if (beforeInterceptor.getDelegate() != controller) { beforeInterceptor.setDelegate(controller); beforeInterceptor.setResolveStrategy(Closure.DELEGATE_FIRST); } Object interceptorResult = beforeInterceptor.call(); if (interceptorResult instanceof Boolean) { executeAction = ((Boolean) interceptorResult).booleanValue(); } } } return executeAction; } private void configureStateForWebRequest(GrailsWebRequest webRequest, HttpServletRequest request) { this.webRequest = webRequest; this.actionName = webRequest.getActionName(); this.controllerName = webRequest.getControllerName(); this.id = webRequest.getId(); if (StringUtils.isBlank(id) && request.getParameter(GrailsWebRequest.ID_PARAMETER) != null) { id = request.getParameter(GrailsWebRequest.ID_PARAMETER); } } private boolean invokeAfterInterceptor(GrailsControllerClass controllerClass, GroovyObject controller, ModelAndView mv) { // Step 9: Check if there is after interceptor Object interceptorResult = null; if (controllerClass.isInterceptedAfter(controller, actionName)) { Closure afterInterceptor = controllerClass.getAfterInterceptor(controller); if (afterInterceptor.getDelegate() != controller) { afterInterceptor.setDelegate(controller); afterInterceptor.setResolveStrategy(Closure.DELEGATE_FIRST); } Map model = new HashMap(); if (mv != null) { model = mv.getModel() != null ? mv.getModel() : new HashMap(); } 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 != null && interceptorResult instanceof Boolean) || ((Boolean) interceptorResult).booleanValue(); } public GrailsApplicationAttributes getGrailsAttributes() { return this.grailsAttributes; } public Object handleAction(GroovyObject controller, Closure action, HttpServletRequest request, HttpServletResponse response) { return handleAction(controller, action, request, response, Collections.EMPTY_MAP); } public Object handleAction(GroovyObject controller, Closure 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.call(); // 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) */ public ModelAndView handleActionResponse(GroovyObject controller, Object returnValue, String closurePropertyName, String viewName) { boolean viewNameBlank = (viewName == null || viewName.length() == 0); // reset the metaclass ModelAndView explicityModelAndView = (ModelAndView) controller .getProperty(ControllerDynamicMethods.MODEL_AND_VIEW_PROPERTY); if (!webRequest.isRenderView()) { return null; } else if (explicityModelAndView != null) { return explicityModelAndView; } else if (returnValue == null) { if (viewNameBlank) { return null; } else { Map model; if (!this.chainModel.isEmpty()) { model = new CompositeMap(this.chainModel, new BeanMap(controller)); } else { model = new BeanMap(controller); } return new ModelAndView(viewName, model); } } else if (returnValue instanceof Map) { // remove any Proxy wrappers and set the adaptee as the value Map finalModel = new LinkedHashMap(); if (!this.chainModel.isEmpty()) { finalModel.putAll(this.chainModel); } Map returnModel = (Map) returnValue; finalModel.putAll(returnModel); removeProxiesFromModelObjects(finalModel); return new ModelAndView(viewName, finalModel); } else 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 (!this.chainModel.isEmpty()) { modelAndView.addAllObjects(this.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() + "]!"); } else { modelAndView.setViewName(viewName); } } return modelAndView; } else { Map model; if (!this.chainModel.isEmpty()) { model = new CompositeMap(this.chainModel, new BeanMap(controller)); } else { model = new BeanMap(controller); } return new ModelAndView(viewName, model); } } private void initChainModel(GroovyObject controller) { FlashScope fs = this.grailsAttributes.getFlashScope( (HttpServletRequest) controller.getProperty(ControllerDynamicMethods.REQUEST_PROPERTY)); if (fs.containsKey(PROPERTY_CHAIN_MODEL)) { this.chainModel = (Map) fs.get(PROPERTY_CHAIN_MODEL); if (this.chainModel == null) this.chainModel = Collections.EMPTY_MAP; } } }