Skip to content

Commit

Permalink
Support the allEntries, key, and condition arguments to CacheEvict.
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskleeh committed May 17, 2017
1 parent 9602b02 commit 1e1c374
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 24 deletions.
20 changes: 20 additions & 0 deletions src/ast/groovy/grails/plugin/cache/CacheEvict.java
Expand Up @@ -40,4 +40,24 @@
* value.
*/
String[] value();

/**
* A closure for computing the key dynamically.
* <p>Default is null, meaning all method parameters are considered as a key.
*/
Class[] key() default {};

/**
* A closure used for conditioning the method caching.
* <p>Default is null, meaning the method is always cached.
*/
Class[] condition() default {};

/**
* Whether or not all the entries inside the cache(s) are removed or not. By
* default, only the value under the associated key is removed.
* <p>Note that specifying setting this parameter to true and specifying a
* CacheKey is not allowed.
*/
boolean allEntries() default false;
}
Expand Up @@ -208,4 +208,22 @@ abstract class AbstractCacheTransformation extends AbstractMethodDecoratingTrans
}
}.visitClosureExpression(closureExpression)
}

protected void handleCacheCondition(SourceUnit sourceUnit, AnnotationNode annotationNode, MethodNode methodNode, MethodCallExpression originalMethodCallExpr, BlockStatement newMethodBody) {
Expression conditionMember = annotationNode.getMember('condition')
if (conditionMember instanceof ClosureExpression) {
ClosureExpression closureExpression = (ClosureExpression) conditionMember
makeClosureParameterAware(sourceUnit, methodNode, closureExpression)
// Adds check whether caching should happen
// if(!condition.call()) {
// return originalMethodCall.call()

Statement ifShouldCacheMethodCallStatement = ifS(
notX(callX(conditionMember, "call")),
returnS(originalMethodCallExpr)
)
newMethodBody.addStatement(ifShouldCacheMethodCallStatement)
annotationNode.members.remove('condition')
}
}
}
Expand Up @@ -20,21 +20,27 @@ import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.h2.schema.Constant
import org.springframework.cache.Cache

import static org.codehaus.groovy.ast.ClassHelper.make
import static org.codehaus.groovy.ast.tools.GeneralUtils.block
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS
import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
import static org.grails.datastore.gorm.transform.AstMethodDispatchUtils.callD

/**
* Implementation of {@link CacheEvict}
Expand All @@ -51,18 +57,40 @@ class CacheEvictTransformation extends AbstractCacheTransformation {

@Override
protected Expression buildDelegatingMethodCall(SourceUnit sourceUnit, AnnotationNode annotationNode, ClassNode classNode, MethodNode methodNode, MethodCallExpression originalMethodCallExpr, BlockStatement newMethodBody) {

VariableExpression cacheManagerVariableExpression = varX(GRAILS_CACHE_MANAGER_PROPERTY_NAME)
BlockStatement cachingBlock = block()
handleCacheCondition(sourceUnit, annotationNode, methodNode, originalMethodCallExpr, newMethodBody)

BlockStatement cachingBlock = block()

// Cache $_cache_cacheVariable = this.grailsCacheManager.getCache("...");
VariableExpression cacheDeclaration = declareCache(annotationNode, cacheManagerVariableExpression, cachingBlock)

// $_cache_cacheVariable.clear()
cachingBlock.addStatement(
stmt(callX(cacheDeclaration, 'clear'))
)
Statement statement

boolean clearAllEntries = false

Expression allEntries = annotationNode.getMember('allEntries')
if (allEntries instanceof ConstantExpression) {
clearAllEntries = ((ConstantExpression)allEntries).isTrueExpression()
annotationNode.members.remove('allEntries')
}

if (clearAllEntries) {
// $_cache_cacheVariable.clear()
statement = stmt(callX(cacheDeclaration, 'clear'))
} else {
// def $_method_parameter_map = [name:value]
declareAndInitializeParameterValueMap(annotationNode, methodNode, cachingBlock)

// def $_cache_cacheKey = customCacheKeyGenerator.generate(className, methodName, hashCode, $_method_parameter_map)
VariableExpression cacheKeyDeclaration = declareCacheKey(sourceUnit, annotationNode, classNode, methodNode , cachingBlock)

// $_cache_cacheVariable.evict($_cache_cacheKey)
statement = stmt(callX(cacheDeclaration, 'evict', cacheKeyDeclaration))
}

cachingBlock.addStatement(statement)

newMethodBody.addStatement(
// if(grailsCacheManager != null)
Expand Down
Expand Up @@ -106,22 +106,4 @@ class CacheableTransformation extends AbstractCacheTransformation {
return originalMethodCallExpr
}

protected void handleCacheCondition(SourceUnit sourceUnit, AnnotationNode annotationNode, MethodNode methodNode, MethodCallExpression originalMethodCallExpr, BlockStatement newMethodBody) {
Expression conditionMember = annotationNode.getMember('condition')
if (conditionMember instanceof ClosureExpression) {
ClosureExpression closureExpression = (ClosureExpression) conditionMember
makeClosureParameterAware(sourceUnit, methodNode, closureExpression)
// Adds check whether caching should happen
// if(!condition.call()) {
// return originalMethodCall.call()

Statement ifShouldCacheMethodCallStatement = ifS(
notX(callX(conditionMember, "call")),
returnS(originalMethodCallExpr)
)
newMethodBody.addStatement(ifShouldCacheMethodCallStatement)
annotationNode.members.remove('condition')
}
}

}
1 change: 1 addition & 0 deletions src/main/docs/guide/introduction/changeLog.adoc
Expand Up @@ -6,6 +6,7 @@
* AST based solution.
* Closures instead of SpEL
* Controller caching is no longer supported.
* CacheEvict beforeInvocation is no longer supported. The eviction will always occur before the method.

=== Version 1.1.7 - June 24, 2014

Expand Down
2 changes: 1 addition & 1 deletion src/main/docs/guide/usage/annotations.adoc
Expand Up @@ -60,7 +60,7 @@ class MessageService {
...
@CachePut(value='message', key= { message.title })
@CachePut(value='message', key = { message.title })
void save(Message message) {
println "Saving message $message"
message.save()
Expand Down
159 changes: 159 additions & 0 deletions src/test/groovy/grails/plugin/cache/CacheEvictParseSpec.groovy
@@ -0,0 +1,159 @@
package grails.plugin.cache

import org.grails.plugin.cache.GrailsCacheManager
import org.springframework.cache.Cache
import spock.lang.Specification

class CacheEvictParseSpec extends Specification {

void "test simple usage"() {
given:
GrailsCacheManager cacheManager = Mock(GrailsCacheManager)
GrailsCacheKeyGenerator keyGenerator = Mock(GrailsCacheKeyGenerator)
def cache = Mock(Cache)
Class testService = new GroovyShell().evaluate('''
import grails.plugin.cache.*
import groovy.transform.CompileStatic
@CompileStatic
class TestService {
@CacheEvict('sum')
def evict(String foo) {
}
}
return TestService
''')
when:
def instance = testService.newInstance()
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__grailsCacheManager" = cacheManager
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__customCacheKeyGenerator" = keyGenerator
instance.evict("a")

then:
1 * keyGenerator.generate("TestService", "evict", _, [foo:"a"]) >> "a"
1 * cacheManager.getCache("sum") >> cache
1 * cache.evict("a")
}

void "test evict with key closure"() {
given:
GrailsCacheManager cacheManager = Mock(GrailsCacheManager)
GrailsCacheKeyGenerator keyGenerator = Mock(GrailsCacheKeyGenerator)
def cache = Mock(Cache)
Class testService = new GroovyShell().evaluate('''
import grails.plugin.cache.*
import groovy.transform.CompileStatic
@CompileStatic
class TestService {
@CacheEvict(value = 'sum', key = { foo })
def evict(String foo) {
}
}
return TestService
''')
when:
def instance = testService.newInstance()
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__grailsCacheManager" = cacheManager
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__customCacheKeyGenerator" = keyGenerator
instance.evict("a")

then:
1 * keyGenerator.generate("TestService", "evict", _, _ as Closure) >> "a"
1 * cacheManager.getCache("sum") >> cache
1 * cache.evict("a")
}

void "test evict all entries"() {
given:
GrailsCacheManager cacheManager = Mock(GrailsCacheManager)
def cache = Mock(Cache)
Class testService = new GroovyShell().evaluate('''
import grails.plugin.cache.*
import groovy.transform.CompileStatic
@CompileStatic
class TestService {
@CacheEvict(value = 'sum', allEntries = true)
def evict(String foo) {
}
}
return TestService
''')
when:
def instance = testService.newInstance()
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__grailsCacheManager" = cacheManager
instance.evict("a")

then:
1 * cacheManager.getCache("sum") >> cache
1 * cache.clear()
}

void "test evict with condition that evaluates to false"() {
given:
GrailsCacheManager cacheManager = Mock(GrailsCacheManager)
def cache = Mock(Cache)
Class testService = new GroovyShell().evaluate('''
import grails.plugin.cache.*
import groovy.transform.CompileStatic
@CompileStatic
class TestService {
@CacheEvict(value = 'sum', allEntries = true, condition = { false })
def evict(String foo) {
}
}
return TestService
''')
when:
def instance = testService.newInstance()
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__grailsCacheManager" = cacheManager
instance.evict("a")

then:
0 * cacheManager.getCache("sum")
0 * cache.clear()
}

void "test evict with condition that evaluates to true"() {
given:
GrailsCacheManager cacheManager = Mock(GrailsCacheManager)
def cache = Mock(Cache)
Class testService = new GroovyShell().evaluate('''
import grails.plugin.cache.*
import groovy.transform.CompileStatic
@CompileStatic
class TestService {
@CacheEvict(value = 'sum', allEntries = true, condition = { true })
def evict(String foo) {
}
}
return TestService
''')
when:
def instance = testService.newInstance()
instance.@"org_grails_plugin_cache_GrailsCacheManagerAware__grailsCacheManager" = cacheManager
instance.evict("a")

then:
1 * cacheManager.getCache("sum") >> cache
1 * cache.clear()
}
}

0 comments on commit 1e1c374

Please sign in to comment.