From e0b2040862bafa7ff5254774ce975b492cd91803 Mon Sep 17 00:00:00 2001 From: Moritz Kobel Date: Mon, 7 May 2018 20:51:04 +0200 Subject: [PATCH 1/4] Implement integration test for @CacheEvict --- .../com/demo/DemoController.groovy | 6 ++++ .../com/demo/BasicCachingService.groovy | 4 +++ .../demo/CachingServiceIntegrationSpec.groovy | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/grails-app/controllers/com/demo/DemoController.groovy b/grails-app/controllers/com/demo/DemoController.groovy index d9f46935..be45e0d9 100644 --- a/grails-app/controllers/com/demo/DemoController.groovy +++ b/grails-app/controllers/com/demo/DemoController.groovy @@ -53,6 +53,12 @@ class DemoController { render "Result: ${result}" } + def cacheEvict(String key) { + basicCachingService.getDataEvict(key) + def result = basicCachingService.getData(key) + render "Result: ${result}" + } + def blockCache(int counter) { [counter: counter] } diff --git a/grails-app/services/com/demo/BasicCachingService.groovy b/grails-app/services/com/demo/BasicCachingService.groovy index 3d9e9065..0c91f9d2 100644 --- a/grails-app/services/com/demo/BasicCachingService.groovy +++ b/grails-app/services/com/demo/BasicCachingService.groovy @@ -66,6 +66,10 @@ class BasicCachingService { "** ${value} **" } + @CacheEvict(value = 'basic', key = { key } ) + def getDataEvict(String key) { + } + @Cacheable(value = 'basic', condition = { x < 10 }) def multiply(int x, int y) { conditionalInvocationCounter++ diff --git a/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy b/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy index 3bd4e4da..9510d877 100644 --- a/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy +++ b/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy @@ -201,4 +201,33 @@ class CachingServiceIntegrationSpec extends GebSpec { then: browser.driver.pageSource.contains 'Result: ** Thin Lizzy' } + + void 'test basic cache evict service'() { + when: + go '/demo/cacheGet?key=band' + + then: + browser.driver.pageSource.contains 'Result: null' + + when: + go '/demo/cachePut?key=band&value=Thin+Lizzy' + + then: + browser.driver.pageSource.contains 'Result: ** Thin Lizzy **' + + when: + go '/demo/cacheGet?key=band' + + then: + browser.driver.pageSource.contains 'Result: ** Thin Lizzy' + + when: + go '/demo/cacheEvict?key=band' + + then: + browser.driver.pageSource.contains 'Result: null' + } + + + } From d7dc5a015b81387446624bc0149b61b5b9e059bc Mon Sep 17 00:00:00 2001 From: Moritz Kobel Date: Mon, 7 May 2018 21:16:53 +0200 Subject: [PATCH 2/4] Add more tests to show only @CacheEvict with a key fails --- .../com/demo/DemoController.groovy | 14 +++++- .../com/demo/BasicCachingService.groovy | 4 ++ .../demo/CachingServiceIntegrationSpec.groovy | 46 +++++++++++++------ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/grails-app/controllers/com/demo/DemoController.groovy b/grails-app/controllers/com/demo/DemoController.groovy index be45e0d9..7933a349 100644 --- a/grails-app/controllers/com/demo/DemoController.groovy +++ b/grails-app/controllers/com/demo/DemoController.groovy @@ -53,12 +53,24 @@ class DemoController { render "Result: ${result}" } - def cacheEvict(String key) { + def cacheEvictAndGet(String key) { basicCachingService.getDataEvict(key) def result = basicCachingService.getData(key) render "Result: ${result}" } + def cacheEvictAllAndGet(String key) { + basicCachingService.getDataEvictAll() + def result = basicCachingService.getData(key) + render "Result: ${result}" + } + + def cacheClearAndGet(String key) { + grailsCacheAdminService.clearCache('basic') + def result = basicCachingService.getData(key) + render "Result: ${result}" + } + def blockCache(int counter) { [counter: counter] } diff --git a/grails-app/services/com/demo/BasicCachingService.groovy b/grails-app/services/com/demo/BasicCachingService.groovy index 0c91f9d2..554c47a0 100644 --- a/grails-app/services/com/demo/BasicCachingService.groovy +++ b/grails-app/services/com/demo/BasicCachingService.groovy @@ -70,6 +70,10 @@ class BasicCachingService { def getDataEvict(String key) { } + @CacheEvict(value = 'basic', allEntries = true) + def getDataEvictAll() { + } + @Cacheable(value = 'basic', condition = { x < 10 }) def multiply(int x, int y) { conditionalInvocationCounter++ diff --git a/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy b/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy index 9510d877..725434a4 100644 --- a/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy +++ b/src/integration-test/groovy/com/demo/CachingServiceIntegrationSpec.groovy @@ -2,6 +2,8 @@ package com.demo import geb.spock.GebSpec import grails.test.mixin.integration.Integration +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.cache.CacheManager @Integration class CachingServiceIntegrationSpec extends GebSpec { @@ -202,13 +204,7 @@ class CachingServiceIntegrationSpec extends GebSpec { browser.driver.pageSource.contains 'Result: ** Thin Lizzy' } - void 'test basic cache evict service'() { - when: - go '/demo/cacheGet?key=band' - - then: - browser.driver.pageSource.contains 'Result: null' - + void 'test basic cache evict key service'() { when: go '/demo/cachePut?key=band&value=Thin+Lizzy' @@ -216,18 +212,40 @@ class CachingServiceIntegrationSpec extends GebSpec { browser.driver.pageSource.contains 'Result: ** Thin Lizzy **' when: - go '/demo/cacheGet?key=band' - - then: - browser.driver.pageSource.contains 'Result: ** Thin Lizzy' - - when: - go '/demo/cacheEvict?key=band' + go '/demo/cacheEvictAndGet?key=band' then: browser.driver.pageSource.contains 'Result: null' } + void 'test basic cache evict all service'() { + when: + go '/demo/cachePut?key=band&value=Thin+Lizzy' + + then: + browser.driver.pageSource.contains 'Result: ** Thin Lizzy **' + + when: + go '/demo/cacheEvictAllAndGet?key=band' + + then: + browser.driver.pageSource.contains 'Result: null' + } + + void 'test basic cache clear service'() { + when: + go '/demo/cachePut?key=band&value=Thin+Lizzy' + + then: + browser.driver.pageSource.contains 'Result: ** Thin Lizzy **' + + when: + go '/demo/cacheClearAndGet?key=band' + + then: + browser.driver.pageSource.contains 'Result: null' + } + } From 9b77b6fc24832b52aa52ffa52f885a262423b271 Mon Sep 17 00:00:00 2001 From: Marco-Antonio Avallone Date: Tue, 8 May 2018 09:48:29 +0200 Subject: [PATCH 3/4] Restore old hashCode and equals in CustomCacheKeyGenerator --- .../cache/CustomCacheKeyGenerator.groovy | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy b/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy index cde66699..c61179f2 100644 --- a/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy +++ b/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy @@ -39,7 +39,6 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { } @SuppressWarnings("serial") - @EqualsAndHashCode private static final class CacheKey implements Serializable { final String targetClassName final String targetMethodName @@ -53,6 +52,49 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { this.targetObjectHashCode = targetObjectHashCode this.simpleKey = simpleKey } + @Override + int hashCode() { + final int prime = 31 + int result = 1 + result = prime * result + + ((simpleKey == null) ? 0 : simpleKey.hashCode()) + result = prime * result + + ((targetClassName == null) ? 0 : targetClassName + .hashCode()) + result = prime * result + + ((targetMethodName == null) ? 0 : targetMethodName + .hashCode()) + result = prime * result + targetObjectHashCode + return result + } + @Override + boolean equals(Object obj) { + if (this.is(obj)) + return true + if (obj == null) + return false + if (getClass() != obj.getClass()) + return false + CacheKey other = (CacheKey) obj + if (simpleKey == null) { + if (other.simpleKey != null) + return false + } else if (!simpleKey.equals(other.simpleKey)) + return false + if (targetClassName == null) { + if (other.targetClassName != null) + return false + } else if (!targetClassName.equals(other.targetClassName)) + return false + if (targetMethodName == null) { + if (other.targetMethodName != null) + return false + } else if (!targetMethodName.equals(other.targetMethodName)) + return false + if (targetObjectHashCode != other.targetObjectHashCode) + return false + return true + } } Object generate(Object target, Method method, Object... params) { @@ -77,7 +119,6 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { } - @EqualsAndHashCode @CompileStatic private static class TemporaryGrailsCacheKey implements Serializable { final String targetClassName @@ -92,6 +133,49 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { this.targetObjectHashCode = targetObjectHashCode this.simpleKey = simpleKey } + @Override + int hashCode() { + final int prime = 31 + int result = 1 + result = prime * result + + ((simpleKey == null) ? 0 : simpleKey.hashCode()) + result = prime * result + + ((targetClassName == null) ? 0 : targetClassName + .hashCode()) + result = prime * result + + ((targetMethodName == null) ? 0 : targetMethodName + .hashCode()) + result = prime * result + targetObjectHashCode + return result + } + @Override + boolean equals(Object obj) { + if (this.is(obj)) + return true + if (obj == null) + return false + if (getClass() != obj.getClass()) + return false + TemporaryGrailsCacheKey other = (TemporaryGrailsCacheKey) obj + if (simpleKey == null) { + if (other.simpleKey != null) + return false + } else if (!simpleKey.equals(other.simpleKey)) + return false + if (targetClassName == null) { + if (other.targetClassName != null) + return false + } else if (!targetClassName.equals(other.targetClassName)) + return false + if (targetMethodName == null) { + if (other.targetMethodName != null) + return false + } else if (!targetMethodName.equals(other.targetMethodName)) + return false + if (targetObjectHashCode != other.targetObjectHashCode) + return false + return true + } } } From 47b0047c282497daab5aee42f27bbec401f7f024 Mon Sep 17 00:00:00 2001 From: Moritz Kobel Date: Wed, 9 May 2018 22:42:22 +0200 Subject: [PATCH 4/4] Implement simpleKey comparison success * a known exception is an empty map (then use other attributes to compare) --- .../grails/plugin/cache/CustomCacheKeyGenerator.groovy | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy b/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy index c61179f2..3b3a8d77 100644 --- a/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy +++ b/src/ast/groovy/grails/plugin/cache/CustomCacheKeyGenerator.groovy @@ -81,6 +81,10 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { return false } else if (!simpleKey.equals(other.simpleKey)) return false + else if ( simpleKey.equals(other.simpleKey) && !(simpleKey instanceof Map && ((Map)simpleKey).size() == 0 ) ) { + return true // equal if simpleKey is identical but not an empty map + } + if (targetClassName == null) { if (other.targetClassName != null) return false @@ -162,6 +166,10 @@ class CustomCacheKeyGenerator implements KeyGenerator, GrailsCacheKeyGenerator { return false } else if (!simpleKey.equals(other.simpleKey)) return false + else if ( simpleKey.equals(other.simpleKey) && !(simpleKey instanceof Map && ((Map)simpleKey).size() == 0 ) ) { + return true // equal if simpleKey is identical but not an empty map + } + if (targetClassName == null) { if (other.targetClassName != null) return false