Thursday, March 5, 2015

How to create a Maven plugin and goals

As any generic tool, Maven may require some customization. If this customization involves some actions not contemplated by the tool, then two approaches can be taken.

The first approach is to include some scripting in the pom file. By embedding ANT code, a lot of tasks can be achieved. Though it is a quick solution, it may turn the pom.xml file into an illegible and unmanageable file. Plus, it is difficult to reuse or share anything.

The second approach is to create a plugin with goals that perform the desired tasks. 

A plugin can be thought as a bag of tasks or commands named goals. Each goal can be used during build time, to perform a specific job. In other words, a Maven plugin is the distribution mechanism for goals.


Building the plugin


It is very  common in Java projects to use .properties files for internationalizing an application or for configuration purposes. It is also very common, to forget a key in one of the .properties files. So lets build a plugin with a goal that checks if a set of .properties files have all the same keys, at build time, to avoid discovering this error later.

The first step is to use an archetype for creating the plugin project:

mvn archetype:generate
    -DarchetypeGroupId=org.apache.maven.archetypes
    -DarchetypeArtifactId=maven-archetype-plugin
    -DgroupId=org.softwaredistilled -DartifactId=properties-maven-plugin


As any archetype, this will create all folders and files, plus an example of a goal. Each goal will be handled by a Java class, named in the Maven world as Mojo (Maven Old Java Object).

The idea is to create a goal that receives a list of regular expressions indicating the .properties files to check. For instance, by setting the expression  "i18n_??" it will validate if all files i18n_{language}.properties have the same keys.

A Mojo will be created to handle the goal, like the one below. You can see the complete code here:


@Mojo(name = "check-properties")
public final class CheckPropertiesMojo extends AbstractMojo {

  @Component
  private MavenProject project;
  
  @Parameter(alias = "check", required = true)
  private String[] matchers;

  @Override
  public void execute() throws MojoExecutionException {
         
        //Validates if properties files have the same keys.
         ...
  }

}

The @Mojo annotation, binds the goal name with its implementation. The method execute() is the hook method called by Maven, when the goal is executed. In this case, it will perform the validation and throw an exception in case the validation fails. 

The @Parameter annotation is used to add parameters to the goal. In this case, the parameter check is the list of regular expressions for finding the .properties files. This parameter must be provided by each project that uses the goal in its build.

The @Component annotation will inject different objects into the Mojo based on the field type, in this case, MavenProject is the project being built (and validated).

Once the goal is finished, the plugin must be installed/deployed in a repository like any other Maven artifact. 


Using the plugin


There are two way of invoking a goal. Through the console, in the project to be built: 

mvn org.softwaredistilled:properties-maven-plugin:1.0:check-properties -Dcheck="i18n_??"


Or specifying it in the pom.xml, inside the build section. This makes more sense as the idea is to use the goal each time the project is built. 

As shown in the next snippet, the goal is bind to the validate phase, and the check parameter, is configured with the regular expressions "i18n_??" and "endpoints_??" (the use of <match> is just for clarity, Maven discards it and provides an array of Strings):


<plugin>
   <groupId>org.softwaredistilled</groupId>
   <artifactId>properties-maven-plugin</artifactId>
   <version>1.0</version>
   <configuration>
<check>
         <match>i18n_??</match>
          <match>endpoints_??</match>
        </check>
   </configuration>
   <executions>
     <execution>
<id>check-properties</id>
<phase>validate</phase>
<goals>
         <goal>check-properties</goal>
</goals>
     </execution>
   </executions>
</plugin>


Lets assume the project has several i18n_{lang}.properties (for internationalization) and several endpoints_{env}.properties (for configuring backends being called in each environment). Triggering the build will perform two validations: all i18n_{lang}.properties have the same keys, and all endpoints_{env}.properties too.


Conclusion


Using plugins and goals for custom build tasks, is a more elegant and convenient approach than scripting because:
  • It allows a cleaner and maintainable pom.xml file.
  • Leverages reuse and sharing.
  • Task are more robust and less error prone, as they can be unit tested, because goals are Java classes.
  • As each task is encapsulated in a goal, any modification or evolution is easier to perform. 





No comments:

Post a Comment