Thursday, April 12, 2007

From maven to mvn : Part 7 -- A Groovier Timestamp

With a bit of help from the maven users list I've sorted out the details of creating a maven plugin written in Groovy. It isn't as clean or elegant as I had hoped but things are still brewing so I suspect they will only get better. I'm told that there is a discussion about this on the Groovy mailing list as well.

Before I continue I would like to thank Dennis Lundberg and Martin Gilday on the maven users list. You can see our conversation here. Dennis turned me on to the groovy compiler and Martin's blog entry got me hooked up with javalike-maven-plugin-tools for generating my project descriptor.

So... Let's get down to it and figure out how to write a simple plugin in Groovy. My goal will be to replace my earlier attempt with a compiled Groovy class.

To begin with, let's take a look at TimestampMojo.groovy:
/**
* Set the current time into a system property for use by resource
* filtering.
*
* @goal execute
* @phase generate-resources
* @requiresDependencyResolution runtime
*/
class TimestampMojo extends AbstractMojo
{
/**
* The System property which will hold the current time.
*
* @parameter default-value="timestamp"
*/
String propertyName;

/**
* The format string given to MessageFormat.format()
*
* @parameter default-value="{0,date,yyyy-MM-dd HH:mm:ss}"
*/
String formatString;

public void execute()
throws MojoExecutionException
{
String timestamp = MessageFormat.format(formatString, new Date())
System.setProperty(propertyName, timestamp)
}
}
Well, that's simple enough. Note that there appear to be quite a few extra semi-colons for a Groovy script. It turns out that they're necessary because of the javalike plugin we will use to build the plugin descriptor. Martin's blog entry goes into that in detail so I won't repeat it here.

Next we need a pom to build the plugin. It's a bit large so I'll break it down into bite-sized pieces beginning with the typical header parts:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>myco.util.mvn.timestamp-plugin</groupId>
<artifactId>myco-timestamp-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.1-SNAPSHOT</version>

<name>myco-timestamp-plugin Maven Mojo</name>
No surprises there so let's move on to specify some extra repositories. We do this because some of the things we need are hosted at Codehaus rather than the default repository.
  <repositories>
<repository>
<id>Codehaus</id>
<url>http://repository.codehaus.org/org/codehaus/mojo/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>Snapshots</id>
<url>http://repository.codehaus.org/org/codehaus/mojo/</url>
</pluginRepository>
</pluginRepositories>
And now we come to the build section where we define the plugins we need in order to build our plugin. However, we first need to tell the groovy plugin where it can find it's sources:
  <build>
<sourceDirectory>${basedir}/src/main/groovy</sourceDirectory>
Did someone say groovy plugin? Yup. If we're going to write the source as groovy then we need some way to compile that into a .class...
    <plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
Remember the annotations in my *.groovy file above? Well, we're going to use the plugin plugin to turn those into a plugin descriptor. Out of the box that plugin doesn't know anything about groovy, though, so we use javalike-maven-plugin-tools to enhance it a bit. As of this writting (April 12, 2007) the javalike plugin hasn't escaped from the Codehaus mojo sandbox so you'll have to check it out of their svn repo and build it yourself. Martin's blog covers that but I'll repeat it here for convenience:
svn co http://svn.codehaus.org/mojo/trunk/mojo \
/mojo-sandbox/groovy-maven-tools/javalike-maven-plugin-tools/

      <plugin>
<artifactId>maven-plugin-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>
javalike-maven-plugin-tools
</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Now that we have our plugins defined we need to define our dependencies. Of course, the plugins will pull in whatever they need but because somebody is going to use us as a plugin (we hope) we need to include groovy in our dependencies so that that somebody will get it (transitively) when they build.
  <dependencies>
<dependency>
<groupId>groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
And that's it! Drop the above into a normal maven project structure (remember, TimestampMojo.groovy goes into src/main/groovy). Compile and install and you're ready to go.



Using the plugin is as simple as using any other. For this kind of thing I would recommend putting it into your ancestral pom.xml that all of your other projects extend but that's a personal choice. In any case, usage looks like this:
    <plugin>
<groupId>myco.util.mvn.timestamp-plugin</groupId>
<artifactId>myco-timestamp-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>execute</goal>
</goals>
</execution>
</executions>
</plugin>
The execute goal is tied to the generate-resources lifecycle phase so that the property will be available to you prior to copying of resources. This means that any ${timestamp} in any filtered resource will be replaced by the current timestamp. If you don't remember how to setup filtered resources, well, neither do I so here it is again:
  <resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
And if you want to filter your jsp's before creating the war file (I like to have a ${timestamp} in my footer.jsp...) then you want something like this:
    <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>
${basedir}/src/main/webapp
</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/footer.jsp</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
Remember these bits are in the pom of the application using the timestamp plugin, not in the pom of the plugin itself.

1 comment:

James CE Johnson said...

Update...

I was rebuilding my timestamp plugin today and found that things have changed in the maven groovy wiring.

Instead of the javalike stuff, we now have groovy-mojo-tools. Thus, the plugins section of your pom would look like this:

<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-mojo-tools</artifactId>
<version>1.0-alpha-3-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>

Also worth noting... You can download and build the groovy support via:

  svn co https://svn.codehaus.org/mojo/trunk/mojo/groovy/
  cd groovy
  mvn clean install