Wednesday, January 6, 2010

Creating a custom (multi module) Maven archetype

In the last couple of days I was working on creating a custom Maven archetype for a multi module project. I used the Maven archetype plug in version 2.0-alpha-4.

What I wanted to do

When I write a component or module, I usually use a maven project structure consisting of (at least) two sub projects. One contains the abstract API of the component, the other the concrete implementation:

component
|--pom.xml
|--component.api
|  `--pom.xml
`--component.imp 
   `--pom.xml

The implementation project references the api project using maven’s property mechanism to keep version and group ID in synch.

<dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>component.api</artifactId>
    <version>${project.version}</version>
</dependency>

Both sub projects inherit from the top level pom:

<parent>
    <groupId>my.groupId</groupId>
    <artifactId>component</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

And the top level pom specifies the api and imp project as modules:

<modules>
    <module>component.api</module> 
    <module>component.imp</module>
</modules>

So I basically wanted to have a custom archetype that generates this kind of project structure for me. As I have stumbled over various issues, I have collected all of my experiences during this task in this blog entry.

Creating from an existing project

Using archetype:create-from-project, you can create a working archetype from an existing project. Unfortunately it did not work for my existing projects out of the box. However, after building an extremely simplified example project, that didn’t have a parent element and not so many (actually none) properties in the pom, it worked. The plugin generates a basic project structure for your archetype project, all necessary files and extracts properties like version, groupId and name. The generated archetype is absolutely sufficient, when your archetype should just generate a skeleton project that contains all necessary dependencies. If you want to provide additional things like correctly packaged boilerplate code, you have to spend some extra effort. But the generated archetype is still a valuable starting point then.

The archetype project layout

The project layout for a maven archetype project looks like this:

archetype-project
|-- pom.xml
`-- src
    `-- main
        `-- resources
            |-- META-INF
            |    `—maven
            |        |-- archetype.xml
            |        `-- archetype-metadata.xml
            `-- archetype-resources
                 |-- pom.xml
                 `-- ...

The top level archetype-project pom

The top level pom declares the standard properties (groupId, artifatcId,..) for the archetype project and pulls in the desired version of the archetype plugin:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.amutech.archtypes</groupId>
  <artifactId>component-archetype-simple</artifactId>
  <version>1.0</version>
  <packaging>maven-archetype</packaging>
  <name>component-archetype-simple</name>
  <build>
    <extensions>
      <extension>
        <groupId>org.apache.maven.archetype</groupId>
        <artifactId>archetype-packaging</artifactId>
        <version>2.0-alpha-4</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <artifactId>maven-archetype-plugin</artifactId>
        <version>2.0-alpha-4</version>
        <extensions>true</extensions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
         <configuration>
             <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
    </plugins>
  </build>
</project>

The archetype-resources folder

This folders contains all files and templates needed to create the new project from the archetype. Basically it represents the projects future structure. For a single project it might look like this:

archetype-resources
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- SomeClass.java
    `-- test
        `-- java
            `—SomeClassTest.java

For a multi module project could look like this:

archetype-resources
|-- pom.xml
|-- module1
|   `-- src
|       |-- main
|       |   `-- java
|       |       `-- AClass1.java
|       `-- test
|           `-- java
|               `-- AClass1Test.java
|
`-- module2
    `-- src
        |-- main
        |   `-- java
        |       `-- AClass2.java
        `-- test
            `-- java
                `-- AClass2Test.java

The archetype-xml archetype descriptor

This files specifies all files that are are used for the project creation by the archetype. The basic form of the xml looks like this:

<archetype>
  <id>archetype-id</id>
  <sources>
    <source>src/main/..</source>
  </sources>
  <testSources>
    <source>src/test/..</source>
  </testSources>
  <resources>
    <resource>src/test/..</resource>
  </resources>
  <testSources>
    <testResource>src/test/resources/..</testResource>
  </testSources>
  <siteResources>
    <siteResource>src/site/..</siteResource>
  </siteResources>
</archetype>

The documentation states that these tags represent the different section of the project. This is correct for a single module, but not exactly for a multi module archetypes. For instance, a source file inside of module2 has to be specified like this:

<resources>
    <resource>module2/src/main/java/AClass1.java</resource>
</resources>

This is because the top level folder is named “module2”, not “src”.  Trying to declare it as a source file will lead to an error when executing the archetype.

The archetype.metadata.xml file

This file describes what will be done with the files specified in the archetype descriptor. The documentation on the plug in site is a good reference material. You basically define file sets, specify where they will be located and if they will be filtered in several ways.

Property replacement in files

Maven seem to use velocity as a template engine internally, as the property replacement syntax look exactly like that. In any file, a statement like ${thePropertyName} will be replaced with the corresponding value. For instance: If your archetypes groupId is “my.groupId”,  all occurrences of ${groupId} will be replaced with this value. This applies for all files that are specified to be filtered inside the archetype-metadata.xml file:

<fileSet filtered="true" packaged="true" encoding="UTF-8">
    <directory>src/main/java</directory>
    <includes>
        <include>**/*.java</include>
    </includes>
</fileSet>

Property replacement in files and folder names

By using __propertyName__ inside or a file or folder name, the corresponding property value will be inserted there. For instance, a file named __groupId__-specialFile.xml will be renamed to my.groupId-specialFile.xml when the archetype is executed.

Extra Properties for multi module archetypes

There are some extra properties for multi module archetypes:

rootArtifactId The artifactId of the that the root project will  have. this is the artifactId that is specified when the archetype is used to create anew project
parentArtifact Inside a sub project, this is the artifact id of the parents project
Escaping

Velocity uses the "$” sign as an identifier. Some artifacts that the archetype needs to generate are velocity templates as well, e.g. pom files using ${project.artifactId} properties. They can be escaped by placing a #set( $symbol_dollar = '$' ) at the beginning of a file:

#set( $symbol_dollar = '$' )
<project>
    <name>${symbol_dollar}{project.artifactId}</name>
</project>

In general, you can escape any special character like this.

Correct packaging of java classes

In my archetype, I wanted to provide some boilerplate and sample Java code to demonstrate the intended packaging and naming convention of my components. The property to use inside a file for the correct package to be inserted is ${package}. That is intuitive! However, if I want maven to create the corresponding folder structure as well,you have to specify this on the file set inside the archetype-metadata.xml:

<fileSet filtered=true packaged="true" encoding="UTF-8">
    <directory>src/main/java</directory>
    <includes>
        <include>**/*.java</include>
    </includes>
</fileSet>

I also wanted some specific sub packages to be created as well. For instance, the imp, project has a package ${package}.imp. In order to have the folder structure generated correctly, I had to place the template java files in an imp sub directory, as the package statement just uses the package property and does not scan the filtered source files.

|
`—component.imp 
    `-- src
        `—main 
           `--java 
               `—imp
                 `-- AClass2.java