Thursday, October 1, 2009

Maven Archetype for App Engine

I have developed a maven archetype for Google App Engine projects. The generated project supports:
  • All dependency jar files are downloaded from maven repositories and copied to lib directory as required by App Engine Eclipse plugin, and local development server.

  • Eclipse project is created with mvn eclipse:eclipse. The resulting Eclipse project has the necessary settings for App Engine Eclipse plugin.

  • Entity classes are processed by DataNucleus enhancer in the build lifecycle.

  • JUnit tests with local App Engine environment can be run from maven.

Setting up all of this is not trivial and therefore I would like to share the solution and I hope you find it useful.

Eclipse Project
The maven eclipse plugin need a lot of configuration.
<build>
<outputDirectory>war/WEB-INF/classes</outputDirectory>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<!--
buildOutputDirectory doesn't work due to
http://jira.codehaus.org/browse/MECLIPSE-422 An workaround is the
outputDirectory at project/build level
<buildOutputDirectory>war/WEB-INF/classes</buildOutputDirectory>
-->
<testOutputDirectory>target/test-classes</testOutputDirectory>
<classpathContainers>
<classpathContainer>com.google.appengine.eclipse.core.GAE_CONTAINER</classpathContainer>
</classpathContainers>
<buildcommands>
<buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
<buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>
<buildcommand>com.google.appengine.eclipse.core.enhancerbuilder</buildcommand>
<buildcommand>com.google.appengine.eclipse.core.projectValidator</buildcommand>
</buildcommands>
<additionalProjectnatures>
<projectnature>org.eclipse.jdt.core.javanature</projectnature>
<projectnature>com.google.appengine.eclipse.core.gaeNature</projectnature>
<projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>
</additionalProjectnatures>
<excludes>
<!-- Included in GAE_CONTAINER -->
<exclude>com.google.appengine:appengine-api-1.0-sdk</exclude>
<exclude>com.google.appengine:appengine-api-1.0-labs</exclude>
<exclude>com.google.appengine.orm:datanucleus-appengine</exclude>
<exclude>org.datanucleus:datanucleus-jpa</exclude>
<exclude>org.datanucleus:datanucleus-core</exclude>
<exclude>org.apache.geronimo.specs:geronimo-jpa_3.0_spec</exclude>
<exclude>org.apache.geronimo.specs:geronimo-jta_1.1_spec</exclude>
<exclude>javax.jdo:jdo2-api</exclude>
</excludes>
</configuration>
</plugin>


Some dependencies must be excluded, since they are part of GAE_CONTAINER, otherwise JUnit tests will not work when running inside Eclipse. The output directory is changed to war/WEB-INF/classes. There is a bug (MECLIPSE-422) which cause the test classes to not be separated if buildOutputDirectory is used. The local development server doesn't like the test classes. The trick is to define the output at the top build level and define testOutputDirectory.

Copy Dependencies
When running the local development server and deploying to App Engine all dependent jar files must be located in war/WEB-INF/lib. I have used the maven dependency plugin to copy the jar files during the maven clean phase.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>clean</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<!-- more ... -->
<artifactItem>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-1.0-sdk</artifactId>
<version>${appengine.version}</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-1.0-labs</artifactId>
<version>${appengine.version}</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>com.google.appengine.orm</groupId>
<artifactId>datanucleus-appengine</artifactId>
<version>1.0.3</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.datanucleus</groupId>
<artifactId>datanucleus-jpa</artifactId>
<version>1.1.5</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.datanucleus</groupId>
<artifactId>datanucleus-core</artifactId>
<version>1.1.5</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jpa_3.0_spec</artifactId>
<version>1.1.1</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jta_1.1_spec</artifactId>
<version>1.1.1</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>javax.jdo</groupId>
<artifactId>jdo2-api</artifactId>
<version>2.3-eb</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>
</artifactItems>
<!-- other configurations here -->
</configuration>
</execution>
</executions>
</plugin>



DataNucleus Enhancer
Running the JUnit tests from maven was a primary goal as I would like to run tests from continous build server. Th JUnit tests are using local App Engine environment with in-memory datastore. Therefore the classes must be processed by DataNucleus enhancer after ordinary compilation.

<plugin>
<groupId>org.datanucleus</groupId>
<artifactId>maven-datanucleus-plugin</artifactId>
<version>1.1.4</version>
<configuration>
<api>JPA</api>
<mappingIncludes>**/*.class</mappingIncludes>
<log4jConfiguration>${basedir}/src/main/resources/log4j.properties</log4jConfiguration>
<verbose>false</verbose>
</configuration>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>


Archetype
All of this is packaged in a maven archetype. Try it like this.
  1. mvn archetype:generate -DarchetypeGroupId=org.fornax.cartridges -DarchetypeArtifactId=fornax-cartridges-sculptor-archetype-appengine -DarchetypeVersion=1.7.0-SNAPSHOT -DarchetypeRepository=http://www.fornax-platform.org/archiva/repository/snapshots/

  2. cd to the new directory

  3. mvn clean

  4. mvn eclipse:eclipse

  5. Import the project in Eclipse

As an extra bonus your new project is configured for Spring 3.0 with a sample of a RESTful controller.

Sculptor code generator tool is of course also configured and ready to be used in the new project. I will soon write another article about Sculptor's support for App Engine.

3 comments:

  1. Solid example of a maven build. I spent the last couple of hours trying to setup a generic php/java/gae project. I was wondering if you think it would be straight forward to setup a maven repository for such a useful template, as I have one configured for lift/scala on the gae and enjoy using it.

    ReplyDelete
  2. Vote here if you are looking for maven support in GAE
    http://code.google.com/p/googleappengine/issues/detail?id=1296

    ReplyDelete
  3. Great work! This is exactly what I needed.

    ReplyDelete