Grails HTTP caching annotation

This simple example shows, how we can build mini framework, that will allow use HTTP response caching header, by marking controller action with custom annotation. Lets start from defining annotation:

  1. @Target([ElementType.FIELD, ElementType.METHOD])
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface WithHttpCache {
  5. CacheFor value()
  6. }

and enumeration that will allow us setup length of caching in convenient way:

  1. enum CacheFor {
  2.  
  3. ONE_HOUR(3600000),
  4. ONE_DAY(ONE_HOUR.value * 7),
  5. ONE_WEEK(ONE_DAY.value * 7),
  6. ONE_MONTH(ONE_WEEK.value * 4),
  7. ONE_YEAR(ONE_MONTH.value * 12)
  8.  
  9.  
  10. CacheFor(long value) {
  11. this.value = value
  12. }
  13. }

Now we need to build class, that base on name of controller and action, will return time of caching, if annotation was used for specific place:

  1. class WithHttpCacheService {
  2.  
  3. private static final String DEFAULT_ACTION = 'index'
  4.  
  5. private static final Map<String, Long> CACHE = [:]
  6.  
  7. @Autowired
  8. GrailsApplication grailsApplication
  9.  
  10. public void init(){
  11. grailsApplication.controllerClasses.each {controller ->
  12. final List elements = []
  13. elements.addAll(controller.clazz.declaredFields)
  14. elements.addAll(controller.clazz.methods)
  15.  
  16. elements.each { element ->
  17. final WithHttpCache annotation = element.getAnnotation(WithHttpCache)
  18. if (annotation) {
  19. final String key = buildKey(controller.name, element.name)
  20. CACHE[key] = annotation.value().value
  21. }
  22. }
  23. }
  24. }
  25.  
  26. public Long getCacheTime(final String controllerName, final String methodName){
  27. final String key = buildKey(controllerName, methodName ?: DEFAULT_ACTION)
  28. return CACHE[key]
  29. }
  30.  
  31. private String buildKey(final String controllerName, final String methodName){
  32. return "$controllerName#$methodName".toLowerCase()
  33. }
  34. }

This class need to be registered as a Spring bean, and initialized in bootstrap:

  1. class BootStrap {
  2.  
  3. def withHttpCacheService
  4.  
  5. def init = { servletContext ->
  6. withHttpCacheService.init()
  7. }
  8. def destroy = {
  9. }
  10. }

At the end, we need to build filter, in which we will setup caching headers, if for specific controller+action combination, WithHttpCacheService returns valid caching value:

  1. class WithHttpCacheFilters {
  2.  
  3. WithHttpCacheService withHttpCacheService
  4.  
  5. def filters = {
  6. all(controller:'*', action:'*') {
  7. before = {
  8. final long cacheTime = withHttpCacheService.getCacheTime(controllerName, actionName)
  9. if (cacheTime){
  10. long current = System.currentTimeMillis();
  11. long expires = current + cacheTime;
  12. int maxAge = Math.round(cacheTime / 1000)
  13. response.addHeader('Cache-Control', 'Public')
  14. response.addIntHeader('max-age', maxAge);
  15. response.addDateHeader('Expires', expires);
  16. response.addDateHeader('Last-Modified', current);
  17. }
  18. }
  19. }
  20. }
  21. }

Example of usage:

  1. class TestController {
  2.  
  3. @WithHttpCache(CacheFor.ONE_WEEK)
  4. def index = {
  5. ...
  6. }
  7. }

Powered by WordPress with GimpStyle Theme design by Horacio Bella.
Entries and comments feeds. Valid XHTML and CSS.