Monday 12 September 2011

Groovy AST Transform to inject Spring Security Check for Java Caller

This post follows on with the trend of using AST transforms to inject compile time behaviour into Groovy code so that Java code can exploit these benefits. In this case we have a piece of Java code (Spring RMIExporter) that calls methods on a 'service' class. Since it is straight Java making the call to the code we cannot use the Groovy metaclass approach of changing the way methods are called. The following AST transform was created that simply iterates over every public non-static class method and calls a security method provided as part of the annotation:
package annotations

import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.transform.ASTTransformation

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class SpringSecurityInvocationTransformation implements ASTTransformation {
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {

        def instanceNode = nodes[1] //specified in documentation
        def annotationNode = nodes[0]

        String securityMethod = annotationNode.getMember("securityMethod").getValue()

        def interfaceMethods = instanceNode.getMethods()
        interfaceMethods.findAll { MethodNode methodNode ->
            methodNode.isPublic() && !methodNode.isStatic() && methodNode.declaringClass == instanceNode.getPlainNodeReference()
        }.each { MethodNode methodNode ->
            List currentStatements = methodNode.getCode().getStatements()
            String methodCallingCode = "this." + securityMethod + "()"

            def methodCallNodes = new AstBuilder().buildFromString(CompilePhase.SEMANTIC_ANALYSIS,true, methodCallingCode)
            currentStatements.add(0, methodCallNodes[0].statements[0])
        }
    }
}


@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass(["annotations.SpringSecurityInvocationTransformation"])
public @interface SpringSecurityInvocation {
    String securityMethod();
}
It might have made more sense to do it over each method from the interface that the class implemented (seeing as it was an implemention of a RMI-exported interface). We can then invoke this using a class definition like:
import annotations.SpringSecurityInvocation

//this annotation injects the checkSecurity method at the start of
//every public non-static method for this class
@SpringSecurityInvocation(securityMethod="checkSecurity")
public class SampleRMIInterfaceImplementation implements SampleRMIInterfaceImplementation {
    private void checkSecurity() {
        //do security stuff
        //throw exception if invalid security
    }

    public void someRMIOperation() {
        //do stuff
    }
}
In this case someRMIOperation will first call the checkSecurity() method.

No comments:

Post a Comment