In every project, there are frequently code fragments, one would like to generate on builtime instead to code by hand. This post demonstrates how to generate Java sources with Groovy as part of the Maven build lifecycle without much effort.
As an example I’ll take the RestyGWT interfaces from one of the previous posts (see GWT: Creating REST Services with Jersey and RestyGWT) and show how to generate them on build time.
Note! Using Groovy scripts it is possible to generate nearly any kind of required project sources / resouces.
First, consider a server-side REST resource and associated RestyGWT interface described in the referred post.
Jersey REST-Resource
@Path("/person") public class PersonResource { @GET @Produces(MediaType.APPLICATION_JSON) public List getPersons() { ... } }
RestyGWT interface
public interface PersonResourceAsync extends RestService { @GET void getPersons(MethodCallback callback); /** * Utility class to get the instance of the Rest Service */ public static final class Util { private static PersonResourceAsync instance; public static final PersonResourceAsync get() { if (instance == null) { instance = GWT.create(PersonResourceAsync.class); ((RestServiceProxy) instance).setResource(new Resource( "rest/person")); } return instance; } private Util() { // Utility class should not be instantiated } } }
The second one should be automatically generated.
First, we write a generic groovy script that is able to generate RestyGWT interfaces from the corresponding Jersey Resources.
GenerateRestAsyncResources.groovy
import japa.parser.* import japa.parser.ast.* import japa.parser.ast.body.MethodDeclaration; import japa.parser.ast.body.ModifierSet; import japa.parser.ast.body.TypeDeclaration; def engine = new groovy.text.SimpleTemplateEngine() def utilClassTmpl = engine.createTemplate(''' /** * Utility class to get the instance of the Rest Service */ public static final class Util { private static ${asyncResourceClass} instance; public static final ${asyncResourceClass} get() { if (instance == null) { instance = com.google.gwt.core.client.GWT.create(${asyncResourceClass}.class); ((org.fusesource.restygwt.client.RestServiceProxy) instance).setResource( new org.fusesource.restygwt.client.Resource(${asyncResourcePath}) ); } return instance; } private Util() { // Utility class should not be instantiated } } ''') def methodTmpl = engine.createTemplate(''' ${methodAnnotations} public void ${methodName}(${methodParams}${methodCallback});\n ''') def restAsyncResourcesPath = project.properties['restAsyncResourcesPath'] def restAsyncResourcesPackage = project.properties['restAsyncResourcesPackage'] def restResourcesPath = project.properties['restResourcesPath'] def restURLBase = project.properties['restURLBase'] def importsRegexp = ~project.properties['importsRegexp'] new File(restResourcesPath).eachFileMatch( ~/.*?\.java/) { file-> println "Reading: ${file.absolutePath}" FileInputStream _in = new FileInputStream(file) CompilationUnit cu; try { cu = JavaParser.parse(_in) TypeDeclaration resource = cu.types.first(); def pathAnnotation = resource.annotations.find { it instanceof japa.parser.ast.expr.SingleMemberAnnotationExpr && it.name.toString().equals("Path") } def asyncResourcePath = "\"${restURLBase}${pathAnnotation.memberValue.toString()[1..-1]}" def asyncResourceClass = "${resource.name}Async" println "Writing: ${asyncResourceClass}.java" File baseDir = new File (restAsyncResourcesPath); baseDir.mkdirs(); File target = new File(baseDir, "${asyncResourceClass}.java") if ( target.exists()) target.delete(); target << ("package ${restAsyncResourcesPackage};\n\n") cu.imports.each(){ if ( importsRegexp.matcher(it.name.toString()).matches()) target << ("$it") } target << ( "\n") target << ( "public interface $asyncResourceClass extends org.fusesource.restygwt.client.RestService {" ) target << ( "\n") resource.members.each(){ member -> if (member instanceof MethodDeclaration && ModifierSet.isPublic(member.modifiers)){ def mAnnotations = "" member.annotations.each { ann -> mAnnotations += "${ann} " } def methodParams = "" member.parameters.each { param -> methodParams += "${param}, " } def methodCallback = "org.fusesource.restygwt.client.MethodCallback<" + (member.type instanceof japa.parser.ast.type.VoidType ? "Void" : member.type.toString()) + "> callback" target << (methodTmpl.make([ "methodAnnotations": mAnnotations, "methodName" :member.name.toString(), "methodParams": methodParams, "methodCallback": methodCallback ]).toString()) } } target << (utilClassTmpl.make([ "asyncResourceClass" : asyncResourceClass, "asyncResourcePath": asyncResourcePath ]).toString()) target << ( "}" ) } catch (Throwable t){ t.printStackTrace() } finally { _in.close(); } }
Some explanations:
To run this script during the Maven build, we have to add following configuration into the pom.xml :
<build> <plugins> ... <plugin> <groupId>org.codehaus.gmaven</groupId> <artifactId>gmaven-plugin</artifactId> <version>1.4</version> <executions> <execution> <id>create-async-rest-services</id> <phase>generate-sources</phase> <goals> <goal>execute</goal> </goals> <configuration> <properties> <restResourcesPath> ${basedir}/src/main/java/net/javaforge/gwt/rest/server/remote/rest </restResourcesPath> <restAsyncResourcesPath> ${basedir}/src/gen/java/net/javaforge/gwt/rest/client/remote/rest </restAsyncResourcesPath> <restAsyncResourcesPackage> net.javaforge.gwt.rest.client.remote.rest </restAsyncResourcesPackage> <restURLBase>rest</restURLBase> <importsRegexp> net\.javaforge\..*|java\..*|javax\.ws\.rs.* </importsRegexp> </properties> <classpath> <element> <groupId>com.google.code.javaparser</groupId> <artifactId>javaparser</artifactId> <version>1.0.8</version> </element> </classpath> <source>${basedir}/GenerateRestAsyncResources.groovy</source> </configuration> </execution> </executions> </plugin> ... </plugins> </build>
This configuration will run our script during the maven’s generate-sources phase and pass following properties to it:
The last thing we need to create the build-helper-maven-plugin configuration to add the generated sources to the maven build.
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.7</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>${basedir}/src/gen/java</source> </sources> </configuration> </execution> </executions> </plugin>
Check out the complete example project on GitHub: https://github.com/bigpuritz/javaforge-blog/tree/master/restgwt-groovy