Maven: eigenes Plugin erstellen

Dieses kleine Tutorial soll zeigen, wie man ganz einfach eigene Plugins für Maven2 schreiben kann.
Ich habe mich dafür entschieden, ein ZIP-Plugin zu schreiben, welches einen bestimmten Ordner rekursiv in ein ZIP-Archiv packt.
Dafür muss man ein Maven Projekt aufsetzen und die folgende Dependency definieren:
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0.4</version>

Als Packaging muss maven-plugin angegeben werden.

Das Plugin-Projekt kann auch über Maven angelegt werden:

mvn archetype:create
  -DgroupId=de.ronnyfriedland
  -DartifactId=maven-zip-plugin
  -DarchetypeGroupId=org.apache.maven.archetypes
  -DarchetypeArtifactId=maven-archetype-mojo

Danach kann man eine eigene Klasse implementieren, welche von org.apache.maven.plugin.AbstractMojo erbt.

Die eigentliche Funktionalität enthält die Methode public void execute() throws MojoExecutionException, MojoFailureException, welche implementiert werden muss.

Das Goal, mit dem die Funktionen im Maven-Build genutzt werden können, wird mit der Annotierung @goal im Klassenkommentar definiert. In meinem Beispiel habe ich mich für @goal zip entschieden - nicht ganz unpassend wie ich finde ;-).

Ganz nützlich ist es, wenn man diverse Sachen über eine Konfiguration beeinflussen kann. Dafür können Attribute angegeben werden, welche im Kommentar mit der Annotation @parameter gekennzeichnet werden. Dabei kann ebenfalls ein Default-Wert durch expression="..." angegeben werden.
In der pom.xml kann dann dieser Wert konfiguriert werden. Der Name des Attributs ist dabei der Name des Tags.

Attribut:

/**
 * The folder to compress.
 *
 * @parameter expression="."
 */
private String folder;

Konfiguration in der pom.xml:

...
<folder>src</folder>

Die komplette Klasse sieht dann so aus:

package de.ronnyfriedland;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

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

/**
 * Compresses the content of the folder to the target zip.
 *
 * For details see http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
 *
 * @goal zip
 */
public class ZipMojo extends AbstractMojo {

    /**
     * Contains exclusions.
     *
     * @parameter
     */
    private final List excludes = new ArrayList();

    /**
     * The folder to compress.
     *
     * @parameter expression="."
     */
    private String folder;

    /**
     * The target zip file.
     *
     * @parameter expression="target.zip"
     */
    private String target;

    /**
     * {@inheritDoc}
     *
     * @see org.apache.maven.plugin.AbstractMojo#execute()
     */
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(target));

            addDirectoryToZip(new File(folder), zos);

            zos.close();
        } catch (final Exception e) {
            getLog().error("Error creating zip.", e);
        }
    }

    /**
     * Fügt das aktuelle Verzeichnis inklusive Unterverzeichnisse dem Archiv hinzu. DIe Ordnerhierarchie bleibt dabei
     * erhalten.
     *
     * @param directory Das Verzeichnis, dessen inhalt rekursiv hinzugefügt werden soll.
     * @param zos Der Outputstream des Archivs.
     * @throws IOException
     */
    public void addDirectoryToZip(final File directory, final ZipOutputStream zos) throws IOException {
        final byte[] buf = new byte[4096];
        final File[] fileArray = directory.listFiles();
        String fileName = "";
        for (final File element : fileArray) {
            fileName = element.getPath();
            for (final String exclude : excludes) {
                final Pattern p = Pattern.compile(exclude);
                final Matcher m = p.matcher(fileName);
                if (m.matches()) {
                    continue;
                }
            }
            if (element.isDirectory()) {
                addDirectoryToZip(element, zos);
            } else {
                final FileInputStream inFile = new FileInputStream(fileName);
                zos.putNextEntry(new ZipEntry(fileName));
                int len;
                while ((len = inFile.read(buf) > 0) {
                    zos.write(buf, 0, len);
                }
                inFile.close();
            }
        }
    }
}

Die pom.xml für das Plugin sieht wie folgt aus:

<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>de.ronnyfriedland.maven</groupId>
      <artifactId>maven-zip-plugin</artifactId>
      <packaging>maven-plugin</packaging>
      <name>Maven Zip Plugin</name>
      <version>1.0</version>
      <description>Plugin, mit dem ein Ordner gezippt werden kann.</description>
      <dependencies>
              <dependency>
                      <groupId>org.apache.maven</groupId>
                      <artifactId>maven-plugin-api</artifactId>
                      <version>3.0.4</version>
              </dependency>
              <dependency>
                      <groupId>junit</groupId>
                      <artifactId>junit</artifactId>
                      <version>4.10</version>
                      <scope>test</scope>
              </dependency>
      </dependencies>
      <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>2.3.2</version>
              <configuration>
                  <source>1.6</source>
                  <target>1.6</target>
              </configuration>
          </plugin>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-source-plugin</artifactId>
              <version>2.1.2</version>
              <executions>
                  <execution>
                      <id>attach-sources</id>
                      <goals>
                          <goal>jar</goal>
                          <goal>test-jar</goal>
                      </goals>
                      <configuration>
                          <includePom>true</includePom>
                      </configuration>
                  </execution>
              </executions>
          </plugin>
      </plugins>
      </build>
</project>
Um das Plugin nutzen zu können, muss es natürlich noch in das lokale Maven Repository mittels mvn install gelegt werden.
Liegt das Plugin dann im (lokalen) Repository, kann es ganz normal für ein anderes Maven Projekt genutzt werden, indem es in der pom.xml angegeben wird:
...
<plugins>
 <plugin>
  <groupId>de.ronnyfriedland</groupId>
  <artificatId>maven-zip-plugin</artifactId>
  <version>1.0</version>
  <configuration>
   <folder>src</folder>
   <target>src.zip</target>
  </configuration>
 </plugin>
</plugins>

Der Aufruf des Maven Goals folgt diesem Muster: <groupId>:<artifactId>:<version>:<goal>. In meinem Beispiel würde das so aussehen:

mvn de.ronnyfriedland:maven-zip-plugin:1.0:zip