Annotations in Grails Controllers

Grails has support for enforcing that a controller method is called via GET or POST using the ‘allowedMethods’ map, e.g.

def allowedMethods = [delete: 'POST', save: 'POST', update: 'POST']

This works well but is somewhat restrictive. Grails decides how to handle a caller making a GET request when POST is required and vice versa – it sends a 405 error.

But if you want some other behavior in this case, or want to check other preconditions (other than security checks, those are much better left to proper security frameworks) then you need to implement the checks yourself, and Grails filters make the most sense.

That leaves the question of how to declare the rules. The “Grails Way”tm would be to define a Map, List, or a DSL block in the controller class that contains all of the rules for the class, but I think that annotations are the way to go here. Annotations have limitations; they can get way out of control. I converted a fairly complicated Spring/Hibernate application that used hbm files to be annotation-based, and ended up with about as much code in the annotations as I replaced in the XML – not much of a reduction.


Recently I was working on creating a site map and some automated security testing so I went through all of the controllers and noted their urls. I tested each one with a browser, issuing a GET request for each. Many failed, assuming that they’d be called via POST with required parameters that were missing, or failed less badly assuming they’re be called via Ajax and only sending a partial page or JSON response. So I created some annotations, RequiresPost and RequiresAjax to guard these. I’m not sure yet what the proper handling of using the wrong request method or type should be, but this conveniently puts that handling code in one place (and separates it from the controllers as a cross-cutting concern).

In this case the annotations are pretty simple, and are declared either at the method or class level. And since they’re declared where they’re used, they’re highly self-documenting – it’s clear looking at the method which rules apply, or that none apply.

Here are the annotations:

package com.foo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates that a service method (or entire controller) requires
 * being called using POST.
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPost {

  /**
   * Whether to display an error or be silent (default).
   * @return  <code>true</code> if an error should be shown
   */
  boolean error() default false;
}
package com.foo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Indicates that a service method (or entire controller) requires
 * being called by Ajax.
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresAjax {

  /**
   * Whether to display an error or be silent (default).
   * @return  <code>true</code> if an error should be shown
   */
  boolean error() default false;
}

and a couple of sample controllers using them. First, one that requires that all actions be called via Ajax:

@RequiresAjax
class PopupController {

  def viewFoo = {
    def foo = Foo.get(params.id)
    render foo as JSON
  }

  def viewThing = {
    def thing = Thing.get(params.id)
    [thing: thing]
  }
}

and another that requires Post or Ajax on a per-action basis:

class SignupController {

  def index = {
    ...
  }

  /**
   * Ajax call to monitor upload status.
   */
  @RequiresAjax
  def uploadInfo = {
    def data = ...
    render data as JSON
  }

  /**
   * Ajax post to perform search.
   */
  @RequiresAjax @RequiresPost
  def searchResults = {
    def results = ...
    render results as JSON
  }
}

So how do we enforce the behavior specified by the annotations? With a Grails filter:

import com.foo.annotation.ControllerAnnotationHelper

class SiteFilters {

  def filters = {

    ajaxAndPostRequired(controller: '*', action:'*') {
      before = {
        boolean ok = true

        if (!request.xhr &&
            ControllerAnnotationHelper.requiresAjax(controllerName, actionName)) {
          ok = false
        }

        if (ok) {
          if (!request.post &&
              ControllerAnnotationHelper.requiresPost(controllerName, actionName)) {
            ok = false
          }
        }

        if (!ok) {
          render text: '', template: null, contentType: 'text/plain'
          return false
        }

        return true
      }
    }
  }
}

This checks in the before() closure whether POST is required and used and whether Ajax is required and used. If either condition isn’t met, it renders a blank page and returns false, which indicates that no further processing should happen.

This isn’t necessarily the final implementation of this check, but changing site-wide behavior only requires changing this single file. Also note that the annotations support an error attribute indicating whether an error should be reported; this is currently not implemented.


One other missing piece is how to detect Ajax. Most of the popular Ajax frameworks have standardized on indicating that a request was made via Ajax by setting the X-Requested-With request header to XMLHttpRequest, so we can add an isXhr() method to the HttpServletRequest metaclass:

if (!HttpServletRequest.metaClass.hasMetaMethod('isXhr')) {
  HttpServletRequest.metaClass.isXhr = { ->
    'XMLHttpRequest' == delegate.getHeader('X-Requested-With')
  }
}

in BootStrap.groovy or in a plugin.


Finally, here’s the helper class that scans the controllers looking for annotations. This would be best in a plugin, but we currently have it implemented as a standalone class that’s triggered in BootStrap.groovy, calling init() when the application starts up. I’m intentionally using generics in type declarations, even though Groovy ignores them, to document the collection types. I’m getting increasingly frustrated with the Grails/Groovy “everything is an Object” approach that often makes it hard to figure out what parameters to send and what method return values are, but that’s another blog post …

package com.foo.annotation

import org.apache.commons.lang.WordUtils

import org.codehaus.groovy.grails.commons.ApplicationHolder as AH

class ControllerAnnotationHelper {

  private static Map<String, Map<String, List<Class>>> _actionMap = [:]
  private static Map<String, Class> _controllerAnnotationMap = [:]

  /**
   * Find controller annotation information. Called by BootStrap.init().
   */
  static void init() {

    AH.application.controllerClasses.each { controllerClass ->
      def clazz = controllerClass.clazz
      String controllerName = WordUtils.uncapitalize(controllerClass.name)
      mapClassAnnotation clazz, RequiresAjax, controllerName
      mapClassAnnotation clazz, RequiresPost, controllerName

      Map<String, List<Class>> annotatedClosures = findAnnotatedClosures(
          clazz, RequiresAjax, RequiresPost)
      if (annotatedClosures) {
        _actionMap[controllerName] = annotatedClosures
      }
    }
  }

  // for testing
  static void reset() {
    _actionMap.clear()
    _controllerAnnotationMap.clear()
  }

  // for testing
  static Map<String, Map<String, List<Class>>> getActionMap() {
    return _actionMap
  }

  // for testing
  static Map<String, Class> getControllerAnnotationMap() {
    return _controllerAnnotationMap
  }

  private static void mapClassAnnotation(clazz, annotationClass, controllerName) {
    if (clazz.isAnnotationPresent(annotationClass)) {
      def list = _controllerAnnotationMap[controllerName] ?: []
      list << annotationClass
      _controllerAnnotationMap[controllerName] = list
    }
  }

  /**
   * Check if the specified controller action requires Ajax.
   * @param controllerName  the controller name
   * @param actionName  the action name (closure name)
   */
  static boolean requiresAjax(String controllerName, String actionName) {
    return requiresAnnotation(RequiresAjax, controllerName, actionName)
  }

  /**
   * Check if the specified controller action requires POST.
   * @param controllerName  the controller name
   * @param actionName  the action name (closure name)
   */
  static boolean requiresPost(String controllerName, String actionName) {
    return requiresAnnotation(RequiresPost, controllerName, actionName)
  }

  private static boolean requiresAnnotation(Class annotationClass,
      String controllerName, String actionName) {

    // see if the controller has the annotation
    def annotations = _controllerAnnotationMap[controllerName]
    if (annotations && annotations.contains(annotationClass)) {
      return true
    }

    // otherwise check the action
    Map<String, List<Class>> controllerClosureAnnotations =
            _actionMap[controllerName] ?: [:]
    List<Class> annotationClasses = controllerClosureAnnotations[actionName]
    return annotationClasses && annotationClasses.contains(annotationClass)
  }

  private static Map<String, List<Class>> findAnnotatedClosures(
      Class clazz, Class... annotationClasses) {

    // since action closures are defined as "def foo = ..." they're
    // fields, but they end up private
    def map = [:]
    for (field in clazz.declaredFields) {
      def fieldAnnotations = []
      for (annotationClass in annotationClasses) {
        if (field.isAnnotationPresent(annotationClass)) {
          fieldAnnotations << annotationClass
        }
      }
      if (fieldAnnotations) {
        map[field.name] = fieldAnnotations
      }
    }

    return map
  }
}

This approach can help centralize request method handling, but more generically also provides some template code for defining and processing other annotations in a Grails app.

9 Responses to “Annotations in Grails Controllers”

  1. Rob Fletcher says:

    Cool post. One thing…

    [quote]I’m not sure yet what the proper handling of using the wrong request method or type should be[/quote]

    The normal way to handle this would be:
    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)

  2. Burt says:

    @Rob – I mentioned SC_METHOD_NOT_ALLOWED, that’s a 405. But I wasn’t clear – what I meant was that I’m not sure what we should do in this application to handle this. Obviously as developers we’ll POST to POST-only actions, etc. but I’m wondering how to handle the case where a user does a GET for a POST-only or Ajax-only action. A 405 error is appropriate but ugly.

  3. Darryl Pentz says:

    In your sample code above, in the annotation class, you have the line:

    @Target({ElementType.FIELD, ElementType.TYPE})

    My IDE complains about the , but is ok with a ; there. Is that a mistype on your part, or am I doing something wrong?

  4. Burt says:

    @Darryl – Semicolon doesn’t work for me in Eclipse and doesn’t compile using javac from the commandline. The JLS says that ElementValues are comma-delimited: http://java.sun.com/docs/books/jls/third_edition/html/interfaces.html#253727

  5. Darryl Pentz says:

    Hmm, figured it out. IDEA had created the @Interface class as a .groovy class and not a .java class and seems IDEA couldn’t grok it as a .groovy @Interface class. Having changed it to a .java class, adding the comma-delimitation works fine.

    My bad.

  6. Darryl Pentz says:

    Burt, on this post – which version of Grails is required at the least? Will 1.0.x work ok? Wasn’t sure which version of Grails included the Groovy version that can grok annotations.

  7. Stefan Kendall says:

    How come .findAnnotatedClosures only works in a static context? I’ve asked about this issue here:

    http://stackoverflow.com/questions/7352580/cannot-call-method-which-requires-java-lang-class-arguments/7353256#7353256

  8. apara says:

    Hi, thanks for the post, cant we add annotations to methods in controller class(eg: def list(String s){}). Is it only for Closures ? if I want to add the annotation to a method how should I change my code to with method findAnnotatedMethods() instead of findAnnotatedClosures() ?

Leave a Reply

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.