blob: 24a89856893a9d91fcfeaf830b0def772af0da5f [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 java.beans.IntrospectionException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//import org.apache.commons.lang.WordUtils;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsControllerClass;
import org.codehaus.groovy.grails.commons.metaclass.GenericDynamicProperty;
import org.codehaus.groovy.grails.commons.metaclass.PropertyAccessProxyMetaClass;
import org.codehaus.groovy.grails.web.metaclass.ChainDynamicMethod;
import org.codehaus.groovy.grails.web.metaclass.ControllerDynamicMethodsInterceptor;
import org.codehaus.groovy.grails.web.metaclass.GetParamsDynamicProperty;
import org.codehaus.groovy.grails.web.servlet.GrailsHttpServletRequest;
import org.codehaus.groovy.grails.web.servlet.GrailsHttpServletResponse;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.IncompatibleParameterCountException;
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.codehaus.groovy.grails.web.servlet.mvc.exceptions.UnsupportedReturnValueException;
import org.springframework.context.ApplicationContext;
import org.springframework.web.servlet.ModelAndView;
public class SimpleGrailsControllerHelper implements GrailsControllerHelper {
private GrailsApplication application;
private ApplicationContext applicationContext;
private Map chainModel = Collections.EMPTY_MAP;
public SimpleGrailsControllerHelper(GrailsApplication application, ApplicationContext context) {
super();
this.application = application;
this.applicationContext = context;
}
/* (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);
}
/* (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);
}
/* (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( GrailsControllerClass controllerClass,Object returnValue,String closurePropertyName, String viewName) {
boolean viewNameBlank = (viewName == null || viewName.length() == 0);
// reset the metaclass
if (returnValue == null) {
if (viewNameBlank) {
return null;
} else {
return new ModelAndView(viewName);
}
} else if (returnValue instanceof Map) {
// remove any Proxy wrappers and set the adaptee as the value
removeProxiesFromModelObjects((Map)returnValue);
if (viewNameBlank) {
throw new NoViewNameDefinedException("Map instance returned by and no view name specified for closure on property [" + closurePropertyName + "] in controller [" + controllerClass.getFullName() + "]!");
} else {
Map returnMap = (Map)returnValue;
if(!this.chainModel.isEmpty()) {
this.chainModel.putAll( returnMap );
return new ModelAndView(viewName, this.chainModel);
}
else {
return new ModelAndView(viewName, returnMap);
}
}
} 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()) {
this.chainModel.putAll(modelMap);
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 [" + controllerClass.getFullName() + "]!");
} else {
modelAndView.setViewName(viewName);
}
}
return modelAndView;
}
throw new UnsupportedReturnValueException("Return value [" + returnValue + "] is not supported for closure property [" + closurePropertyName + "] in controller [" + 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 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.startsWith("/"))
uri = '/' + uri;
if(uri.endsWith("/"))
uri = uri.substring(0,uri.length() - 2);
// Step 2: lookup the controller in the application.
GrailsControllerClass controllerClass = getControllerClassByURI(uri);
if (controllerClass == null) {
throw new UnknownControllerException("No controller found for URI [" + uri + "]!");
}
//String controllerName = WordUtils.uncapitalize(controllerClass.getName());
// Step 3: load controller from application context.
GroovyObject controller = controllerClass.newInstance();
// Step 4: get closure property name for URI.
String closurePropertyName = controllerClass.getClosurePropertyName(uri);
if (closurePropertyName == null) {
throw new NoClosurePropertyForURIException("Could not find closure property for URI [" + uri + "] for controller [" + controllerClass.getFullName() + "]!");
}
// Step 5: get the view name for this URI.
String viewName = controllerClass.getViewByURI(uri);
// Step 6: get closure from closure property
Closure closure = (Closure)controller.getProperty(closurePropertyName);
// Step 7: process the action
Object returnValue = handleAction( controller,closure,request,response,params );
// Step 8: determine return value type and handle accordingly
return handleActionResponse(controllerClass,returnValue,closurePropertyName,viewName);
}
public void setChainModel(Map model) {
this.chainModel = model;
}
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) {
// Step 3a: Configure a proxy interceptor for controller dynamic methods for this request
try {
ControllerDynamicMethodsInterceptor interceptor;
if(!controller.getMetaClass().getClass().equals( PropertyAccessProxyMetaClass.class )) {
interceptor = new ControllerDynamicMethodsInterceptor(controller,this,request,response);
}
else {
ProxyMetaClass pmc = (ProxyMetaClass)controller.getMetaClass();
interceptor = (ControllerDynamicMethodsInterceptor)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.getValue();
chainPropertyModel.putAll( this.chainModel );
this.chainModel = chainPropertyModel;
}
}
}
catch(IntrospectionException ie) {
throw new ControllerExecutionException("Error creating dynamic controller methods for controller ["+controller.getClass()+"]: " + ie.getMessage(), ie);
}
// Step 7: determine argument count and execute.
Object returnValue = null;
if (action.getParameterTypes().length == 1) {
// closure may have zero or one parameter, we cannot be sure.
returnValue = action.call(new GrailsHttpServletRequest(request));
} else if (action.getParameterTypes().length == 2) {
returnValue = action.call(new Object[] { new GrailsHttpServletRequest(request), new GrailsHttpServletResponse(response) });
} else {
throw new IncompatibleParameterCountException("Closure on property [" + action + "] in [" + controller.getClass() + "] has an incompatible parameter count [" + action.getParameterTypes().length + "]! Supported values are 0 and 2.");
}
return returnValue;
}
}