Developing custom plugins

A greater part of this chapter is already spent on providing all the necessary background knowledge to start Maven custom plugin development. Under this section, let's see how to build your own Maven custom plugin from scratch. There are so many Maven plugins out there, and most of the time, you can find a plugin to do whatever you want. Let's start by defining a use case for our custom plugin. Say, you want to write a plugin to send an email to a given recipient once the build is completed.

Maven plain Old Java Object (MOJO) is at the heart of a Maven plugin. A Maven plugin is a collection of goals, and each goal is implemented via a MOJO. In other words, a Maven plugin is a collection of MOJOs. To create a custom plugin, proceed with the following steps:

  1. The first step in writing a custom plugin is to identify the goals of the plugin, and then represent (and implement) each of them with a MOJO. In our case, we have a single goal, that is, to send an email once the build is completed.

    We will write our own EmailMojo class that extends the AbstractMojo class of the org.apache.maven.plugin package. This class must have the Mojo annotation, and the value of the name attribute represents the goal name. In your custom plugin, if you have multiple goals, then for each goal, you need to have a MOJO and override the execute() method. The code is as follows:

    package com.packt.plugins;
    
    import org.apache.maven.plugin.AbstractMojo;
    import org.apache.maven.plugin.MojoExecutionException;
    import org.apache.maven.plugins.annotations.Mojo;
    
    @Mojo( name = "mail")
    public class EmailMojo extends AbstractMojo
    {
      public void execute() throws MojoExecutionException
      {
        getLog().info( "Sending Email…" );
      }
    }
  2. For the time being, let's not worry about the email sending logic. Once you have implemented your business logic inside the execute() method of your MOJO, next we need to package this as a plugin so that the Maven plugin execution framework can identify and execute it.

    You can use maven-plugin-plugin to generate the metadata related to your custom plugin. The following POM file associates maven-plugin-plugin with your custom plugin project. Also, we need to have two dependencies: one for maven-plugin-api and the other one for maven-plugin-annotations.

    <project>
    
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.packt.plugins</groupId>
      <artifactId>mail-maven-plugin</artifactId>
      <version>1.0.0</version>
      <packaging>maven-plugin</packaging>
      <name>PACKT Maven Plugin Project</name>
    
      <dependencies>
        <dependency>
          <groupId>org.apache.maven</groupId>
          <artifactId>maven-plugin-api</artifactId>
          <version>2.0</version>
        </dependency>
        <dependency>
          <groupId>org.apache.maven.plugin-tools</groupId>
          <artifactId>maven-plugin-annotations</artifactId>
          <version>3.2</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-plugin-plugin</artifactId>
            <version>3.2</version>
            <configuration>
              <skipErrorNoDescriptorsFound>
                true
              </skipErrorNoDescriptorsFound>
            </configuration>
            <executions>
              <execution>
                <id>mojo-descriptor</id>
                <goals>  
                  <goal>descriptor</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
  3. Make sure that your project structure looks similar to the following structure, and then build the project with mvn clean install:
    |-src/main/
    |       |-java/org/java/com/packt/plugins
    |           |-EmailMojo.java
    |-pom.xml
  4. The previous step will produce the mail-maven-plugin-1.0.0.jar file inside the target directory of your Maven project. Extract the JAR file with following command:
    $ jar –xvf  mail-maven-plugin-1.0.0.jar
    
  5. The extracted JAR file will have the following directory structure, with the generated metadata files. Only the key/important files are shown here:
    |-com/packt/plugins/EmailMojo.class
    |-META-INF
          |-maven/plugin.xml
  6. Let's have a look at the plugin.xml file first, which is as follows. A mojo element will be generated for each MOJO in the plugin project, having the annotation Mojo. All the child elements defined under the mojo element are derived from the annotations. If there is no annotation, the default value is set. We will discuss the key attributes in the plugin.xml file later in this chapter.
    <plugin>
      <name>PACKT Maven Plugin Project</name>
      <description></description>
      <groupId>com.packt.plugins</groupId>
      <artifactId>mail-maven-plugin</artifactId>
      <version>1.0.0</version>
      <goalPrefix>mail</goalPrefix>
      <isolatedRealm>false</isolatedRealm>
      <inheritedByDefault>true</inheritedByDefault>
      <mojos>
        <mojo>
          <goal>mail</goal>
          <requiresDirectInvocation>false
          </requiresDirectInvocation>
          <requiresProject>true</requiresProject>
          <requiresReports>false</requiresReports>
          <aggregator>false</aggregator>
          <requiresOnline>false</requiresOnline>
          <inheritedByDefault>true</inheritedByDefault>
          <implementation>com.packt.plugins.EmailMojo
          </implementation>
          <language>java</language>
          <instantiationStrategy>per-lookup
          </instantiationStrategy>
          <executionStrategy>once-per-session
          </executionStrategy>
          <threadSafe>false</threadSafe>
          <parameters/>
        </mojo>
      </mojos>
      <dependencies>
        <dependency>
          <groupId>org.apache.maven</groupId>
          <artifactId>maven-plugin-api</artifactId>
          <type>jar</type>
          <version>2.0</version>
        </dependency>
      </dependencies>
    </plugin>
  7. The following Mojo annotation of the EmailMojo class will generate exactly the same configuration as shown in the previous step:
    @Mojo(name = "mail", requiresDirectInvocation = false, requiresProject = true, requiresReports = false, aggregator = true, requiresOnline = true, inheritByDefault = true, instantiationStrategy = InstantiationStrategy.PER_LOOKUP, executionStrategy = "once-per-session", threadSafe = false)

    Before moving any further, let's have a look at the definition of each configuration element used in the previous Mojo annotation:

    Elements

    Explanation

    name

    Every MOJO has a goal. The name attribute represents the goal name.

    requiresDirectInvocation

    A given plugin can be invoked in two ways. The first is by direct invocation where you invoke the plugin as mvn plugin-name:goal-name. The second way of invoking a plugin is as part of a Maven lifecycle, where you execute a lifecycle phase, and being part of a lifecycle phase, plugin goals also get executed. If you set requiresDirectInvocation to true, then you cannot associate the plugin with a lifecycle.

    requiresProject

    If requiresProject is set to true, this means you cannot execute the Maven plugin without a Maven project. It must be executed against a Maven POM file.

    requiresReports

    If your plugin depends on a set of reports, the goal of your plugin is to aggregate, or summarize a set of reports, then you must set the value of requiresReports to tru e.

    aggregator

    If you set the value of aggregator to true, the corresponding goal of your plugin will get executed only once during the complete build lifecycle. In other words, it won't run for each project build. In our case, we want to send an email when the complete Maven build is executed and not for each project; in this case, we must set the value of the aggregator to true.

    requiresOnline

    If you set the value of requiresOnline to true, the corresponding goal of your plugin will only get executed when you are performing an online build. In our case, we have to set requiresOnline to true, because you need to be online to send an email.

    instantiationStrategy

    This is related to Plexus. If the value of instantiationStrategy is set to per-lookup, then a new instance of the corresponding MOJO will be created each time Maven looks up from Plexus. Other possible values are keep-alive, singleton, and poolable.

    executionStrategy

    This attribute will be deprecated in the future. It informs Maven when and how to execute a MOJO. The possible values are once-per-session and always.

    threadSafe

    Once the value of threadSafe is set to true, MOJO will execute in a thread-safe manner during parallel builds.

    inheritByDefault

    If the value of inheritByDefault is set to true, then any plugin goal associated with a Maven project will be inherited by all its child projects.

    Another important element in the generated plugin.xml file is goalPrefix. If nothing is explicitly mentioned in maven-plugin-plugin, the value of goalPrefix is derived by the naming convention of the plugin artifactId. In our case, the artifactId of the plugin is mail-maven-plugin and the value before the first hyphen is taken as the goalPrefix. Maven uses goalPrefix to invoke a plugin goal in the following manner:

    $ mvn goalPrefix:goal
    

    In our case, our custom plugin can be executed as follows, where the first mail word is the goalPrefix, while the second one is the goal name:

    $ mvn mail:mail
    

    If you want to override the value of the goalPrefix without following the naming convention, then you need to explicitly give a value to the goalPrefix configuration element of maven-plugin-plugin in the POM file of the custom Maven plugin project, as follows:

    <configuration>
      <goalPrefix>email</goalPrefix>
    </configuration>
  8. All set. Now we need to execute our custom plugin. To execute the Maven plugin without a Maven project (to consume it), you need to set the value of the requiresProject annotation attribute to false.

    In our case, we have not set this attribute in our MOJO, so the default value is set, which is true. To execute the Maven plugin without a project (you do not need to have a POM file), you need to set the value of requiresProject to false and rebuild the plugin project, as follows:

    @Mojo( name = "mail", requiresProject=false)
    public class EmailMojo extends AbstractMojo
    {
    }
  9. Now try to execute the plugin goal in the following manner:
      $ mvn mail:mail
    

    This will result in an error. Any guesses why? This is related to how Maven looks up for plugins. When you execute a plugin by its goalPrefix, we do not specify its groupId, so the Maven engine will look for it in the local Maven repository (and then in the remote repository) assuming its groupId to be one of the default groupIds. As this is a custom plugin with our own groupId, the Maven engine won't find it. The error is as follows:

    [ERROR] No plugin found for prefix 'mail' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (/Users/prabath/.m2/repository), Central (http://repo1.maven.org/maven2)] -> [Help 1]
    
  10. To help Maven to locate the groupId plugin, add the following configuration element to USER_HOME/.m2/settings.xml under <pluginGroups>:
    <pluginGroup>com.packt.plugins</pluginGroup>
    
  11. Now try to execute the plugin goal once again:
      $ mvn mail:mail
    

    This will now produce the following output:

    [INFO] --- mail-maven-plugin:1.0.0:mail (default-cli) @ mail-maven-plugin ---
    [INFO] Sending Email...
    

Associating a plugin with a lifecycle

A plugin can be executed on its own or as a part of a Maven lifecycle. In the previous section, we went through the former, and now let's see how to associate our custom plugin with the Maven default lifecycle. The Maven default lifecycle has 23 phases, and let's see how to engage our custom plugin to the post-integration-test phase. We only want to send the email if everything up to the post-integration-test phase is successful.

Note

The Maven default lifecycle includes the phases: validate -> initialize -> generate-sources -> process-sources -> generate-resources -> process-resources -> compile -> process-classes -> generate-test-sources -> process-test-sources -> generate-test-resources -> process-test-resources -> test-compile -> process-test-classes -> test -> prepare-package -> package -> pre-integration-test -> integration-test -> post-integration-test -> verify -> install -> deploy.

Proceed with the following steps:

  1. First, you need to create a Maven project to consume the custom plugin that we just developed. Create a project with the following sample POM file, which associates the mail-maven-plugin with the project:
    <project>
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.packt.plugins</groupId>
      <artifactId>plugin-consumer</artifactId>
      <version>1.0.0</version>
      <packaging>jar</packaging>
      <name>PACKT Maven Plugin Consumer Project</name>
    
      <build>
        <plugins>
          <plugin>
            <groupId>com.packt.plugins</groupId>
            <artifactId>mail-maven-plugin</artifactId>
            <version>1.0.0</version>
            <executions>
              <execution>
                <id>post-integration-mail</id>
                <phase>post-integration-test</phase>
                <goals>
                  <goal>mail</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>

    Inside the execution element of the plugin configuration, we associate the corresponding plugin goal with a lifecycle phase.

  2. Just type mvn clean install against the previous POM file. It will execute all the phases in the Maven default lifecycle up to and including the install phase, which also includes the post-integration-test phase. The mail goal of the plugin will get executed during the post-integration-test phase and will result in the following output:
    [INFO] --- maven-jar-plugin:2.4:jar (default-jar-1) @ plugin-consumer ---
    [INFO] 
    [INFO] --- mail-maven-plugin:1.0.0:mail (post-integration-mail) @ plugin-consumer ---
    [INFO] Sending Email.
    

This is only one way of associating a plugin with a lifecycle phase. Here, the responsibility is with the consumer application to define the phase. The other way is that the plugin itself declares the phase it wants to execute in. To do this, you need to add the Execute annotation to your MOJO class, shown as follows:

@Mojo( name = "mail", requiresProject=false)
@Execute (phase=LifecyclePhase.POST_INTEGRATION_TEST)
public class EmailMojo extends AbstractMojo
{
}

Now, in the POM file of your plugin consumer project, you do not need to define a phase for the plugin. The configuration is as follows:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.packt.plugins</groupId>
  <artifactId>plugin-consumer</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <name>PACKT Maven Plugin Consumer Project</name>

  <build>
    <plugins>
      <plugin>
        <groupId>com.packt.plugins</groupId>
        <artifactId>mail-maven-plugin</artifactId>
        <version>1.0.0</version>
      </plugin>
    </plugins>
  </build>
</project>

The plugin execution order

When a plugin gets executed through a lifecycle phase, the order of execution is governed by the lifecycle itself. If there are multiple plugin goals associated with the same phase, then the order of execution is governed by the order you define the plugins in your application POM file.

Inside the execute method

The business logic of a Maven plugin is implemented inside the execute method. The execute method is the only abstract method defined in the org.apache.maven.plugin.AbstractMojo class. The following Java code shows how to get the details about the current Maven project going through the build. Notice that the instance variable of the MavenProject type is annotated with the Component annotation:

package com.packt.plugins;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.project.MavenProject;

@Mojo(name = "mail")
public class EmailMojo extends AbstractMojo {

  @Component
  private MavenProject project;

  public void execute() throws MojoExecutionException {
    getLog().info("Artifact Id " + project.getArtifactId());
    getLog().info("Version " + project.getVersion());
    getLog().info("Packaging " + project.getPackaging());
  }
}

The previous code is required to have the following three dependencies:

<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-plugin-api</artifactId>
  <version>2.0</version>
</dependency>
<dependency>
  <groupId>org.apache.maven.plugin-tools</groupId>
  <artifactId>maven-plugin-annotations</artifactId>
  <version>3.2</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-core</artifactId>
  <version>3.2.1</version>
</dependency>

In the example use case, we took to develop the custom plugin; we need to figure out the list of recipients who we want to send the emails. Also, we might need to get connection parameters related to the mail server. The following code example shows you how to read plugin configuration details from a MOJO:

@Mojo(name = "mail")
public class EmailMojo extends AbstractMojo {

  @Component
  private MavenProject project;
  public void execute() throws MojoExecutionException {
    
    // get all the build plugins associated with the 
    // project under the build.
    List<Plugin> plugins = project.getBuildPlugins();
    
    if (plugins != null && plugins.size() > 0) {
      for (Iterator<Plugin> iterator = plugins.iterator(); 
      iterator.hasNext();) {
        Plugin plugin = iterator.next();
        // iterate till we find mail-maven-plugin.
        if ("mail-maven-plugin".equals(plugin.getArtifactId()))   
        {
          getLog().info(plugin.getConfiguration().toString());
          break;
        }
      }
    }
  }
}

For the email plugin we developed, the required configuration can be defined inside the plugin definition, shown as follows. This should go into the POM file of the plugin consumer application. Under the configuration element of the corresponding plugin, you can define your own XML element to carry out the configuration required by your custom plugin:

<build>
  <plugins>
    <plugin>
      <groupId>com.packt.plugins</groupId>
      <artifactId>mail-maven-plugin</artifactId>
      <version>1.0.0</version>
      <configuration> 
        <emailList>
          [email protected],
          [email protected]</emailList>
        <mailServer>mail.google.com</mailServer>
        <password>password</password>
      </configuration>
      <executions>
        <execution>
          <id>post-integration-mail</id>
          <phase>post-integration-test</phase>
          <goals>
            <goal>mail</goal>
          </goals
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

When you run the plugin with the previous configuration, it will result in the following output. The MOJO implementation can parse the XML element and get the required values:

[INFO] <?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <emailList>[email protected],[email protected]</emailList>
  <mailServer>mail.google.com</mailServer>
  <password>password</password>
</configuration>

The complete source code related to the mail Maven plugin is available at https://svn.wso2.org/repos/wso2/people/prabath/maven/chapter05/mail-plugin, and the plugin consumer code is available at https://svn.wso2.org/repos/wso2/people/prabath/maven/chapter05/plugin-consumer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.37.136