From 14f93faad7f4406699f9982f8d77235fa6bad850 Mon Sep 17 00:00:00 2001 From: Rob Fletcher Date: Tue, 8 Nov 2011 18:00:14 +0000 Subject: [PATCH 1/7] dependency updates --- .gitignore | 1 + application.properties | 1 - grails-app/conf/BuildConfig.groovy | 7 ++++++- .../springcache-test/application.properties | 10 ---------- .../grails-app/conf/BuildConfig.groovy | 16 +++++++++++++++- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f1bfed1..b8266ee 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ plugin.xml .DS_Store .idea/ *.iml +CodeNarcReport.html \ No newline at end of file diff --git a/application.properties b/application.properties index 6055998..1f0c895 100644 --- a/application.properties +++ b/application.properties @@ -2,4 +2,3 @@ #Tue Dec 21 13:35:56 GMT 2010 app.grails.version=1.3.7 app.name=springcache -plugins.spock=0.5-groovy-1.7 diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 250ae64..611312e 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -3,7 +3,9 @@ grails.project.test.class.dir = "target/test-classes" grails.project.test.reports.dir = "target/test-reports" grails.project.dependency.resolution = { - inherits("global") + inherits("global") { + excludes "xml-apis" + } log "warn" repositories { grailsHome() @@ -23,5 +25,8 @@ grails.project.dependency.resolution = { } } plugins { + test(":spock:0.5-groovy-1.7") { + export = false + } } } diff --git a/test/projects/springcache-test/application.properties b/test/projects/springcache-test/application.properties index 7ce6315..0d851b8 100644 --- a/test/projects/springcache-test/application.properties +++ b/test/projects/springcache-test/application.properties @@ -4,13 +4,3 @@ app.grails.version=1.3.7 app.name=springcache-test app.servlet.version=2.4 app.version=0.1 -plugins.bean-fields=0.5 -plugins.build-test-data=1.1.1 -plugins.cache-headers=1.1.2 -plugins.geb=0.5.1 -plugins.hibernate=1.3.7 -plugins.rateable=0.6.2 -plugins.shiro=1.1.1 -plugins.spock=0.5-groovy-1.7 -plugins.tomcat=1.3.7 -plugins.yui=2.7.0.1 diff --git a/test/projects/springcache-test/grails-app/conf/BuildConfig.groovy b/test/projects/springcache-test/grails-app/conf/BuildConfig.groovy index 5f2292b..8302feb 100644 --- a/test/projects/springcache-test/grails-app/conf/BuildConfig.groovy +++ b/test/projects/springcache-test/grails-app/conf/BuildConfig.groovy @@ -2,7 +2,9 @@ grails.project.class.dir = "target/classes" grails.project.test.class.dir = "target/test-classes" grails.project.test.reports.dir = "target/test-reports" grails.project.dependency.resolution = { - inherits "global" + inherits("global") { + excludes "xml-apis" + } log "warn" repositories { grailsPlugins() @@ -18,5 +20,17 @@ grails.project.dependency.resolution = { excludes "groovy", "xml-apis", "commons-logging" } } + plugins { + build ":tomcat:$grailsVersion" + compile ":bean-fields:0.5" + compile ":cache-headers:1.1.2" + compile ":rateable:0.6.2" + compile ":shiro:1.1.1" + compile ":yui:2.7.0.1" + runtime ":hibernate:$grailsVersion" + test ":build-test-data:1.1.1" + test ":geb:0.5.1" + test ":spock:0.5-groovy-1.7" + } } grails.plugin.location.springcache = "../../.." From bcd2e983a2d8e9e482fa85f889f7b10f6f92a4ec Mon Sep 17 00:00:00 2001 From: Rob Fletcher Date: Wed, 9 Nov 2011 11:22:46 +0000 Subject: [PATCH 2/7] Fix for GPSPRINGCACHE-5 forwarding between cached actions now works --- .../web/GrailsFragmentCachingFilter.groovy | 18 +++-- .../grails-app/conf/Config.groovy | 1 + .../forwarding/ForwardingController.groovy | 22 ++++++ .../springcache/web/ForwardingSpec.groovy | 67 +++++++++++++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 test/projects/springcache-test/grails-app/controllers/forwarding/ForwardingController.groovy create mode 100644 test/projects/springcache-test/test/functional/grails/plugin/springcache/web/ForwardingSpec.groovy diff --git a/src/groovy/grails/plugin/springcache/web/GrailsFragmentCachingFilter.groovy b/src/groovy/grails/plugin/springcache/web/GrailsFragmentCachingFilter.groovy index 6c74f98..95b7fd9 100644 --- a/src/groovy/grails/plugin/springcache/web/GrailsFragmentCachingFilter.groovy +++ b/src/groovy/grails/plugin/springcache/web/GrailsFragmentCachingFilter.groovy @@ -35,7 +35,12 @@ class GrailsFragmentCachingFilter extends PageFragmentCachingFilter { SpringcacheService springcacheService CacheManager cacheManager - private final ThreadLocal contextHolder = new ThreadLocal() + private final ThreadLocal> contextHolder = new ThreadLocal>() { + @Override + protected Stack initialValue() { + new Stack() + } + } static final String X_SPRINGCACHE_CACHED = "X-Springcache-Cached" /** @@ -249,15 +254,20 @@ class GrailsFragmentCachingFilter extends PageFragmentCachingFilter { } private void initContext() { - contextHolder.set(new FilterContext()) + contextStack.push(new FilterContext()) } private FilterContext getContext() { - contextHolder.get() + contextStack.peek() } private void destroyContext() { - contextHolder.remove() + contextStack.pop() + if (contextStack.empty()) contextHolder.remove() + } + + private Stack getContextStack() { + contextHolder.get() } } diff --git a/test/projects/springcache-test/grails-app/conf/Config.groovy b/test/projects/springcache-test/grails-app/conf/Config.groovy index d1d42db..7a1497f 100644 --- a/test/projects/springcache-test/grails-app/conf/Config.groovy +++ b/test/projects/springcache-test/grails-app/conf/Config.groovy @@ -96,6 +96,7 @@ springcache { userControllerCache latestControllerCache popularControllerCache + forwardingControllerCache layoutsCache configuredCache { timeToLive = 86400 diff --git a/test/projects/springcache-test/grails-app/controllers/forwarding/ForwardingController.groovy b/test/projects/springcache-test/grails-app/controllers/forwarding/ForwardingController.groovy new file mode 100644 index 0000000..c23186e --- /dev/null +++ b/test/projects/springcache-test/grails-app/controllers/forwarding/ForwardingController.groovy @@ -0,0 +1,22 @@ +package forwarding + +import grails.plugin.springcache.annotations.Cacheable + +class ForwardingController { + + @Cacheable("forwardingControllerCache") + def cachedForwardsToCached = { forward action: "cachedAction" } + + @Cacheable("forwardingControllerCache") + def cachedForwardsToUncached = { forward action: "uncachedAction" } + + def uncachedForwardsToCached = { forward action: "cachedAction" } + + def uncachedForwardsToUncached = { forward action: "uncachedAction" } + + @Cacheable("forwardingControllerCache") + def cachedAction = { render contentType: "text/plain", text: System.currentTimeMillis() } + + def uncachedAction = { render contentType: "text/plain", text: System.currentTimeMillis() } + +} diff --git a/test/projects/springcache-test/test/functional/grails/plugin/springcache/web/ForwardingSpec.groovy b/test/projects/springcache-test/test/functional/grails/plugin/springcache/web/ForwardingSpec.groovy new file mode 100644 index 0000000..39a84cc --- /dev/null +++ b/test/projects/springcache-test/test/functional/grails/plugin/springcache/web/ForwardingSpec.groovy @@ -0,0 +1,67 @@ +package grails.plugin.springcache.web + +import grails.plugin.springcache.SpringcacheService +import groovyx.net.http.RESTClient +import net.sf.ehcache.Ehcache +import org.apache.http.HttpStatus +import org.codehaus.groovy.grails.commons.ApplicationHolder +import spock.lang.* + +@Issue("http://jira.grails.org/browse/GPSPRINGCACHE-5") +@Stepwise +class ForwardingSpec extends Specification { + + @Shared SpringcacheService springcacheService = ApplicationHolder.application.mainContext.springcacheService + @Shared Ehcache forwardingControllerCache = ApplicationHolder.application.mainContext.forwardingControllerCache + + private RESTClient http = new RESTClient("http://localhost:8080/") + + @Unroll("an initial hit on #action primes the cache") + def "an initial hit on an action primes the cache"() { + when: + def response = http.get(path: "/forwarding/$action") + + then: + response.status == HttpStatus.SC_OK + response.data.text ==~ /\d+/ + + and: + cacheMisses == old(cacheMisses) + misses + cacheHits == old(cacheHits) + hits + + where: + action | hits | misses + "uncachedForwardsToCached" | 0 | 1 + "cachedForwardsToUncached" | 0 | 1 + "cachedForwardsToCached" | 1 | 1 + } + + @Unroll("a subsequent hit on #action primes the cache") + def "a subsequent hit on an action hits the cache"() { + when: + def response = http.get(path: "/forwarding/$action") + + then: + response.status == HttpStatus.SC_OK + response.data.text ==~ /\d+/ + + and: + cacheMisses == old(cacheMisses) + misses + cacheHits == old(cacheHits) + hits + + where: + action | hits | misses + "uncachedForwardsToCached" | 1 | 0 + "cachedForwardsToUncached" | 1 | 0 + "cachedForwardsToCached" | 1 | 0 + } + + private long getCacheMisses() { + forwardingControllerCache.statistics.cacheMisses + } + + private long getCacheHits() { + forwardingControllerCache.statistics.cacheHits + } + +} From 35713429d77da435111d983d13fd865543f1038c Mon Sep 17 00:00:00 2001 From: Stefan Armbruster Date: Mon, 21 Nov 2011 16:03:09 +0100 Subject: [PATCH 3/7] exposing ehcache to JMX depending on config settings springcache.jmx ehcache gets registered to the platformMBeanServer. Change is backward compatible, if no config is given, JMX will not be used. --- SpringcacheGrailsPlugin.groovy | 14 ++++++++++++++ TODO.md | 3 +-- application.properties | 3 ++- src/docs/guide/1.2. Release Notes.gdoc | 4 ++++ src/docs/guide/7. Cache Configuration.gdoc | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/SpringcacheGrailsPlugin.groovy b/SpringcacheGrailsPlugin.groovy index 7a01255..6844ed0 100644 --- a/SpringcacheGrailsPlugin.groovy +++ b/SpringcacheGrailsPlugin.groovy @@ -23,6 +23,8 @@ import org.springframework.web.filter.DelegatingFilterProxy import grails.plugin.springcache.aop.* import grails.plugin.springcache.web.* import org.springframework.cache.ehcache.* +import java.lang.management.ManagementFactory +import net.sf.ehcache.management.ManagementService class SpringcacheGrailsPlugin { @@ -134,6 +136,18 @@ class SpringcacheGrailsPlugin { for (tagLibClass in application.tagLibClasses) { decorator.decorate(tagLibClass, applicationContext."${tagLibClass.fullName}") } + + def jmxConfig = application.config.springcache.jmx + if (jmxConfig) { + def springcacheCacheManager = applicationContext.getBean("springcacheCacheManager") + ManagementService.registerMBeans(springcacheCacheManager, ManagementFactory.platformMBeanServer, + jmxConfig.cacheManager, + jmxConfig.cache, + jmxConfig.cacheConfiguration, + jmxConfig.cacheStatistics + ) + } + } def onChange = { event -> diff --git a/TODO.md b/TODO.md index 60d3776..9e9ebe5 100644 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,6 @@ ## General * convert annotation classes to Groovy so they can use null defaults -* expose caching statistics (JMX?) * support reloading via onChange and onConfigChange [GRAILSPLUGINS-1825][1825] ## Controller caching @@ -27,4 +26,4 @@ * cacheable version of g:render? -[1825]:http://jira.codehaus.org/browse/GRAILSPLUGINS-1825 \ No newline at end of file +[1825]:http://jira.codehaus.org/browse/GRAILSPLUGINS-1825 diff --git a/application.properties b/application.properties index 1f0c895..3e0ab43 100644 --- a/application.properties +++ b/application.properties @@ -1,4 +1,5 @@ #Grails Metadata file -#Tue Dec 21 13:35:56 GMT 2010 +#Mon Nov 21 15:44:22 CET 2011 app.grails.version=1.3.7 app.name=springcache +plugins.tomcat=1.3.7 diff --git a/src/docs/guide/1.2. Release Notes.gdoc b/src/docs/guide/1.2. Release Notes.gdoc index fb6e250..a83f185 100644 --- a/src/docs/guide/1.2. Release Notes.gdoc +++ b/src/docs/guide/1.2. Release Notes.gdoc @@ -1,3 +1,7 @@ +h4. 1.3.2 + + * exposing ehcache to JMX + h4. 1.3.1 * Fixes compilation problem on recent Groovy versions "GRAILSPLUGINS-2820":http://jira.codehaus.org/browse/GRAILSPLUGINS-2820 diff --git a/src/docs/guide/7. Cache Configuration.gdoc b/src/docs/guide/7. Cache Configuration.gdoc index 5c6a2fa..de8aa8c 100644 --- a/src/docs/guide/7. Cache Configuration.gdoc +++ b/src/docs/guide/7. Cache Configuration.gdoc @@ -53,3 +53,21 @@ springcache { Under the hood this is simply setting up @EhCacheFactoryBean@ instances in the Spring context, so it is up to you whether you prefer to use @resources.groovy@ or @Config.groovy@ there is not much difference. The properties shown are just examples, see the "EhCacheFactoryBean":http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/cache/ehcache/EhCacheFactoryBean.html documentation for full details of all the properties you can set. + + +h3. exposing ehcache to JMX + +Ehcache is already prepared to expose itself to JMX. To enable this use the following snippet in @grails-app/conf/Config.groovy@: + +{code} +springcache { + jmx { + cacheManager = true + cache = true + cacheConfiguration = true + cacheStatistics = true + } +} +{code} + +For details, see [Ehcache's JMX manual|http://ehcache.org/documentation/operations/jmx]. \ No newline at end of file From f4a7fd6a6735ef14fd0695acf8dee0690c3c8e27 Mon Sep 17 00:00:00 2001 From: Konstantinos Kostarellis Date: Mon, 30 Apr 2012 15:06:00 +0200 Subject: [PATCH 4/7] Fixed some links in the docs links in the docs were pointing to invalid and deprecated targets. --- src/docs/guide/1. Introduction.gdoc | 4 ++-- src/docs/guide/1.1. Known Issues.gdoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/docs/guide/1. Introduction.gdoc b/src/docs/guide/1. Introduction.gdoc index 5788fab..6de90a3 100644 --- a/src/docs/guide/1. Introduction.gdoc +++ b/src/docs/guide/1. Introduction.gdoc @@ -8,8 +8,8 @@ The plugin depends on the "EhCache":http://ehcache.org/ and "EhCache-Web":http:/ h3. Contact -The plugin code is hosted on [GitHub|http://github.com/robfletcher/grails-springcache]. Please feel free to fork the plugin and contribute patches. +The plugin code is hosted on [GitHub|https://github.com/gpc/grails-springcache]. Please feel free to fork the plugin and contribute patches. -Please raise defects or enhancements against the Grails Springcache plugin component on the [Codehaus JIRA|http://jira.codehaus.org/browse/GRAILSPLUGINS/component/14010]. +Please raise defects or enhancements against the Grails Springcache plugin component on the [Codehaus JIRA|http://jira.grails.org/browse/GPSPRINGCACHE]. Questions, comments? [rob@energizedwork.com|mailto:rob@energizedwork.com] or better still contact me via the [Grails User mailing list|http://grails.org/Mailing+lists]. diff --git a/src/docs/guide/1.1. Known Issues.gdoc b/src/docs/guide/1.1. Known Issues.gdoc index 08b27fc..7875d16 100644 --- a/src/docs/guide/1.1. Known Issues.gdoc +++ b/src/docs/guide/1.1. Known Issues.gdoc @@ -1,2 +1,2 @@ - * "GRAILSPLUGINS-2553":http://jira.codehaus.org/browse/GRAILSPLUGINS-2553 Cached methods with default arguments are not intercepted unless all the arguments are specified. Therefore the cache is completely bypassed (neither hit or missed). - * "GRAILSPLUGINS-2544":http://jira.codehaus.org/browse/GRAILSPLUGINS-2544 Due to a bug with Grails itself query string parameters are not included in the generated cache key. This can be a serious issue if you are using pagination for example as query string parameters such as @offset@ will be ignored resulting in the same cached content being served for every page. This *only* affects Grails version 1.3.4. + * "GPSPRINGCACHE-11":http://jira.grails.org/browse/GPSPRINGCACHE-11 Cached methods with default arguments are not intercepted unless all the arguments are specified. Therefore the cache is completely bypassed (neither hit or missed). + * "GPSPRINGCACHE-14G":http://jira.grails.org/browse/GPSPRINGCACHE-14 Due to a bug with Grails itself query string parameters are not included in the generated cache key. This can be a serious issue if you are using pagination for example as query string parameters such as @offset@ will be ignored resulting in the same cached content being served for every page. This *only* affects Grails version 1.3.4. From 68eb382dc921847e158950e864c8e836505a78d3 Mon Sep 17 00:00:00 2001 From: Robert Fischer Date: Mon, 11 Jun 2012 13:46:57 -0400 Subject: [PATCH 5/7] Ignore vi swap fils --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b8266ee..6f40b19 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ plugin.xml .DS_Store .idea/ *.iml -CodeNarcReport.html \ No newline at end of file +CodeNarcReport.html +.*.swp From 98a7755893832824e50d969088aa5d350a74a54d Mon Sep 17 00:00:00 2001 From: Robert Fischer Date: Mon, 11 Jun 2012 13:52:21 -0400 Subject: [PATCH 6/7] Spacing clean-up --- SpringcacheGrailsPlugin.groovy | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/SpringcacheGrailsPlugin.groovy b/SpringcacheGrailsPlugin.groovy index fe7fb18..732508f 100644 --- a/SpringcacheGrailsPlugin.groovy +++ b/SpringcacheGrailsPlugin.groovy @@ -137,17 +137,16 @@ class SpringcacheGrailsPlugin { decorator.decorate(tagLibClass, applicationContext."${tagLibClass.fullName}") } - def jmxConfig = application.config.springcache.jmx - if (jmxConfig) { - def springcacheCacheManager = applicationContext.getBean("springcacheCacheManager") - ManagementService.registerMBeans(springcacheCacheManager, ManagementFactory.platformMBeanServer, - jmxConfig.cacheManager, - jmxConfig.cache, - jmxConfig.cacheConfiguration, - jmxConfig.cacheStatistics - ) - } - + def jmxConfig = application.config.springcache.jmx + if (jmxConfig) { + def springcacheCacheManager = applicationContext.getBean("springcacheCacheManager") + ManagementService.registerMBeans(springcacheCacheManager, ManagementFactory.platformMBeanServer, + jmxConfig.cacheManager, + jmxConfig.cache, + jmxConfig.cacheConfiguration, + jmxConfig.cacheStatistics + ) + } } def onChange = { event -> From 2ba6feef53c5992a1831e7c14d9a7cd364fa13d3 Mon Sep 17 00:00:00 2001 From: Robert Fischer Date: Mon, 11 Jun 2012 14:21:21 -0400 Subject: [PATCH 7/7] Inject the 'cached' property --- SpringcacheGrailsPlugin.groovy | 33 ++++++++++++++++--- ...3.2 Calling Cached Methods Internally.gdoc | 4 +-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/SpringcacheGrailsPlugin.groovy b/SpringcacheGrailsPlugin.groovy index 732508f..62bd6d2 100644 --- a/SpringcacheGrailsPlugin.groovy +++ b/SpringcacheGrailsPlugin.groovy @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -25,11 +25,12 @@ import grails.plugin.springcache.web.* import org.springframework.cache.ehcache.* import java.lang.management.ManagementFactory import net.sf.ehcache.management.ManagementService +import org.springframework.context.ApplicationContext class SpringcacheGrailsPlugin { - def version = "1.4" - def grailsVersion = "1.2.0 > *" + def version = "1.4.1" + def grailsVersion = "2.0.0 > *" def dependsOn = [:] def pluginExcludes = [ "grails-app/views/**", @@ -81,7 +82,7 @@ class SpringcacheGrailsPlugin { def doWithSpring = { if (!isEnabled(application)) { log.warn "Springcache plugin is disabled" - springcacheFilter(NoOpFilter) + springcacheFilter(NoOpFilter) } else { if (application.config.grails.spring.disable.aspectj.autoweaving) { log.warn "Service method caching is not compatible with the config setting 'grails.spring.disable.aspectj.autoweaving = false'" @@ -129,6 +130,7 @@ class SpringcacheGrailsPlugin { def doWithDynamicMethods = {ctx -> PageInfo.metaClass.mixin(HeadersCategory) + attachGetCached(applicationContext) } def doWithApplicationContext = { applicationContext -> @@ -147,16 +149,37 @@ class SpringcacheGrailsPlugin { jmxConfig.cacheStatistics ) } + } def onChange = { event -> + ApplicationContext ctx = event.ctx + GrailsApplication application = event.application + if (application.isTagLibClass(event.source)) { def tagLibClass = application.getTagLibClass(event.source.name) - def instance = event.ctx."${event.source.name}" + def instance = ctx."${event.source.name}" def decorator = new CachingTagLibDecorator(event.ctx.springcacheService) decorator.decorate(tagLibClass, instance) } + + attachGetCached(ctx) + } + + def attachGetCached(ApplicationContext ctx) { + ctx.beanDefinitionNames.each { beanName -> + def mc + if(ctx.isSingleton(beanName)) { + mc = ctx.getBean(beanName).getMetaClass() + } else { + mc = ctx.getType(beanName).getMetaClass() + } + + mc.getCached = { -> + ctx.getBean(beanName) + } + } } def getWebXmlFilterOrder() { diff --git a/src/docs/guide/3.2 Calling Cached Methods Internally.gdoc b/src/docs/guide/3.2 Calling Cached Methods Internally.gdoc index 8e24d72..fbf9ceb 100644 --- a/src/docs/guide/3.2 Calling Cached Methods Internally.gdoc +++ b/src/docs/guide/3.2 Calling Cached Methods Internally.gdoc @@ -28,7 +28,7 @@ class ExampleService { def grailsApplication def nonCachedMethod() { - grailsApplication.mainContext.exampleService.cachedMethod() + cached.cachedMethod() } @Cacheable('cachedMethodCache') @@ -38,4 +38,4 @@ class ExampleService { } {code} -Instead of calling the method on @this@, we obtain the proxy via the application context (i.e. @grailsApplication.mainContext.exampleService@) and call the method on that. This way we go through the caching mechanism. \ No newline at end of file +Instead of calling the method on @this@, we obtain the proxy via the injected @cached@ property, and call the method on that. This way we go through the caching mechanism.