Plugin API
I have discussed how Maven aims to abstract similarities in the build process across projects. Whenever possible you should use the standard core Maven functionality and plugins to achieve the desired results. But there are times when a custom solution just cannot be avoided. In these cases you will need to write a custom plugin and bind it to one of the lifecycle stages.
In Maven 2.0 plugins can be written in Java or in one of several scripting languages (currently Marmalade is the only scripting language supported but support for others is being developed). Maven plugins are basically command objects. To create a Maven plugin you write a Java class that implements the Mojo interface (which stands for Maven POJO). This interface defines a single-execute method with no parameters and no return value and some logging methods. You must also write a plugin descriptor that provides additional information about your plugin, including lifecycle phase binding and parameters. Parameters defined in this descriptor will be injected into member variables in your plugin at runtime by Maven.
Maven simplifies this process by providing tools to generate the plugin descriptor and to package your plugin for you. All you need to do is annotate your plugin class and Maven will take care of the rest.
Greeter ApplicationIteration 3
The best way to understand Maven plugins is to walk through the steps involved in creating one. In this section, you will create a plugin that writes a few useful pieces of project metadata to a properties file that will be read by your application at runtime. This properties file will be bundled in your WAR and will be used by the greeter servlet at runtime to display the version of the application. First, use the archetype plugin to create a simple plugin under your project root:
m2 archetype:create -DgroupId=com.devx.greeter -DartifactId=greeter-
plugin –DpackageName=com.devx.greeter -DarchetypeArtifactId=maven-
archetype-mojo
Next, delete the sample MyMojo class and replace it with a class named PomWriterMojo that will indirectly implement the Mojo interface indirectly by subclassing the AbstractMojo class. This abstract super class will provide default implementations of the log methods defined in the Mojo interface.
Also, you will annotate your class to indicate how you want this plugin used in the build process. You want your properties file created during the generate-sources lifecycle phase, so you'll annotate your plugin class accordingly. During this phase your properties file will be created and placed in the target project's output directory. This will make it available for unit testing, packaging, and deployment as subsequent lifecycle phases are executed.
/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.java
package com.devx.greeter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
/**
* @goal write
* @phase generate-sources
* @description Build a version.txt file to provide information at runtime
*/
public class PomWriterMojo extends AbstractMojo
{
...
}
Before implementing the actual logic of your plugin, you need to determine where you will get the project metadata from. Instead of having your plugin go out and retrieve this information from Maven, instead ask the Maven to inject it into your class. You'll specify what data needs to be injected by annotating the properties you want to contain this information. The annotation will specify an expression, which will be evaluated by Maven, and the results of the evaluation will be injected into the property. Expressions can reference configuration parameters and system properties but they generally reference POM elements and look similar to "${project.build.resources}".
The annotated properties look as follows:
/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.java (cont.)
public class PomWriterMojo extends AbstractMojo
{
/**
* @parameter expression="${project.version}"
* @required
* @readonly
*/
private String version;
/**
* @parameter expression="${project.build.outputDirectory}"
* @required
* @readonly
*/
private String outputDirectory;
}
Lastly, you can use the data that has been injected into your plugin to perform the task for which your plugin was written. Namely, to write the version of the project to a file named pom.properties in the output directory.
/greeter/greeter-plugin/src/main/java/com/devx/greeter/PomWriterMojo.java (cont.)
public class PomWriterMojo extends AbstractMojo
{
...
public void execute() throws MojoExecutionException
{
Properties projectProperties = new Properties();
projectProperties.put("version", version);
try
{
File targetDirectory = new File(outputDirectory);
if (!targetDirectory.exists())
{
targetDirectory.mkdirs();
}
File propertiesFile = new File(outputDirectory + "/pom.properties");
propertiesFile.createNewFile();
OutputStream propertiesStream = new FileOutputStream(propertiesFile);
projectProperties.store(propertiesStream, "Properties from Maven POM");
propertiesStream.close();
}
catch (IOException e)
{
getLog().error(e);
throw new MojoExecutionException("Unable to open properties file for write");
}
}
}
To take advantage of your new plugin, declare it in the plugins section of the POM of your greeter-web project. Additionally, you need to add this plugin as a dependency to that project and to the modules section of the parent POM. This will cause your plugin to be built prior to being used by the greeter-web project and, once again, illustrates multi-project support at work.
/greeter/greeter-web/pom.xml (cont.)
...
<dependency>
<groupId>com.devx.greeter</groupId>
<artifactId>greeter-plugin</artifactId>
<version>1.0-SNAPSHOT </version>
<type>maven-plugin</type>
</dependency>
</dependencies>
<build>
<finalName>greeter-web</finalName>
<plugins>
<plugin>
<groupId>com.devx.greeter</groupId>
<artifactId>greeter-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<goals>
<goal>
<id>write</id>
</goal>
</goals>
</plugin>
</plugins>
</build>
</model>
/greeter/pom.xml
<model>
<modelVersion>4.0.0</modelVersion>
<groupId>com.devx.greeter</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>greeter</artifactId>
<packaging>pom</packaging>
<modules>
<module>greeter-app</module>
<module>greeter-web</module>
<module>greeter-plugin</module>
</modules>
</model>
 | |
| Figure 4. Greeter Output: The PomWriter plugin is invoked before the WAR is built and stores project meta-data for the servlet to access at runtime. |
Finally, you just want to use your generated POM properties file to display the version number of your servlet in its greeting.
/greeter/greeter-web/src/main/java/com/devx/greeter/GreeterServlet.java
package com.devx.greeter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class GreeterServlet extends HttpServlet
{
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
PrintWriter writer = new PrintWriter(response.getOutputStream());
GreeterService service = new GreeterService();
writer.println("<html><body>");
writer.print("<em>Servlet version " + getProjectVersion() + " says: </em>");
writer.println(service.sayHello());
writer.println("</body></html>");
writer.close();
}
private String getProjectVersion()
{
String version;
try
{
Properties pomProperties = new Properties();
pomProperties.load(GreeterServlet.class
.getResourceAsStream("/pom.properties"));
version = pomProperties.getProperty("version");
}
catch (IOException e)
{
version = "unknown";
}
return version;
}
}
The new greeting displays the greeter-web's version from its POM (see
Figure 4).
Maven 2.0 is a powerful tool to organize your projects and to simplify your build process. By following a set of recommended practices and providing some information describing your project, you can let Maven do most of the heavy lifting in your build process. Its recommended practices also promote consistency across projects. And the Maven plugin API will continue to encourage innovative development of plugins useful in enterprise development.