Install this theme
Generating boilerplate code with Maven and Groovy

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:

  • This script processes all java sources found in the directory specified in the restResourcesPath. For simplicity reasons it assumes that all java sources found there are Jersey Resources. However it is very easy to customize the regular expression to match only the sources you are interested in.
  • Each found java source file will be parsed using wonderful Java 1.5 Parser and AST library
  • Using the generated AST groovy script creates the corresponding RestyGWT interface.

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:

  • restResourcesPath - path where to find Jersey REST resources (java classes)
  • restAsyncResourcesPath - path where to generate RestyGWT resources (interfaces)
  • restAsyncResourcesPackage - java package to use in generated RestyGWT interfaces
  • restURLBase - base REST resources URL to generate into the interfaces
  • importsRegexp - regular expression of imports allowed within the generated RestyGWT interfaces

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

 
Blog comments powered by Disqus