I want to add the following if
-statement at the beginning of a method:
@Grant( [ Permission.admin ] )
def someMethod( someArg ){
if( GrantUtil.checkAuthorization( someArg, Permission.admin ){ // 2nd param comes from annotation
// nothing
}else{
return null
}
// the rest
}
For that I'm using a custom ASTTransformation:
class GrantASTTransformation implements ASTTransformation {
@Override
void visit( ASTNode[] nodes, SourceUnit source ) {
if( !nodes ) return
MethodNode methodNode = (MethodNode) nodes.find{ it in MethodNode }
if( methodNode ) augmentMethod methodNode
}
void augmentMethod( MethodNode methodNode ) {
List<AnnotationNode> annos = methodNode.getAnnotations new ClassNode( Grant )
Expression rolesValue = annos.first().getMember 'value'
Expression param = new VariableExpression( methodNode.parameters[ 0 ] )
Expression args = new ArgumentListExpression( [ param, rolesValue ] )
StaticMethodCallExpression callExp = new StaticMethodCallExpression( new ClassNode( GrantUtil ), 'checkAuthorization', args )
BooleanExpression checkExpression = new BooleanExpression( callExp )
IfStatement ifStmt = new IfStatement( checkExpression, new EmptyStatement(), new ReturnStatement( ConstantExpression.NULL ) )
((BlockStatement)methodNode.code).statements.add 0, ifStmt
}
}
and it works like charm.
Now I want to rewrite this method using a more transparent AstBuilder and so far I came out with:
void augmentMethod( MethodNode methodNode ) {
List<AnnotationNode> annos = methodNode.getAnnotations new ClassNode( Grant )
Expression rolesValue = annos.first().getMember 'value'
IfStatement ifStmt = (IfStatement)new AstBuilder().buildFromSpec{
ifStatement{
booleanExpression{
staticMethodCall( GrantUtil, 'checkAuthorization' ){
argumentList{
// this doesn't work
param
rolesValue
}
}
}
//if block
empty()
//else block
returnStatement{ constant null }
}
}.first()
((BlockStatement)methodNode.code).statements.add 0, ifStmt
}
but it fails filling up the argumentList
, throwing
groovy.lang.MissingMethodException: No signature of method: static GrantUtil.checkAuthorization() is applicable for argument types: () values: []
Possible solutions: checkAuthorization(io.vertx.ext.web.RoutingContext, java.util.List)
I looked through the test but could not find a way to introduce the "external" params to the argumentList
.
What is the proper way of doing this? Is this supported by the AstBuilder?
UPDATE:
I tried the suggestion of @cfrick and it worked to 50%:
staticMethodCall(GrantUtil, 'checkAuthorization') {
argumentList {
variable methodNode.parameters[0].name // works like charm!
constant( [ Permission.user ] as Permission[] )
}
}
Now upon execution I'm getting the exception:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during instruction selection: Cannot generate bytecode for constant: [Lio.lootwalk.domain.Permission;@1e7f19b4 of type: [Lio.lootwalk.domain.Permission;
So, passing an array of Enumeration object as a constant
in argumentList
fails...
You can improve your original solution using the static methods in Groovy's GeneralUtils
class:
class GrantASTTransformation implements ASTTransformation {
@Override
void visit(ASTNode[] nodes, SourceUnit source ) {
nodes?.find{ it in MethodNode }?.with{ augmentMethod it }
}
void augmentMethod( MethodNode mn ) {
def annos = mn.getAnnotations new ClassNode( Grant )
def rolesValue = annos.first().getMember 'value'
def param = varX( mn.parameters[ 0 ] )
def args = args( param, rolesValue )
def callCheck = callX( new ClassNode( GrantUtil ), 'checkAuthorization', args )
def notAuthorized = notX( boolX( callCheck ) )
def returnEarly = returnS( nullX() )
def guard = ifS( notAuthorized, returnEarly )
mn.code.statements.add 0, guard
}
}
With regards to AstBuilder
, it is now less favoured than its replacement, macros. One way to use macros to write the augmentMethod
is:
void augmentMethod( MethodNode mn ) {
def grantNode = ClassHelper.make(Grant)
def grantUtilNode = ClassHelper.make(GrantUtil)
def annos = mn.getAnnotations grantNode
def rolesValue = annos.first().getMember 'value'
def param = varX( mn.parameters[ 0 ] )
def guard = macro {
if (!$v{ classX(grantUtilNode) }.checkAuthorization( $v{ param }, $v{ rolesValue } )) {
return
}
}
mn.code.statements.add 0, guard
}
You just have to be careful with referencing classes within that code which is why GrantUtil
doesn't appear explicitly.
To stick with AstBuilder
you can use:
void augmentMethod( MethodNode mn ) {
def annos = mn.getAnnotations new ClassNode( Grant )
def rolesValue = annos.first().getMember('value').expressions[0]
def param = varX( mn.parameters[ 0 ] )
def guard = new AstBuilder().buildFromSpec {
ifStatement{
booleanExpression{
staticMethodCall( GrantUtil, 'checkAuthorization' ) {
argumentList {
variable param.name
property {
classExpression rolesValue.objectExpression.type.typeClass
constant rolesValue.propertyAsString
}
}
}
}
empty()
returnStatement{ constant null }
}
}.first()
mn.code.statements.add 0, guard
}
There is probably a simplification possible but the above works.