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.
Hello,
ReplyDeleteI'm trying to configure my own key generator but it is not working properly.
I have created the file /grails-app/conf/com/example/web/key/MyKeyGenerator.groovy with the following content:
package com.example.web.key
import grails.plugin.springcache.key.CacheKeyBuilder
import grails.plugin.springcache.web.ContentCacheParameters
import grails.plugin.springcache.web.key.WebContentKeyGenerator
import org.springframework.web.servlet.i18n.SessionLocaleResolver
class MyKeyGenerator extends WebContentKeyGenerator {
@Override protected void generateKeyInternal(CacheKeyBuilder builder, ContentCacheParameters context) {
super.generateKeyInternal(builder, context)
builder << "Current language: ${context.request.session[SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME]}"
}
}
And in the file resources.groovy I have the following lines:
// Place your Spring DSL code here
beans = {
myKeyGenerator(com.example.web.key.MyKeyGenerator) {
}
}
But when I try to use it in a controller I always get the following error:
2011-08-29 23:10:40,161 [http-8080-6] ERROR [/Demo].[grails] - Servlet.service() for servlet grails threw exception
java.lang.NullPointerException: Cannot invoke method generateKey() on null object
at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:77)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:45)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
at org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:32)
Please, could you tell me what I am not configuring right?
Thank you very much and regars,
Iván.
Ivan,
ReplyDeleteI think the problem is with how you are setting the generator in the resources.groovy, you are doing:
beans = {
myKeyGenerator(com.example.web.key.MyKeyGenerator) {
}
}
but it should be:
beans = {
springcacheDefaultKeyGenerator(com.example.web.key.MyKeyGenerator) {
}
}
Try changing that and let me know how it goes.
Maricel.
Thank you for your quick reply. The problem was in the @cacheable annotation. I was misspelling the name of the keyGenerator. Instead of writing "myKeyGenerator" I was writing "MyKeyGenerator" with capital 'M'. This works defining my bean as myKeyGenerator(com.example...) in resources.groovy
ReplyDelete@Cacheable(cache = "translationControllerCache", keyGenerator = "myKeyGenerator")
Your reply is working but now the @cacheable annotation must be:
@Cacheable(cache = "translationControllerCache")
Without defining the key generator.
Thanks a lot!!.
Regards, Iván.
I am glad it worked out for you Ivan and yes both options work :-)
ReplyDeleteI would really like your post ,it would really explain each and every point clearly well thanks for sharing.
ReplyDeleteChevy HHR Turbo
Hi,
ReplyDeleteI have created a sample grails project (v1.3.5) to test caching contents per user. But, it is not working as I expected. You can find more detail on the problem in below link:
https://docs.google.com/leaf?id=0B3j9inzR_LLhNDY3N2M5OTItM2Y3Mi00YmZlLTlmNTctZmFlZGRiODI0M2Ni&hl=en_US
I have uploaded the source code for this project at below location. It will be grateful if someone looks into it & let me know where I'm wrong.
https://docs.google.com/leaf?id=0B3j9inzR_LLhYjhiMmY3MDUtNTVlYi00ZTE2LWIyYTctZDAxMjQ4YTg2NTVi&hl=en_US
Thanks,
Kishore
I created MimeTypeAndAuthenticationAwareKeyGenerator.groovy in /grails-app/conf folder.
ReplyDeleteimport grails.plugin.springcache.web.key.WebContentKeyGenerator
import grails.plugin.springcache.key.CacheKeyBuilder
import grails.plugin.springcache.web.ContentCacheParameters
import org.codehaus.groovy.grails.commons.ApplicationHolder
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()
}
}
}
And added below lines in resources.groovy
beans = {
springcacheDefaultKeyGenerator(MimeTypeAndAuthenticationAwareKeyGenerator) {
ajax = true
contentType = true
}
}
Caching is working. But not per user!!!!!!!!!!
[I've shared proj source & more description in one of my thread at http://maricel-tech.blogspot.com/2011/02/grails-spring-security-with-spring.html#comment-form]
Any idea?
I'm confused where "mySpecialKeyGenerator" is defined?
ReplyDelete@Cacheable(cache = "albumControllerCache", keyGenerator = "mySpecialKeyGenerator")
Kishora,
ReplyDeleteYou have two options on how to use your custom key generator:
1. You can use it per action
@Cacheable(cache = "albumControllerCache", keyGenerator = "mySpecialKeyGenerator")
In this case you are saying that you want a given action to use a generator that you are defining that is not the default. You would need to create the class for it and define the bean in the spring resources so it can be accessed.
2. Option two is when you want your generator to override the plug in default. This what you are doing when you do:
beans = {
springcacheDefaultKeyGenerator(MimeTypeAndAuthenticationAwareKeyGenerator) {
ajax = true
contentType = true
}
}
Hope this helps!
Hi Maricel,
ReplyDeleteI have applied second method (springcacheDefaultKeyGenerator) in a sample grails project to test caching contents per user. But, it is not working as I expected. I'm stuck here. It will be great if it is possible for you take a look at the source code? I might have made a silly mistake.
Source code is shared at:
https://docs.google.com/leaf?id=0B3j9inzR_LLhYjhiMmY3MDUtNTVlYi00ZTE2LWIyYTctZDAxMjQ4YTg2NTVi&hl=en_US
Thanks,
Kishore
I can't see your code in that link, it just shows me a file to download and when I do it is unreadable. Could you post it again please?
ReplyDeleteI have uploaded again at:
ReplyDeletehttps://docs.google.com/viewer?a=v&pid=explorer&chrome=true&srcid=0B3j9inzR_LLhODg0MjBiNzItYTZkYi00OWVmLTk1MmEtYmUwY2M1YjJjMjky&hl=en_US
Thanks,
Kishore
Hi Maricel,
ReplyDeleteI guess you didn't get my last uploaded file too. If you can share your email id on kishor.ys@gmail.com, then I can send it as an attachment.
Regards,
Kishore
I reviewed the code and everything looks good with the key generator. Running the application and debugging the code I see that for some reason is not working, so what I did was I added the @Cacheable tag to the action in the controller instead of the service and that seems to work.
ReplyDeleteYou can put a breakpoint in the class MimeTypeAndAuthenticationAwareKeyGenerator and follow the execution that will take you to this class GrailsFragmentCachingFilter, in there you can verify that the keys are generated with a different value depending on the user logged in and if the page is being loaded from the cache or not.
The generator should work with the Service method as well, not sure why it is not working, I am going to look into a bit more and let you know.
I just remembered that the Service methods do not use the key generator, the key generator is used for content caching. The key for a service method is generated based on the object, the method signature and the method parameters, since in this case the method doesn't have parameters then the key is always the same.
ReplyDeleteRead this for more information about this
http://gpc.github.com/grails-springcache/docs/guide/3.%20Caching%20Service%20Methods.html
I verified it by adding a parameter to the service method, so the user name is passed from the controller to the service and it worked.
Makes sense?
Hi Miracel,
ReplyDeleteYou made my day.
It works perfectly fine!!!!!!!!!!
Thanks a lot for your time.
Kishore
class DashboardController:
ReplyDelete----------------------------------
@Cacheable(cache = "myBooksCache")
def getMyBooks = {
// list books
}
class BookController:
----------------------------------
@CacheFlush("myBooksCache")
def save = {
// save book
}
When all users opens dashboard, a cache ("myBooksCache") will be created for each user.
When a book is added by user1 (/book/save called), "myBooksCache" cache will be flushed for all the users (user2, user3..)! Expected to clear only user1's cache.
Caching content per user works fine.
Any idea how to handle flushing cache per user?
Source:
https://docs.google.com/viewer?a=v&pid=explorer&chrome=true&srcid=0B3j9inzR_LLhMDc4MmVkMDItZjgyZS00NTkxLTg5YTktYTY2NGVjN2ZhYWE4&hl=en_US
Sorry, I haven't reviewed how cache flushing per user works, but you might want to check the plug in code to see if it takes into consideration the content key when doing the flushing, I would supposed it does.
ReplyDeleteHi, very interesting post about Spring Cache and Grails, but is anyone having problems with the content cache?
ReplyDeleteImagine we have the next scenario. We have just one domain class and we implement static scaffolding and we want to cache the list method. This cache will be flush everytime we save, update or delete an item. But, if you list and then you try to create a new item without saving it and go back to the list, the application doesn't show the content correctly because the content generated in the main layout has not been cached.
It means that using springcache, the content in the layouts won't be saved? The documentation doesn't say anything about it but I feel that's the problem.
hello, your all post is very nice and understandable and working.i am happy to get your blog and solve my very soon.Grails Technology , Spring3 Technology
ReplyDelete