Thursday, December 28, 2006

Maven Plugin Writing Intro

(this post is an excerpt from the upcoming book Java Power Tools... no reproductions without permission)

Using Plugins to Customize the Build Process

If there has been one substantial improvement between Maven 1 and Maven 2, it is the simplicity and flexibility of extending the default execution set with custom plugins. One can even write plugins in other programming languages, such as Jruby, Groovy, or Ant; however we will turn our focus to the most heavily used and supported default language: Java.

A Maven plugin is a collection of goals and, as mentioned in previous articles, a goal is a unit of work in the Maven build lifecycle. Maven comes with tools allowing you to easily create and install your own goals. This allows you to extend the default build lifecycle in any way you can imagine - like Ant tasks, if you are so inclined to draw the comparison - but with the benefits of the well-defined lifecycle and network portability of Maven.

In order to create a simple plugin through the archetype, type the following in your command line:

mvn archetype:create -DgroupId=my.plugin \
    -DartifactId=maven-my-plugin \
    -DarchetypeGroupId=org.apache.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-mojo \
    -DarchetypeVersion=1.0

In Maven, the implementation of a goal is done in a Mojo – a play-on-words meaning Maven POJO (Plain Old Java Object) and, well, mojo. All Java Maven Mojos implement the org.apache.maven.plugins.Mojo interface. Without getting too detailed, it is good to understand that Maven is built upon the inversion of control (IoC) container / dependency injection (DI) framework called Plexus. If you are familiar with Spring, you are good way there to understanding Plexus. Plexus is built around the concept of components that each play a "role", and each role has an implementation. The role name tends to be the fully qualified name of the interface. Like Spring, Plexus components (think Spring beans) are defined in an XML file. In Plexus that XML file is named components.xml and lives in META-INF/plexus. The consumers of a component need not know the role's implementation, as that is managed by the Plexus DI framework. When you create your own Mojo implementation, you are effectively creating your own component that implements the org.apache.maven.plugins.Mojo role.

So – you may be thinking – what does this have to do with Maven goals? When you create a Mojo class, you annotate the class with certain values; those values are then used to generate a variant of the Plexus components.xml file named plugin.xml living under META-INF/maven. So what translates those annotations to a plugin.xml file? Maven goals, of course! Your maven-plugin packaging project's build lifecycle binds goals that generate the descriptor for you. In short, non-jargon speak: Maven does that work for you.

In the plugin you generated above, navigate to the maven-my-plugin/src/main/java/my/plugin/MyMojo.java file and set the contents to the following:

package my.plugin;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

/**
* A simple Mojo.
* @goal my-goal
*/
public class MyMojo extends AbstractMojo
{
/**
* This populates the message to print.
* @parameter required default-value="No message set"
*/
private String message;

public void execute()
throws MojoExecutionException
{
getLog().info( message );
}
}

Now install the plugin vis the normal Maven method; type:

mvn install

The execute method is solely responsible for executing the goal - any other methods you encounter in a Mojo are just helper methods. Maven injects values into the Mojo object directly into the project’s fields. In the example above the "message" field is a valid Maven property and is printed out the logger returned by the getLog method. Remember that Plexus is a "dependency injection" framework. Since we annotated the message field as a parameter, that parameter can now be populated by Maven (via Plexus). You can populate your goal in the same way you do any goal, through the configuration element in the POM:

<project>
<build>
<plugins>
<plugin>
<groupid>my.plugin</groupid>
<artifactid>maven-my-plugin</artifactid>
<configuration>
<message>Hello World!</message>
<configuration>

This sets the configuration for all goals under maven-my-plugin. Remember, a plugin can contain multiple goals, one per Mojo in the project. If you wish to configure a specific goal, you can create an execution. An execution is a configured set of goals to be executed.

<project>
<build>
<plugins>
<plugin>
<groupId>my.plugin</groupId>
<artifactId>maven-my-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>my-goal</goal>
</goals>
<configuration>
<message>Hello World!</message>
<configuration>

In either case, you execute the goal in the same way:

mvn my.plugin:maven-my-plugin:my-goal

Hello World!

You may wonder why so much typing when the create goal we ran was only archetype:create? That’s because the archetype goal has the groupId org.apache.maven.plugins, which is tried as a possible prefix by default when none is provided. You can add more plugin groups to your system by added this to your .m2/settings.xml file:

<settings>
...
<plugingroups>
<plugingroup>my.plugin</plugingroup>
</plugingroups>
</settings>

Furthermore, if your plugin name is surrounded by maven-*-plugin, Maven will allow you to simply type the name in the middle. Since we have already done this, you can now just run the much simpler goal:

mvn my:my-goal

The final way to configure a goal is via a property. You can set an expression to populate the property rather than a direct value.

@parameter expression="${my.message}"

This gives you the flexibility to set the property within the POM, in the settings.xml, or even on the command line… anywhere you can set a property in Maven.

mvn my:my-goal -Dmy.message=Hello

Prints:

[INFO] Hello

Manipulating the Build Lifecycle

Creating goals is great, however that alone is hardly much better than just creating Ant tasks. In order to benefit from Maven’s well-defined build lifecycle, it often makes sense to put your goal into the lifecycle somehow. In rare cases, your plugin may need to create its own lifecycle definition.

There are two major ways in which to bind a goal to a lifecycle. The first is to just add the goal to an execution phase, defined in your running project’s POM.

<project>
<build>
<plugins>
<plugin>
<groupId>my.plugin</groupId>
<artifactId>maven-my-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>my-goal</goal>
</goals>
<configuration>
<message>I am validating</message>
<configuration>

Running

mvn validate
will print the configured message. Oftentimes a goal will be created with a specific phase in mind. When creating your Mojo, you can define a phase that the goal will run in. Add the following annotation to the my-goal goal and install the plugin via "mvn install".

@phase validate

Now you need only add the plugin to your POM configuration, and the my:my-goal goal will be bound to the validate phase for you.

<project>
<build>
<plugins>
<plugin>
<groupid>my.plugin</groupid>
<artifactid>maven-my-plugin</artifactid>

Another way to manipulate the build lifecycle is to create your own forked lifecycle. You can tell the Mojo to execute the forked lifecycle up to a given phase. If you do not set the lifecycle, then the default is used, but if you do, you must provide a definition of that new lifecycle.

@execute phase="validate" lifecycle="mycycle"

You define the mycycle build lifecycle in a META-INF/maven/lifecycle.xml file. The following lifecycle executes the my-goal goal (only once, not recursively) in the validate phase.

<lifecycles>
<lifecycle>
<id>mycycle</id>
<phases>
<phase>
<id>validate</id>
<executions>
<execution>
<goals>
<goal>my-goal</goal>
</goals>
<configuration>
<message>I am forked</message>
</configuration>
</execution>
</executions>
</phase>
</phases>
</lifecycle>
</lifecycles>

When combined with the above POM configuration, it will execute two validate phases:

[INFO] I am forked
...
[INFO] No message set

Hooking into Maven

The simplest way to hook into the Maven runtime is to create parameters populated by Maven parameters. Some commonly used parameters are as follows:

The current project (the POM data):

/**
* @parameter expression="${project}"
*/
private org.apache.maven.project.MavenProject project;

The current version:

/**
* @parameter expression="${project.version}"
*/
private String version;

A similar method may be used for getting simple properties from the POM, such as project.groupId/project.artifactId/project.url. Another commonly required parameter is the project’s build directory.

/**
* @parameter expression="${project.build.directory}"
*/
private java.io.File outputDirectory;

More complex values can be acquired with the following parameter names of the following types.

${project.build} =>
org.apache.maven.model.Build
${project.ciManagement} =>
org.apache.maven.model.CiManagement
${project.dependency} =>
org.apache.maven.model.Dependency
${project.dependencyManagement} =>
org.apache.maven.model.DependencyManagement
${project.distributionManagement} =>
org.apache.maven.model.DistributionManagement
${project.issueManagement} =>
org.apache.maven.model.IssueManagement
${project.license} =>
org.apache.maven.model.License
${project.mailingList} =>
org.apache.maven.model.MailingList
${project.organization} =>
org.apache.maven.model.Organization
${project.reporting} =>
org.apache.maven.model.Reporting
${project.scm} =>
org.apache.maven.model.Scm

The local repository:

/**
* @parameter expression="${localRepository}"
*/
private org.apache.maven.artifact.repository.ArtifactRepository localRepository;

Using Plexus Components

As mentioned above, Maven is built upon Plexus, which is an IoC container that manages components. There are components in Plexus that you may wish to use in your Mojos, for example, the Plexus JarArchiver class:

/**
* @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
* @required
*/
private org.codehaus.plexus.archiver.jar.JarArchiver jarArchiver;

Just like other Maven expressions, these values can be injected from components when the parameter is prefixed with "component." followed by the Plexus role name. If the role can play varying roles, you can pinpoint that role via the role-hint, specified by "#jar", or "#zip", or whatever that role-hint may be.

Many plexus components exist in the Maven repository, and can be used in a similar way, for example the i18n component:

/**
* @parameter expression="${component. org.codehaus.plexus.i18n.I18N}"
* @required
* @readonly
*/
private org.codehaus.plexus.i18n.I18N i18n;

See the list at http://repo1.maven.org/maven2/org/codehaus/plexus/

Obviously your goals can and probably will be far more complicated than the examples shown, but you have been given the basic tools to start writing your own plugins, utilizing full control of Maven.

Tuesday, December 26, 2006

Apache Archiva - Part One

Sure, you love Maven. We all do. If used correctly it can simplify even your ginourmous build processes, or I'll eat my hat (metaphorically - I no longer wear a hat). But managing your own remote repositories is still a pain for any organization. Enter: Archiva. Let's take a look at this repository management tool from the folks who brought you Maven - as well as the dark underbelly of managing large public repositories.

Archiva is not yet available for download, but it is becomming fairly stable, so here is a sneaky-peeky. First, download it via Subversion:
svn co \
http://svn.apache.org/repos/asf/maven/archiva/trunk \
archiva
Now, an annoying part of javax not yet in any repositories, is that you must manually install a required artifact first. JavaX Connector version 1.0. It can be downloaded from http://java.sun.com/j2ee/connector/download.html, then installed via:
mvn install:install-file -DgroupId=javax.resource \
-DartifactId=connector \
-Dversion=1.0 \
-Dpackaging=jar \
-Dfile=connector.jar
Where "connector.jar" includes the path to the connector.jar you just downloaded. That should be the hardest part of the setup. Next, in the base archiva directory, run
mvn install
If any tests fail (it is trunk afterall), try and run with
mvn -Dmaven.test.skip install
Once successful, cd to the runtime's freshly built bin directory under $ARCHIVA_BASE/archiva-plexus-runtime's target:
target/plexus-archiva-runtime/bin/$MY_OS
Where $MY_OS is the operating system you think you're running. Then, execute the run script within it. If you are running windows, you can install Plexus as a service via the win32/InstallService.bat script and will find a new service installed in your Control Panel's Services list. Other operating systems can add the run.sh script as part of its startup routine.

After you have the server running or installed, navigate your web browser of choice to http://localhost:8080. If this is the first time running Archiva, you will be confronted with a screen requesting you to create an admin user. After submission, you will be required to log in as an administrator with that information.

Once you are logged in, you will be required to create at least one managed repository. The Maven standard is to create at least two repositories, one for development SNAPSHOT artifacts, and one for release artifacts. However, you can create as many repositories as you wish, for example testing and staging releases. We recommend giving the URL extensions and identifier the same value, to avoid confusion. Note that you can create a repository of types other than Maven 2, but also Maven 1.

Directory is the absolute path where this repository will place its artifacts. If you are installing on a Windows-based machine, note that since Windows has an upper-limit on directory + file -names, it is best to make the directory fairly short. "Snapshots Included" means just as it sounds - the repository will make snapshots available.

Once you have created a configuration, you need to set up a schedule for when the Archiva repositories will index (it defaults to every 3 minutes), and what directory the index should be held in. I suggest you leave the year field blank (unless you really do want to update only on certain years). The index directory will be relative directory from running directory, or absolute directory if prefixed with / or drive: in Windows. Like the managed repositories, you should enter an absolute directory location that has plenty of disk space.


After the index is set up, you will be shown your settings. This signals the end of required setup.

Now that you have added a managed repository and indexes, you can now proxy any number of remote repositories through that one. For example, we will add Maven Central and the Codehaus Snapshot repositories to be proxied through the Development Repository (snapshot) we created above. The benefit of this practice is three-fold. One, it allows your build manager to control network access to remote repositories locally. Two, it allows a convenient single-access point repository across an organization (you can add any number of proxied repositories, but your user's POMs need only point to the single managed repository). Three, if the proxied repositories are indexed, your builds are not bound to the remote repositories' network access in order to succeed. I am certain there are more.

A list of proxied repositories... the more the merrier.

If you select "Managed Repositories" in the LHS menu, you will be treated to the basic information about the repositories. A nice little feature is if you select "Show POM Snippet" a div will expand revealing the correct values for connecting to the specific repository, as well as information for deploying built artifacts to this repository via WebDAV, which Archiva manages for you.

Monday, December 18, 2006

karoshi

Karoshi (Kah-roe-she) is the phenomenon first widely pinpointed in Japan in the late 80's, early 90's, meaning "death by overwork". Estimates range from a few hundred to tens of thousands dying each year from cases of heart attack, stroke and other stress-related issues. The interesting thing about it is that those caught up in the epidemic of overwork find it incredibly difficult to stop. They find vacations stressful, their minds focused mostly on work not done. The obsession with work spirals to a complete distancing from friends and family, creating a recursion of more work to fill that gap, distancing more, etc - eventually resulting in death. The practice is grounded in the theory that more time spent working equals more work done.

So what does karoshi have to do with coding? The parallels between the obsession with more code is apt - not only by individuals, but amplified by organizations as a whole. Overeager architects, clueless managers, developers desperate to make a name for themselves - all create an environment where the theory that big projects need big piles of code pervades. I have exhausted too many late nights death marching (working on dead projects) and have joyfully completed too many projects using agile technologies (like rails) to believe this, but its amazing how difficult it is to convince the players caught up in it to change. The obvious answer - less code - seems to elude them, and fight against it by producing even more frameworks, more meetings, more code. If nothing else, it makes me and baby Jesus cry - and you don't want that, do you?