All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.artefact.Controller.groovy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2023 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
 *
 *      https://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 grails.artefact

import java.lang.reflect.Method

import jakarta.servlet.ServletRequest
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse

import groovy.transform.CompileStatic
import groovy.transform.Generated
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.runtime.InvokerHelper
import org.springframework.beans.factory.config.AutowireCapableBeanFactory
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpMethod
import org.springframework.validation.BindingResult
import org.springframework.validation.Errors
import org.springframework.validation.ObjectError
import org.springframework.web.context.ContextLoader
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.servlet.ModelAndView

import grails.artefact.controller.support.RequestForwarder
import grails.artefact.controller.support.ResponseRedirector
import grails.artefact.controller.support.ResponseRenderer
import grails.core.GrailsControllerClass
import grails.databinding.DataBindingSource
import grails.databinding.SimpleMapDataBindingSource
import grails.util.GrailsClassUtils
import grails.util.GrailsMetaClassUtils
import grails.web.api.ServletAttributes
import grails.web.api.WebAttributes
import grails.web.databinding.DataBinder
import grails.web.databinding.DataBindingUtils

import org.grails.compiler.web.ControllerActionTransformer
import org.grails.core.artefact.DomainClassArtefactHandler
import org.grails.datastore.mapping.model.config.GormProperties
import org.grails.plugins.web.api.MimeTypesApiSupport
import org.grails.plugins.web.controllers.ControllerExceptionHandlerMetaData
import org.grails.plugins.web.servlet.mvc.InvalidResponseHandler
import org.grails.plugins.web.servlet.mvc.ValidResponseHandler
import org.grails.web.servlet.mvc.GrailsWebRequest
import org.grails.web.servlet.mvc.SynchronizerTokensHolder
import org.grails.web.servlet.mvc.TokenResponseHandler
import org.grails.web.util.GrailsApplicationAttributes

/**
 * Classes that implement the {@link Controller} trait are automatically treated as web controllers in a Grails application
 *
 * @author Jeff Brown
 * @author Graeme Rocher
 *
 * @since 3.0
 *
 */
@CompileStatic
trait Controller implements ResponseRenderer, ResponseRedirector, RequestForwarder, DataBinder, WebAttributes, ServletAttributes {

    private final MimeTypesApiSupport mimeTypesSupport = new MimeTypesApiSupport()

    /**
     * 

The withFormat method is used to allow controllers to handle different types of * request formats such as HTML, XML and so on. Example usage:

* *
     * 
     *    withFormat {
     *        html { render "html" }
     *        xml { render "xml}
     *    }
     * 
     * 
* * @param callable * @return The result of the closure execution selected */ @Generated def withFormat(Closure callable) { HttpServletResponse response = GrailsWebRequest.lookup().currentResponse mimeTypesSupport.withFormat((HttpServletResponse) response, callable) } /** * Sets a response header for the given name and value * * @param headerName The header name * @param headerValue The header value */ @Generated void header(String headerName, headerValue) { if (headerValue != null) { HttpServletResponse response = getResponse() response?.setHeader headerName, headerValue.toString() } } /** * Binds data for the given type to the given collection from the request * * @param targetType The target type * @param collectionToPopulate The collection to populate * @param request The request */ @Generated void bindData(Class targetType, Collection collectionToPopulate, ServletRequest request) { DataBindingUtils.bindToCollection targetType, collectionToPopulate, request } /** * Return true if there are an errors * @return true if there are errors */ @Generated boolean hasErrors() { getErrors()?.hasErrors() } /** * Sets the errors instance of the current controller * * @param errors The error instance */ @Generated void setErrors(Errors errors) { def webRequest = currentRequestAttributes() setErrorsInternal(webRequest, errors) } /** * Obtains the errors instance for the current controller * * @return The Errors instance */ @Generated Errors getErrors() { def webRequest = currentRequestAttributes() getErrorsInternal(webRequest) } /** * Obtains the ModelAndView for the currently executing controller * * @return The ModelAndView */ @Generated ModelAndView getModelAndView() { (ModelAndView) currentRequestAttributes().getAttribute(GrailsApplicationAttributes.MODEL_AND_VIEW, 0) } /** * Sets the ModelAndView of the current controller * * @param mav The ModelAndView */ @Generated void setModelAndView(ModelAndView mav) { currentRequestAttributes().setAttribute(GrailsApplicationAttributes.MODEL_AND_VIEW, mav, 0) } /** * Returns the URI of the currently executing action * * @return The action URI */ @Generated String getActionUri() { "/${getControllerName()}/${getActionName()}" } /** * Returns the URI of the currently executing controller * @return The controller URI */ @Generated String getControllerUri() { "/${getControllerName()}" } /** * Obtains a URI of a template by name * * @param name The name of the template * @return The template URI */ @Generated String getTemplateUri(String name) { getGrailsAttributes().getTemplateUri(name, getRequest()) } /** * Obtains a URI of a view by name * * @param name The name of the view * @return The template URI */ @Generated String getViewUri(String name) { getGrailsAttributes().getViewUri(name, getRequest()) } /** * Redirects for the given arguments. * * @param argMap The arguments * @return null */ @Generated void redirect(Map argMap) { if (argMap.isEmpty()) { throw new IllegalArgumentException("Invalid arguments to method 'redirect': $argMap") } GrailsWebRequest webRequest = (GrailsWebRequest) RequestContextHolder.currentRequestAttributes() if (this instanceof GroovyObject) { GroovyObject controller = (GroovyObject) this // if there are errors add it to the list of errors Errors controllerErrors = getErrorsInternal(webRequest) Errors errors = (Errors) argMap.get(GormProperties.ERRORS) if (controllerErrors != null && errors != null) { controllerErrors.addAllErrors errors } else { setErrorsInternal webRequest, errors } def action = argMap.get(GrailsControllerClass.ACTION) if (action != null) { argMap.put(GrailsControllerClass.ACTION, action.toString()) } if (!argMap.containsKey(GrailsControllerClass.NAMESPACE_PROPERTY)) { // this could be made more efficient if we had a reference to the GrailsControllerClass object, which // has the namespace property accessible without needing reflection argMap.put GrailsControllerClass.NAMESPACE_PROPERTY, GrailsClassUtils.getStaticFieldValue(controller.getClass(), GrailsControllerClass.NAMESPACE_PROPERTY) } } super.redirect(argMap) } /** * Used the synchronizer token pattern to avoid duplicate form submissions * * @param callable The closure to execute * @return The result of the closure execution */ @Generated TokenResponseHandler withForm(Closure callable) { withForm getWebRequest(), callable } /** *

Main entry point, this method will check the request for the necessary TOKEN and if it is valid * will call the passed closure. * *

For an invalid response an InvalidResponseHandler is returned which will invoke the closure passed * to the handleInvalid method. The idea here is to allow code like: * *


     * withForm {
     *   // handle valid form submission
     * }.invalidToken {
     *    // handle invalid form submission
     * }
     * 
*/ @Generated TokenResponseHandler withForm(GrailsWebRequest webRequest, Closure callable) { TokenResponseHandler handler if (isTokenValid(webRequest)) { resetToken(webRequest) handler = new ValidResponseHandler(callable?.call()) } else { handler = new InvalidResponseHandler() } webRequest.request.setAttribute(TokenResponseHandler.KEY, handler) handler } /** * Checks whether the token in th request is valid. * * @param request The servlet request */ private synchronized boolean isTokenValid(GrailsWebRequest webRequest) { HttpServletRequest request = webRequest.getCurrentRequest() SynchronizerTokensHolder tokensHolderInSession = (SynchronizerTokensHolder) request.getSession(false)?.getAttribute( SynchronizerTokensHolder.HOLDER) if (!tokensHolderInSession) { return false } String tokenInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_KEY] if (!tokenInRequest) { return false } String urlInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_URI] if (!urlInRequest) { return false } try { return tokensHolderInSession.isValid(urlInRequest, tokenInRequest) } catch (IllegalArgumentException ignored) { return false } } /** * Resets the token in the request */ private synchronized resetToken(GrailsWebRequest webRequest) { HttpServletRequest request = webRequest.getCurrentRequest() SynchronizerTokensHolder tokensHolderInSession = (SynchronizerTokensHolder) request.getSession(false)?.getAttribute( SynchronizerTokensHolder.HOLDER) String urlInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_URI] String tokenInRequest = webRequest.params[SynchronizerTokensHolder.TOKEN_KEY] if (urlInRequest && tokenInRequest) { tokensHolderInSession.resetToken(urlInRequest, tokenInRequest) } if (tokensHolderInSession.isEmpty()) { request.getSession(false)?.removeAttribute(SynchronizerTokensHolder.HOLDER) } } @Generated static ApplicationContext getStaticApplicationContext() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes() if (!(requestAttributes instanceof GrailsWebRequest)) { return ContextLoader.getCurrentWebApplicationContext() } ((GrailsWebRequest) requestAttributes).getApplicationContext() } /** * Initializes a command object. * * If type is a domain class and the request body or parameters include an id, the id is used to retrieve * the command object instance from the database, otherwise the no-arg constructor on type is invoke. * If an attempt is made to retrieve the command object instance from the database and no corresponding * record is found, null is returned. * The command object is then subjected to data binding and dependency injection before being returned. * * @param type The type of the command object * @return the initialized command object or null if the command object is a domain class, the body or * parameters included an id and no corresponding record was found in the database. */ @Generated def initializeCommandObject(Class type, String commandObjectParameterName) throws Exception { HttpServletRequest request = getRequest() def commandObjectInstance = null try { DataBindingSource dataBindingSource = DataBindingUtils .createDataBindingSource( getGrailsApplication(), type, request) DataBindingSource commandObjectBindingSource = getCommandObjectBindingSourceForPrefix( commandObjectParameterName, dataBindingSource) def entityIdentifierValue = null boolean isDomainClass if (GroovyObject.isAssignableFrom(type)) { isDomainClass = ClassHelper.make(type).implementsInterface(ClassHelper.make('grails.artefact.DomainClass')) } else { isDomainClass = DomainClassArtefactHandler .isDomainClass(type) } if (isDomainClass) { entityIdentifierValue = commandObjectBindingSource.getIdentifierValue() if (entityIdentifierValue == null) { GrailsWebRequest webRequest = GrailsWebRequest.lookup(request) entityIdentifierValue = webRequest?.getParams()?.getIdentifier() } } if (entityIdentifierValue instanceof String) { entityIdentifierValue = ((String) entityIdentifierValue).trim() if (entityIdentifierValue == '' || entityIdentifierValue == 'null') { entityIdentifierValue = null } } HttpMethod requestMethod = HttpMethod.valueOf(request.getMethod()) if (entityIdentifierValue != null) { try { commandObjectInstance = InvokerHelper.invokeStaticMethod(type, 'get', entityIdentifierValue) } catch (Exception e) { Errors errors = getErrors() if (errors != null) { errors.reject("${getClass().getName()}.commandObject.${commandObjectParameterName}.error", e.getMessage()) } } } else if (requestMethod == HttpMethod.POST || !isDomainClass) { commandObjectInstance = type.newInstance() } if (commandObjectInstance != null && commandObjectBindingSource != null) { boolean shouldDoDataBinding if (entityIdentifierValue != null) { switch (requestMethod) { case HttpMethod.PATCH: case HttpMethod.POST: case HttpMethod.PUT: shouldDoDataBinding = true break default: shouldDoDataBinding = false } } else { shouldDoDataBinding = true } if (shouldDoDataBinding) { bindData(commandObjectInstance, commandObjectBindingSource, Collections.emptyMap(), null) } } } catch (Exception e) { Method exceptionHandlerMethodFor = getExceptionHandlerMethodFor(e.getClass()) if (exceptionHandlerMethodFor != null) { throw e } commandObjectInstance = type.newInstance() Object o = GrailsMetaClassUtils.invokeMethodIfExists(commandObjectInstance, 'getErrors') if (o instanceof BindingResult) { BindingResult errors = (BindingResult) o String msg = "Error occurred initializing command object [${commandObjectParameterName}]. ${e.getMessage()}" ObjectError error = new ObjectError(commandObjectParameterName, msg) errors.addError(error) } } if (commandObjectInstance != null) { ApplicationContext applicationContext = getApplicationContext() AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory() autowireCapableBeanFactory.autowireBeanProperties(commandObjectInstance, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false) } commandObjectInstance } /** * Return a DataBindingSource for a command object which has a parameter name matching the specified prefix. * If params include something like widget.name=Thing and prefix is widget then the returned binding source * will include name=thing, not widget.name=Thing. * * @param prefix The parameter name for the command object * @param params The original binding source associated with the request * @return The binding source suitable for binding to a command object with a parameter name matching the specified prefix. */ private DataBindingSource getCommandObjectBindingSourceForPrefix(String prefix, DataBindingSource params) { DataBindingSource commandParams = params if (params != null && prefix != null) { def innerValue = params[prefix] if (innerValue instanceof DataBindingSource) { commandParams = (DataBindingSource) innerValue } else if (innerValue instanceof Map) { commandParams = new SimpleMapDataBindingSource(innerValue) } } commandParams } @Generated @SuppressWarnings('unchecked') Method getExceptionHandlerMethodFor(final Class exceptionType) throws Exception { if (!Exception.isAssignableFrom(exceptionType)) { throw new IllegalArgumentException("exceptionType [${exceptionType.getName()}] argument must be Exception or a subclass of Exception") } Method handlerMethod List exceptionHandlerMetaDataInstances = (List) GrailsClassUtils.getStaticFieldValue(this.getClass(), ControllerActionTransformer.EXCEPTION_HANDLER_META_DATA_FIELD_NAME) if (exceptionHandlerMetaDataInstances) { // find all of the handler methods which could accept this exception type List matches = (List) exceptionHandlerMetaDataInstances.findAll { ControllerExceptionHandlerMetaData cemd -> cemd.exceptionType.isAssignableFrom(exceptionType) } if (matches.size() > 0) { ControllerExceptionHandlerMetaData theOne = matches.get(0) // if there are more than 1, find the one that is farthest down the inheritance hierarchy for (int i = 1; i < matches.size(); i++) { ControllerExceptionHandlerMetaData nextMatch = matches.get(i) if (theOne.getExceptionType().isAssignableFrom(nextMatch.getExceptionType())) { theOne = nextMatch } } handlerMethod = this.getClass().getMethod(theOne.getMethodName(), theOne.getExceptionType()) } } handlerMethod } private Errors getErrorsInternal(GrailsWebRequest webRequest) { (Errors) webRequest.getAttribute(GrailsApplicationAttributes.ERRORS, 0) } private setErrorsInternal(GrailsWebRequest webRequest, Errors errors) { webRequest.setAttribute(GrailsApplicationAttributes.ERRORS, errors, 0) } @Generated @Override String toString() { getControllerClass()?.fullName } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy