Friday 9 September 2011

Groovy injecting JAX-WS annotations to interfaces for CXF web services

So having done some Spring development using CXF web services I found that interface method parameters were named arg0, arg1 etc in the WSDL. This is ugly but it seems that there isn't an elegant way of specifying parameter names using the basic CXF front end module (JAX-WS annotations do not work). So I switched to using JaxWsServerFactoryBean (with the security interceptors I discussed in a previous post). But this meant annotating the interface methods and parameters with JAX-WS annotations which was tedious and gross. So Groovy to the rescue with an AST transform that injects the annotations so that when the Java CXF code reads the interface it sees annotations. So the local AST transform iterates through every method in the interface and decorates the method with a javax.jws.WebMethod annotation containing the name of the method. It then iterates through the method's parameters and adds a javax.jws.WebParam annotation with the name of the parameter (this fixes the arg0, arg1 issue).
package annotations

import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.expr.ConstantExpression

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class WSAnnotationTransformation implements ASTTransformation {
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
        def interfaceNode = nodes[1] //specified in documentation
        def interfaceMethods = interfaceNode.getMethods()
        interfaceMethods.each { MethodNode methodNode ->

            //add an annotation for the method web operation
            ClassNode webMethod = new ClassNode(javax.jws.WebMethod)
            AnnotationNode methodAnnotation = new AnnotationNode(webMethod)
            methodAnnotation.addMember("operationName",new ConstantExpression(methodNode.getName()))
            methodNode.addAnnotation(methodAnnotation)

            //add the annotation for the parameter names
            methodNode.parameters.each { parameter ->
                ClassNode webParam = new ClassNode(javax.jws.WebParam)
                AnnotationNode parameterAnnotation = new AnnotationNode(webParam)
                parameterAnnotation.addMember("name",new ConstantExpression(parameter.getName()))
                parameter.addAnnotation(parameterAnnotation)
            }
        }
    }
}

@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass(["annotations.WSAnnotationTransformation"])
public @interface ParameterAnnotationInjection {
}
We can then decorate the interface with the annotation: @ParameterAnnotationInjection e.g.
package webservices

import annotations.ParameterAnnotationInjection

@ParameterAnnotationInjection
public interface DigitizerService {
     void doSomething(String a, String b, String c);
     void somethingElse(String a, String b, String c);
}
Which returns a XSD schema like:

    
        
        
        
    


    
        
        
        
    

Also, the Eclipse-Groovy Maven plugin had trouble with the annotation and compilation order, so I added an ant task to force the compile of the annotation before calling the Eclipse-Groovy Maven plugin to do the heavy lifting:
            
                maven-antrun-plugin
                
                    
                        process-sources
                        process-sources
                        
                            
                                
                                
                                         
                                
                                
                                
                                    
                                
                            
                        
                        
                            run
                        
                    
                
            

No comments:

Post a Comment