blob: 9db836f87ada5bb1c85019424976d152213421f6 [file] [log] [blame]
/*
* 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 groovy.lang.Closure;
import groovy.lang.GroovyObject;
import groovy.lang.ProxyMetaClass;
import groovy.util.Proxy;
import org.apache.commons.collections.BeanMap;
import org.apache.commons.collections.map.CompositeMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsControllerClass;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.commons.metaclass.GenericDynamicProperty;
import org.codehaus.groovy.grails.scaffolding.GrailsScaffolder;
import org.codehaus.groovy.grails.web.metaclass.ChainDynamicMethod;
import org.codehaus.groovy.grails.web.metaclass.ControllerDynamicMethods;
import org.codehaus.groovy.grails.web.metaclass.GetParamsDynamicProperty;
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.NoClosurePropertyForURIException;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.NoViewNameDefinedException;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.UnknownControllerException;
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.beans.IntrospectionException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SimpleGrailsControllerHelper implements GrailsControllerHelper {
private static final String SCAFFOLDER = "Scaffolder";
private GrailsApplication application;
private ApplicationContext applicationContext;
private Map chainModel = Collections.EMPTY_MAP;
private ControllerDynamicMethods interceptor;
private GrailsScaffolder scaffolder;
private ServletContext servletContext;
private GrailsApplicationAttributes grailsAttributes;
private Pattern uriPattern = Pattern.compile("/(\\w+)/?(\\w*)/?(\\w*)/?(.*)");
private static final Log LOG = LogFactory.getLog(SimpleGrailsControllerHelper.class);
private static final String DISPATCH_ACTION_PARAMETER = "_action";
private static final String ID_PARAMETER = "id";
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 this.application.getController(name);
}
public GrailsScaffolder getScaffolderForController(String controllerName) {
GrailsControllerClass controllerClass = getControllerClassByName(controllerName);
return (GrailsScaffolder)applicationContext.getBean( controllerClass.getFullName() + SCAFFOLDER );
}
/* (non-Javadoc)
* @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#getControllerClassByURI(java.lang.String)
*/
public GrailsControllerClass getControllerClassByURI(String uri) {
return this.application.getControllerByURI(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());
}
/* (non-Javadoc)
* @see org.codehaus.groovy.grails.web.servlet.mvc.GrailsControllerHelper#handleURI(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
public ModelAndView handleURI(String uri, HttpServletRequest request, HttpServletResponse response) {
return handleURI(uri,request,response,Collections.EMPTY_MAP);
}
/**
* 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 model) {
for (Iterator keyIter = model.keySet().iterator(); keyIter.hasNext();) {
Object current = keyIter.next();
Object modelObject = model.get(current);
if(modelObject instanceof Proxy) {
model.put( current, ((Proxy)modelObject).getAdaptee() );
}
}
}
/* (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, HttpServletRequest request, HttpServletResponse response, Map params) {
if(uri == null)
throw new IllegalArgumentException("Controller URI [" + uri + "] cannot be null!");
// step 1: process the uri
if (uri.indexOf("?") > -1) {
uri = uri.substring(0, uri.indexOf("?"));
}
if(uri.indexOf('\\') > -1) {
uri = uri.replaceAll("\\\\", "/");
}
if(!uri.startsWith("/"))
uri = '/' + uri;
if(uri.endsWith("/"))
uri = uri.substring(0,uri.length() - 1);
String id = null;
String controllerName = null;
String actionName = null;
Map extraParams = Collections.EMPTY_MAP;
Matcher m = uriPattern.matcher(uri);
if(m.find()) {
controllerName = m.group(1);
actionName = m.group(2);
uri = '/' + controllerName + '/' + actionName;
id = m.group(3);
String extraParamsString = m.group(4);
if(extraParamsString != null && extraParamsString.indexOf('/') > - 1) {
String[] tokens = extraParamsString.split("/");
extraParams = new HashMap();
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
if(i == 0 || ((i % 2) == 0)) {
if((i + 1) < tokens.length) {
extraParams.put(token, tokens[i + 1]);
}
}
}
}
}
// if the action name is blank check its included as dispatch parameter
if(StringUtils.isBlank(actionName) && request.getParameter(DISPATCH_ACTION_PARAMETER) != null) {
actionName = GrailsClassUtils.getPropertyNameRepresentation(request.getParameter(DISPATCH_ACTION_PARAMETER));
uri = '/' + controllerName + '/' + actionName;
}
// if the id is blank check if its a request parameter
if(StringUtils.isBlank(id) && request.getParameter(ID_PARAMETER) != null) {
id = request.getParameter(ID_PARAMETER);
}
if(LOG.isDebugEnabled()) {
LOG.debug("Processing request for controller ["+controllerName+"], action ["+actionName+"], and id ["+id+"]");
}
if(LOG.isTraceEnabled()) {
LOG.trace("Extra params from uri ["+extraParams+"] ");
}
// Step 2: lookup the controller in the application.
GrailsControllerClass controllerClass = getControllerClassByURI(uri);
// parse the uri in its individual tokens
controllerName = WordUtils.uncapitalize(controllerClass.getName());
if (controllerClass == null) {
throw new UnknownControllerException("No controller found for URI [" + uri + "]!");
}
// Step 3: load controller from application context.
GroovyObject controller = getControllerInstance(controllerClass);
request.setAttribute( GrailsApplicationAttributes.CONTROLLER, controller );
// Step 3a: Configure a proxy interceptor for controller dynamic methods for this request
if(this.interceptor == null) {
try {
interceptor = new ControllerDynamicMethods(controller,this,request,response);
}
catch(IntrospectionException ie) {
throw new ControllerExecutionException("Error creating dynamic controller methods for controller ["+controller.getClass()+"]: " + ie.getMessage(), ie);
}
}
// Step 3b: if scaffolding retrieve scaffolder
if(controllerClass.isScaffolding()) {
this.scaffolder = (GrailsScaffolder)applicationContext.getBean( controllerClass.getFullName() + SCAFFOLDER );
if(this.scaffolder == null)
throw new IllegalStateException("Scaffolding set to true for controller ["+controllerClass.getFullName()+"] but no scaffolder available!");
}
// Step 4: get closure property name for URI.
if(StringUtils.isBlank(actionName))
actionName = controllerClass.getClosurePropertyName(uri);
if (StringUtils.isBlank(actionName)) {
// Step 4a: Check if scaffolding
if( controllerClass.isScaffolding() && !scaffolder.supportsAction(actionName))
throw new NoClosurePropertyForURIException("Could not find closure property for URI [" + uri + "] for controller [" + controllerClass.getFullName() + "]!");
}
// Step 4a: Set dynamic properties on controller
controller.setProperty(ControllerDynamicMethods.CONTROLLER_URI_PROPERTY, '/' + controllerName);
controller.setProperty(ControllerDynamicMethods.ACTION_URI_PROPERTY, '/' + controllerName + '/' + actionName);
// populate additional params from url
Map controllerParams = (Map)controller.getProperty(GetParamsDynamicProperty.PROPERTY_NAME);
if(!StringUtils.isBlank(id)) {
controllerParams.put(GrailsApplicationAttributes.ID_PARAM, id);
}
if(!extraParams.isEmpty()) {
for (Iterator i = extraParams.keySet().iterator(); i.hasNext();) {
String name = (String) i.next();
controllerParams.put(name,extraParams.get(name));
}
}
// set the flash scope instance to its next state and set on controller
FlashScope fs = this.grailsAttributes.getFlashScope(request);
fs.next();
controller.setProperty(ControllerDynamicMethods.FLASH_SCOPE_PROPERTY,fs);
// Step 5: get the view name for this URI.
String viewName = controllerClass.getViewByURI(uri);
// Step 6: get closure from closure property
Closure action = (Closure)controller.getProperty(actionName);
if(action == null)
throw new IllegalStateException("Scaffolder supports action ["+actionName +"] for controller ["+controllerClass.getFullName()+"] but getAction returned null!");
// Step 7: process the action
Object returnValue = handleAction( controller,action,request,response,params );
// Step 8: determine return value type and handle accordingly
return handleActionResponse(controller,returnValue,actionName,viewName);
}
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) {
if(interceptor == null) {
ProxyMetaClass pmc = (ProxyMetaClass)controller.getMetaClass();
interceptor = (ControllerDynamicMethods)pmc.getInterceptor();
}
// if there are additional params add them to the params dynamic property
if(params != null && !params.isEmpty()) {
GetParamsDynamicProperty paramsProp = (GetParamsDynamicProperty)interceptor.getDynamicProperty( GetParamsDynamicProperty.PROPERTY_NAME );
paramsProp.addParams( params );
}
// check the chain model is not empty and add it
if(!this.chainModel.isEmpty()) {
// get the "chainModel" property
GenericDynamicProperty chainProperty = (GenericDynamicProperty)interceptor.getDynamicProperty(ChainDynamicMethod.PROPERTY_CHAIN_MODEL);
// if it doesn't exist create it
if(chainProperty == null) {
interceptor.addDynamicProperty( new GenericDynamicProperty( ChainDynamicMethod.PROPERTY_CHAIN_MODEL,Map.class,this.chainModel,false ) );
}
else {
// otherwise add to it
Map chainPropertyModel = (Map)chainProperty.get(controller);
chainPropertyModel.putAll( this.chainModel );
this.chainModel = chainPropertyModel;
}
}
// Step 7: determine argument count and execute.
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);
Boolean renderView = (Boolean)controller.getProperty(ControllerDynamicMethods.RENDER_VIEW_PROPERTY);
FlashScope fs = this.grailsAttributes.getFlashScope((HttpServletRequest)controller.getProperty(ControllerDynamicMethods.REQUEST_PROPERTY));
if(fs.containsKey(ChainDynamicMethod.PROPERTY_CHAIN_MODEL)) {
this.chainModel = (Map)fs.get(ChainDynamicMethod.PROPERTY_CHAIN_MODEL);
if(this.chainModel == null)
this.chainModel = Collections.EMPTY_MAP;
}
if(renderView == null) renderView = Boolean.TRUE;
if(!renderView.booleanValue()) {
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 returnModel = (Map)returnValue;
removeProxiesFromModelObjects(returnModel);
if(!this.chainModel.isEmpty()) {
returnModel.putAll(this.chainModel);
}
return new ModelAndView(viewName, returnModel);
} 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);
}
}
}