Using Class Level Metadata to Allow Controller Actions to Opt Out of a Filter’s Processing
You will find an instance of the very handy GrailsApplication class injected into filters, plug-ins and other types of Grails components you may need to write. GrailsApplication has a method that allows you to get at the GrailsClass that wraps Grails artefacts, such as domain objects and controllers.
As a usage example, suppose you had a controller named ‘FooController’. You would get a handle to it like this:
GrailsClass controllerClass =
grailsApplication?.
getArtefactByLogicalPropertyName("Controller", 'foo')
My understanding of GrailsClass is that it primarily serves as a means of providing class metadata in response to reflection-like queries. GrailsClass reflection support goes beyond what you’d get in the JDK. For instance, you can query a property to find out if it is static or not.
Such a handle would be useful within the context of a filter, wherein you want to activate the filter for some controller actions, while silently skipping the filter’s processing for others. One way to allow particular controller actions to opt out of filter processing is to hang some metadata off the controller class. When this metadata is introspected it can provide the list of the actions on the controller for which filter processing should be skipped.
Of course you need to get the class before you get its metadata. The preceding code fragment shows you how to do this given the controller name. The currently active controller’s name is one of the things Grails makes available to you inside the closure block that defines your filter.
As an aside I should mention an alternative method for getting a handle to the Grails class that is best avoided due to the potential brittleness of the code you write with it. This second method uses the dynamic method GrailsApplication.getControllerClass(), which takes the package qualified class name of the controller whose GrailsClase wrapper you want to fetch. The documentation for this API suggests the controller short name should be useable to fetch the controller’s class, but I haven’t had luck with this. Hopefully I will get a response to my mailing list and wiki queries on this subject.
Reflectively Accessing The Property Value That Enables Opt Out
I’m currently experimenting with the technique of hanging configuration metadata off of a class property as I’m developing a filter that I plan to make available via a plug-in (a working first cut is downloadable via this link). The filter I’m writing is designed to solve the Stale Responses To Redirected Requests problem that I wrote about in a prior post. Basically, the problem has to do with a bug in IE that makes your Ajax requests seem to do nothing because redirect requests end up getting serviced from Internet Explorer’s cache (you can read the earlier post for more details.)
My filter will stuff in cache control headers in the outgoing response of every controller action unless the controller explicitly opts of sending headers for that action. A controller opts out by defining the noCacheHeaderActions property, which returns a list of the actions for which header emission is to be skipped (or the string ‘all’ for all actions in the controller.) Controller code which uses the plug-in will look something like this:
class FooController {
public static List noCacheHeaderActions = [ 'noCacheAction' ]
def sayHello = {
render 'hello!'
}
def sayHelloNoCache {
render 'hello - with no cache control headers!'
}
Accessing the sayHello action (via something like http://localhost/foo/sayHello) will result in a response that includes cache control headers, while accessing the sayHelloNoCache action will result in a response with no such headers. The code that accesses the property ‘noCacheHeaderActions’ is shown below.
Note that before we call this code we will have used the GrailsApplication.getArtefactByLogicalPropertyName to go from controller name to ‘controllerClass’ (the GrailsClass which wraps the controller.)
/**
* @return true if the given controllerClass has defined a
* variable that disables cache header
* emission for the action with name 'actionName',
* or if cache header emission has been
* disabled for all actions. Otherwise returns
* 'false' to indicate emission of cache headers
* should NOT be skipped.
*/
private boolean skipCacheHeadersForThisAction(
GrailsClass controllerClass,
String actionName) {
logger.debug("++ skipCacheHeadersForThisAction? - " +
" checking class: {}", controllerClass)
List actions = GrailsClassUtils.
getStaticPropertyValue(
controllerClass?.clazz,
Constants.NO_CACHE_HEADER_ACTIONS)
if (actions) {
if (actions.find { it == Constants.ALL } ) {
logger.debug("no cache headers emitted " +
" for action {} - matches all", actionName)
return true
}
if (actions.find { it == actionName } ) {
logger.debug("no cache headers emitted for " +
" action {} - explicitly disabeld", actionName )
return true
}
}
return false // Don't skip emission of headers, do it !
}
A Rejected Alternative Using GrailsClass.hasProperty()
The code presented above does not represent my first attempt to use metadata to allow controller actions to opt out of processing. I initially used GrailsClass.hasProperty() and GrailsClass.getPropertyValue() and came up with the implementation below. This implementation worked, but had the disadvantage of requiring a getter to be created for the noCacheHeaderActions property. Just creating a static member variable ‘noCacheHeaderActions’ was not sufficient because the the Spring BeanWrapperImpl class that backs the GrailsClass instance fails to realize that the property exists unless it has an associated getter function. The rejected alternative looked like this:
private boolean skipCacheHeadersForThisAction(
GrailsClass controllerClass,
String actionName) {
logger.debug(... etc.
if (controllerClass?.hasProperty(
Constants.NO_CACHE_HEADER_ACTIONS)) {
List actions = controllerClass.
getPropertyValue( // requires a getter !
Constants.NO_CACHE_HEADER_ACTIONS)
if (actions.find { it == Constants.ALL } ) {
etc...
if (actions.find { it == actionName } ) {
etc...
}
return false // Don't skip emission of headers, do it !
}
And with this approach, your controller needed to implement the opt out property through a getter, like this:
class FooController {
public List getNoCacheHeaderActions() {
return [ 'noCacheAction' ]
}
def sayHello = {
render 'hello!'
}
def sayHelloNoCache {
render 'hello - with no cache control headers!'
}
Even though the GrailsClassUtils. getStaticPropertyValue() approach lets you use a simple property without a getter, it’s still less than ideal. Both approaches fail to prevent plug-in users from making two mistakes when setting up the metadata. First off, things will mysteriously not work if you mistype the name of the noCacheHeaderActions property in your controller, and secondly, you will get burned if you fail to make that property static. Good software works to prevent this type of user error, so I’m not 100% happy with this solution.
I’m now thinking that the best way to do this would be to avoid using properties hanging off the class, and instead hang my metadata off of good ‘ol Java annotations, perhaps using a scheme like this:
////
@SkipActions('sayHelloNoCache,otherSkippedAction')
class FooController {
def sayHello = {
render 'hello!'
}
def otherSkippedAction = {
render 'hello!'
}
def sayHelloNoCache {
render 'hello - with no cache control headers!'
}
We still run the risk of mistakes due to mistyping the names of actions, but I can’t think of any way to guard against that.
Downloading And Using The Plug-In
Before using the plug-in in your own projects I’d recommend that you confirm your ability to reproduce the IE caching bug that the plug-in attempts to solve, and that after you install it the problem goes away.
Before using the plug-in in your own projects I’d recommend that you confirm your ability to reproduce the IE caching bug that the plug-in attempts to solve, and that after you install it the problem goes away. I created a sample project that you can use for this purpose. This project is slightly different from the one that I presented in my prior post on the Stale Responses To Redirected Requests problem. The reproduction instructions are identical to those in the earlier post.
So, as a first step, please download the new problem demo project and follow the instructions listed in the section Reproducing the Incorrect Behavior in the prior post.
How The Demo App Reproduces the Stale Response Problem
Our approach to reproducing the problem in the new demo app is slightly different than that taken in our prior post. The main difference is that the getViaRedirect method does not simply append the text in the ‘addToResponse’ parameter to the file /result.html and issue a redirect toward the URL for /result.html. Instead we issue a redirect to another controller action ’serveFileContents’ which spits out the content of /result.html. The reason for doing this is that if we just sent back /result.html’s contents, those contents would go out with out any of the cache control headers added by our plug-in. Our plug-in only kicks in when there is a controller action that it can ‘decorate’ via emission of the caching headers.
If you keep the forgoing in mind then it should be straightforward to understand the relevant code in the new demo app, which we present below.
def getViaRedirect = {
if (params.doRedirect == "true") {
String fileSystemPath = servletContext.getRealPath("/result.html")
println "fileSystemPath is $fileSystemPath"
File responseFile = new File(fileSystemPath)
String addition = ""
if (params.addToResponse) {
addition += "\n" + params.addToResponse
println "addition is $addition"
}
responseFile.text += addition
redirect(action: 'serveFileContents')
}
else {
render("no redirecting. Here is some static text.")
}
}
// This method takes the place of the redirect directly to /result.html
//
def serveFileContents = {
String fileSystemPath = servletContext.getRealPath("/result.html")
println "serveFileContents > fileSystemPath is $fileSystemPath"
File responseFile = new File(fileSystemPath)
render responseFile.text
}
Correcting the Problem With The Filters Made Available Via our Plug-In
Download and unzip the Build Lackey Labs base-intrastructure plug-in, Next, go to the root of the directory hierarchy into which you unzipped the plugin (let’s call this directory PLUGIN-ROOT) and type
grails package plugin
Then cd to the directory where you unzipped the problem demo project (books).
Install the plugin using the following command:
grails install-plugin
PLUGIN-ROOT/base-infrastructure/grails-base-infrastructure-0.1.zip
You are now ready to try running the problem demo project with the plug-in, but first you need to make sure that you’ve cleaned out your cache. If you don’t, the newly sent cache headers won’t help you at all – IE will still end up serving stale content from whatever is hanging around in that cache. If you were running IE 8 on Vista, you’d clear your cache by clicking on the ‘Safety’ drop down menu in the upper right hand corner of your screen, then selecting Delete Browsing History, selecting all check boxes for good measure, then clicking Delete.
Now launch the problem demo app (using grails run-app). Ignore the INFO messages about trying and failing to load properties files. These messages are related to another aspect of base-intrastructure plug-in’s functionality that will be discussed in a later post.
Finally, type the following URL in IE’s address bar, and hit return:
http://localhost:8080/books/book/
getViaRedirect?doRedirect=true&addToResponse=haha
Keep typing it and vary what you specify for ‘addToResponse’ and notice the you get a different response each time, and that each response echos back what you specified for addToResponse. Problem solved.
Resources
For more information and background on plug-in development, check out these links:
Plug-in Developers Guide
Tutorial On Building Plug-in’s using Grails
Graeme Rocher’s Plug-In Architecture Presentation
Mastering Grails: Understanding plug-in
Mastering Grails: Creating a custom plug-in
























No Responses to “Using Class Level Metadata to Allow Controller Actions to Opt Out of a Filter’s Processing”