(Quick Reference)

7.6 Filters - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari

Version: 2.3.8

7.6 Filters

Although Grails controllers support fine grained interceptors, these are only really useful when applied to a few controllers and become difficult to manage with larger applications. Filters on the other hand can be applied across a whole group of controllers, a URI space or to a specific action. Filters are far easier to plugin and maintain completely separately to your main controller logic and are useful for all sorts of cross cutting concerns such as security, logging, and so on.

7.6.1 Applying Filters

To create a filter create a class that ends with the convention Filters in the grails-app/conf directory. Within this class define a code block called filters that contains the filter definitions:

class ExampleFilters {
   def filters = {
        // your filters here
   }
}

Each filter you define within the filters block has a name and a scope. The name is the method name and the scope is defined using named arguments. For example to define a filter that applies to all controllers and all actions you can use wildcards:

sampleFilter(controller:'*', action:'*') {
  // interceptor definitions
}

The scope of the filter can be one of the following things:

  • A controller and/or action name pairing with optional wildcards
  • A URI, with Ant path matching syntax

Filter rule attributes:

  • controller - controller matching pattern, by default * is replaced with .* and a regex is compiled
  • controllerExclude - controller exclusion pattern, by default * is replaced with .* and a regex is compiled
  • action - action matching pattern, by default * is replaced with .* and a regex is compiled
  • actionExclude - action exclusion pattern, by default * is replaced with .* and a regex is compiled
  • regex (true/false) - use regex syntax (don't replace '*' with '.*')
  • uri - a uri to match, expressed with as Ant style path (e.g. /book/**)
  • uriExclude - a uri pattern to exclude, expressed with as Ant style path (e.g. /book/**)
  • find (true/false) - rule matches with partial match (see java.util.regex.Matcher.find())
  • invert (true/false) - invert the rule (NOT rule)

Some examples of filters include:

  • All controllers and actions

all(controller: '*', action: '*') {

}

  • Only for the BookController

justBook(controller: 'book', action: '*') {

}

  • All controllers except the BookController

notBook(controller: 'book', invert: true) {

}

  • All actions containing 'save' in the action name

saveInActionName(action: '*save*', find: true) {

}

  • All actions starting with the letter 'b' except for actions beginning with the phrase 'bad*'

actionBeginningWithBButNotBad(action: 'b*', actionExclude: 'bad*', find: true) {

}

  • Applied to a URI space

someURIs(uri: '/book/**') {

}

  • Applied to all URIs

allURIs(uri: '/**') {

}

In addition, the order in which you define the filters within the filters code block dictates the order in which they are executed. To control the order of execution between Filters classes, you can use the dependsOn property discussed in filter dependencies section.

Note: When exclude patterns are used they take precedence over the matching patterns. For example, if action is 'b*' and actionExclude is 'bad*' then actions like 'best' and 'bien' will have that filter applied but actions like 'bad' and 'badlands' will not.

7.6.2 Filter Types

Within the body of the filter you can then define one or several of the following interceptor types for the filter:
  • before - Executed before the action. Return false to indicate that the response has been handled that that all future filters and the action should not execute
  • after - Executed after an action. Takes a first argument as the view model to allow modification of the model before rendering the view
  • afterView - Executed after view rendering. Takes an Exception as an argument which will be non-null if an exception occurs during processing. Note: this Closure is called before the layout is applied.

For example to fulfill the common simplistic authentication use case you could define a filter as follows:

class SecurityFilters {
   def filters = {
       loginCheck(controller: '*', action: '*') {
           before = {
              if (!session.user && !actionName.equals('login')) {
                  redirect(action: 'login')
                  return false
               }
           }
       }
   }
}

Here the loginCheck filter uses a before interceptor to execute a block of code that checks if a user is in the session and if not redirects to the login action. Note how returning false ensure that the action itself is not executed.

Here's a more involved example that demonstrates all three filter types:

import java.util.concurrent.atomic.AtomicLong

class LoggingFilters {

private static final AtomicLong REQUEST_NUMBER_COUNTER = new AtomicLong() private static final String START_TIME_ATTRIBUTE = 'Controller__START_TIME__' private static final String REQUEST_NUMBER_ATTRIBUTE = 'Controller__REQUEST_NUMBER__'

def filters = {

logFilter(controller: '*', action: '*') {

before = { if (!log.debugEnabled) return true

long start = System.currentTimeMillis() long currentRequestNumber = REQUEST_NUMBER_COUNTER.incrementAndGet()

request[START_TIME_ATTRIBUTE] = start request[REQUEST_NUMBER_ATTRIBUTE] = currentRequestNumber

log.debug "preHandle request #$currentRequestNumber : " + "'$request.servletPath'/'$request.forwardURI', " + "from $request.remoteHost ($request.remoteAddr) " + " at ${new Date()}, Ajax: $request.xhr, controller: $controllerName, " + "action: $actionName, params: ${new TreeMap(params)}"

return true }

after = { Map model ->

if (!log.debugEnabled) return true

long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]

def msg = "postHandle request #$requestNumber: end ${new Date()}, " + "controller total time ${end - start}ms" if (log.traceEnabled) { log.trace msg + "; model: $model" } else { log.debug msg } }

afterView = { Exception e ->

if (!log.debugEnabled) return true

long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]

def msg = "afterCompletion request #$requestNumber: " + "end ${new Date()}, total time ${end - start}ms" if (e) { log.debug "$msg \n\texception: $e.message", e } else { log.debug msg } } } } }

In this logging example we just log various request information, but note that the model map in the after filter is mutable. If you need to add or remove items from the model map you can do that in the after filter.

7.6.3 Variables and Scopes

Filters support all the common properties available to controllers and tag libraries, plus the application context:

However, filters only support a subset of the methods available to controllers and tag libraries. These include:

  • redirect - For redirects to other controllers and actions
  • render - For rendering custom responses

7.6.4 Filter Dependencies

In a Filters class, you can specify any other Filters classes that should first be executed using the dependsOn property. This is used when a Filters class depends on the behavior of another Filters class (e.g. setting up the environment, modifying the request/session, etc.) and is defined as an array of Filters classes.

Take the following example Filters classes:

class MyFilters {
    def dependsOn = [MyOtherFilters]

def filters = { checkAwesome(uri: "/*") { before = { if (request.isAwesome) { // do something awesome } } }

checkAwesome2(uri: "/*") { before = { if (request.isAwesome) { // do something else awesome } } } } }

class MyOtherFilters {
    def filters = {
        makeAwesome(uri: "/*") {
            before = {
                request.isAwesome = true
            }
        }
        doNothing(uri: "/*") {
            before = {
                // do nothing
            }
        }
    }
}

MyFilters specifically dependsOn MyOtherFilters. This will cause all the filters in MyOtherFilters whose scope matches the current request to be executed before those in MyFilters. For a request of "/test", which will match the scope of every filter in the example, the execution order would be as follows:

  • MyOtherFilters - makeAwesome
  • MyOtherFilters - doNothing
  • MyFilters - checkAwesome
  • MyFilters - checkAwesome2

The filters within the MyOtherFilters class are processed in order first, followed by the filters in the MyFilters class. Execution order between Filters classes are enabled and the execution order of filters within each Filters class are preserved.

If any cyclical dependencies are detected, the filters with cyclical dependencies will be added to the end of the filter chain and processing will continue. Information about any cyclical dependencies that are detected will be written to the logs. Ensure that your root logging level is set to at least WARN or configure an appender for the Grails Filters Plugin (org.codehaus.groovy.grails.plugins.web.filters.FiltersGrailsPlugin) when debugging filter dependency issues.