Monday, June 20, 2011

Grails - Spring Security with Spring Cache: Caching content per user [Updated]

In my previous post I explained how to customize the Spring Cache plug in so we could cache content considering what user is logged in or not. However, that code was written using the version 1.2.1 of the plug in and if you want to upgrade to the latest version, which currently is 1.3.1, then a few changes need to happen in the code for this to work. Because of this I am going to explain what these changes are so this customization continues working for you.

In the original example I mentioned that we needed to extend the class MimeTypeAwareKeyGenerator, we used this class specifically because we need to handle different content types. However, in the new version of the plug in this class no longer exists, what we have now is a class called WebContentKeyGenerator that extends DefaultKeyGenerator.

The WebContentKeyGenerator is a multipurpose implementation of the KeyGenerator interface. The different purposes of the key generator are managed through several boolean properties, all false by default, these properties are: [1]
  • ajax: If set to true then keys will differ depending on the presence or absence of the X-Requested-With request header so AJAX requests will be cached separately from regular requests. This is useful when you have an action that renders different content when it is requested via AJAX.
  • contentType: If true keys will differ depending on the requested content format as determined by the format meta-property on HttpServletRequest. This is useful when you use content negotiation in a request so that responses with different formats are cached separately.
  • requestMethod: If true keys will differ depending on the request HTTP method. This is useful for some RESTful controllers (although if different request methods are mapped to different actions you do not need to use this mechanism). GET and HEAD requests are considered the same for the purposes of key generation.

So our new key generator class should look like this:
public class MimeTypeAndAuthenticationAwareKeyGenerator extends WebContentKeyGenerator {

  @Override
  protected void generateKeyInternal(CacheKeyBuilder builder, ContentCacheParameters context) {
    super.generateKeyInternal(builder, context)
    def springSecurityService = ApplicationHolder.application.mainContext.getBean('springSecurityService')
    if (springSecurityService?.isLoggedIn()) {
      builder << "authUserId=${springSecurityService.principal.getId()}".toString()
    }
  }

}
As you can see the code is still very similar, but besides extending a different class, the signature of the generateKeyInternal method has also changed, instead of receiving a FilterContext object as a second parameter, you need to receive a ContentCacheParameters object.

To configure the plug in to use our class we used the Config.groovy configuration to tell the springcacheFilter what key generator to use. However, this needs to be done differently now, you have two options:

1. Setting the key generator by action: You can specify your key generator for a specific action by adding the keyGenerator element to the @Cacheable annotation specifying the name of a Spring bean that implements the KeyGenerator interface, something like:

@Cacheable(cache = "albumControllerCache", keyGenerator = "mySpecialKeyGenerator")
def doSomething = {
    // …
}

2. Overriding the default key generator: You can also override the default key generator instance in the resources.groovy file, which is named springcacheDefaultKeyGenerator. You can do something like:
springcacheDefaultKeyGenerator(MimeTypeAndAuthenticationAwareKeyGenerator) {
     ajax = true
     contentType = true
    }

As you can see we now have different options that allow us to have more control on how caching is managed for different actions giving us more flexibility.

[1] Taken from Spring Cache Plug in documentation.