home >> mb151
di

Francesco Benincasa

Nella stessa sezione>
BetterSoftware2010
graniteds-3
gwt_liferay-2
mavenplugin
ws_spring-4
Allegati>
dummyplugin.zip
News on Feed


Bookmarking
bookmark on Delicious bookmark on Digg bookmark on Furl bookmark on Reddit bookmark on Slashdot bookmark on Technorati
 
 


MokaByte 151 - Maggio 2010

Estendere Maven

Come scrivere un plug-in per Maven

Apache Maven Ŕ uno strumento di gestione dei progetti, utilizzato per la realizzazione delle build e per moltri alti compiti importanti. La sua potenza e la sua versatilitÓ lo rendono un'ottima risorsa per tanti sviluppatori. In questo articolo si vedrÓ come realizzare un plug-in per Maven.

Introduzione

Maven [1] è un potente strumento per la gestione dei progetti per la piattaforma Java, con una moltitudine di plug-in già pronti. Nonostante ciò, a volte può essere necessario estendere ulteriormente le capacità di Maven a causa di particolari esigenze. Ad esempio, si può creare un plug-in per generare report custom legati al progetto o per inserire nella build le informazioni contenute nei log del source version control utilizzato.


Nel corso di questo articolo vengono presentati in modo pratico i passi necessari a realizzare un plug-in, che chiameremo Dummy, con le seguenti funzioni:

  • individua le dipendenze di un progetto e le scrive su un file di testo nella cartella di output del progetto;
  • verifica la possibilità di istanziare una o più classi appartenenti al progetto o alle sue dipendenze.

Implementare le funzioni appena esposte implica lo svolgimento delle seguenti attività all’interno del plug-in:

  • definire dei parametri in ingresso per il plug-in;
  • individuare l’elenco dei componenti da cui dipende il progetto;
  • individuare la cartella delle classi compilate;
  • caricare le classi compilate appartenenti al progetto;
  • scrivere un file nella cartella in cui il progetto viene creato.

Queste sono alcune delle attività di base che possono essere utilizzate per realizzare un qualsiasi plug-in.

Nel corso di questo articolo si userà l’IDE Eclipse 3.5 [2] con il plug-in di Maven [3] e con Java 1.6 (1.6.0_14). Si suppone che il lettore abbia sufficiente conoscenza dell’uso di Maven e di Eclipse.

Creazione del progetto

Realizzare lo scheletro del plug-in con Eclipse è semplice. Di seguito riportiamo i passi necessari a farlo.

Partendo dall’elenco di tipi di progetti è sufficiente selezionare il wizard relativo: Maven --> Maven Project.

Figura 1 - Selezione del wizard.

 

Premendo il pulsante Next si arriverà nello step illustrato in Figura 2.

Figura 2 - Step 1 del wizard.




Premendo il pulsante Next si arriverà alla schermata con la quale selezionare l’archetype da utilizzare. Per creare Dummy utilizzeremo maven-archetype-mojo.

Figura 3 - Selezione del tipo di progetto.




Giunti a questo punto sarà necessario inserire il GroupId e l’ArtifactId del progetto.

Figura 4 - Definizione dell’identificativo del plug-in.




A questo punto nel workspace verrà creato il progetto Dummy.

Mojo! Mojo!

Mojo sta per Maven plain Old Java Object ed è l’implementazione di un goal del plug-in. Può essere considerato come l’interfaccia con la quale il plug-in interagisce con il resto di Maven. Un plug-in può contenere uno o più oggetti mojo. All’interno del progetto Dummy (allegato scaricabile dal menu in alto a sinistra) si troverà un’unica classe denominata MyMojo, il mojo del plug-in. In [4] si trovano le specifiche relative ai mojo.

/**
 * Goal which touches a timestamp file.
 *
 * @goal mygoal
 *
 */
public class MyMojo extends AbstractMojo
{
...
        public void execute()
            throws MojoExecutionException
        {
            ...
        }
    ...
}

Il metodo che viene eseguito per l’esecuzione del goal è il metodo execute. Il mojo viene configurato mediante le annotazioni che si trovano nei JavaDoc. Nel JavaDoc della classe troviamo l’annotazione @goal che indica il nome del goal implementato dal plugin. Nel caso di Dummy l’annotazione avrà come valore mygoal.

Log

La classe AbstractMojo da cui deriva il mojo del nostro plugin, espone un logger per visualizzare a console tutti messaggi mediante la proprietà Log. I messaggi visualizzati possono essere di 4 tipi: debug, info, error, warn. Per utilizzarlo è sufficiente dichiarare una variabile e utilizzarla nel seguente modo:

...
Log log = getLog();
log.info(" *** Dummy Plugin ***");
...

Parametri

Dummy ha bisogno di ricevere dei parametri in ingresso. Per avere queste informazioni aggiungiamo le seguenti proprietà di istanza al mojo:

/**
 * The greeting to display.
 *
 * @parameter default-value="Hello World!"
 */
private String greeting;
/**
 * The class name to test
 * @parameter
 */
private List<String> classNameList;
/**
 * @parameter default-value="${project}"
 */
private MavenProject mavenProject;
/**
 * @parameter expression="${project.build.directory}"
 */
private File outputDirectory;
/**
 * @parameter expression="${project.build.outputDirectory}"
 */
private File buildDirectory;
/**
 * @parameter expression="${project.build.sourceDirectory}"
 */
private File inputDirectory;

L’uso dell’annotazione @parameter inserita nei javadoc di una proprietà permette di iniettare nella stessa il valore del parametro in ingresso definito per il plug-in. L’attributo greeting rappresenta un parametro passato in ingresso al plugin. Di default assume il valore "Hello World". L’attributo classNameList rappresenta l’elenco delle classi che si desidera istanziare per prova e è di tipo List<String>. I parametri in ingresso al mojo possono essere di vario tipo: string, long, double, boolean e perfino liste e mappe. L’attributo mavenProject è il componente che rappresenta il progetto in cui viene fatto girare il plugin. Esso viene utilizzato per recuperare le dipendenze del progetto. La classe MavenProject si trova nell’artifact maven-project: per risolvere la classe è necessario includerla nelle dipendenze del progetto mediante maven. Nella proprietà outputDirectory è iniettato il valore della variabile d’ambiente ${project.build.directory}, che rappresenta la cartella in cui avverrà la build del progetto. Gli attributi buildDirectory e inputDirectory sono stringhe nelle quali vengono iniettati i nomi delle cartelle rispettivamente di output e dei sorgenti.


Mediante l’attributo expression dell’annotazione @parameter, si possono usare le proprietà definite nel pom.xml o in qualsiasi risorsa processata da Maven. Il nome di una risorsa deve seguire la sintassi ${<nome proprietà>}. Si tenga presente che:

  • Le proprietà project.*: rappresentano le proprietà nel pom.xml (ad esempio project.groupId and project.version).
  • Le proprietà settings.*: rappresentano le proprietà in settings.xml.
  • Le proprietà env.*: rappresentano le variabili d’ambiente quali PATH e M2_HOME.


Per lavorare con le cartelle del progetto si utilizzano le variabili d’ambiente:

project.build.sourceDirectory
project.build.scriptSourceDirectory
project.build.testSourceDirectory
project.build.outputDirectory
project.build.testOutputDirectory
project.build.directory

Per ulteriori dettagli sui parametri si rimanda a [3]. Esistono diverse opzioni per definire il comportamento dell’annotazione @parameter. Nei casi affrontati in questo articolo se ne vedono solo una minima parte, per cui si consiglia di consultare [4] per approfondire l’argomento.

Class loader

Individuato da dove recuperare le classi, è necessario caricare in memoria i JAR e le classi del progetto. All’interno del codice allegato a questo articolo è presente una classe denominata DynamicClassLoader, il cui scopo è proprio quello di facilitare il caricamento in memoria delle classi del progetto. Essa deriva dalla classe loader URLClass. Per visualizzare i messaggi a console si utilizza un’istanza della classe Log, ereditata direttamente dalla classe padre di MyMojo.

Il metodo execute del mojo rappresenta il cuore del plug-in. Al suo interno si inserisce il codice necessario a recuperare dalla definizione del progetto le dipendenze:

...
// recuperiamo l’elenco delle dipendenze
Set<Artifact> set=(Set<Artifact>)mavenProject.getDependencyArtifacts();
List<String> jars=new LinkedList<String>();
...

Il codice relativo al caricamento in memoria delle classi:

try {
    // istanziamo ora delle classi con il DynamicClassLoader
    dyn = new DynamicClassLoader(buildDirectory.getAbsolutePath()
+ File.separator, jars);
} catch (Exception e1) {
    log.error("Error " + e1.toString());
    e1.printStackTrace();
}

Per istanziare una classe del progetto si deve utilizzare il class loader che ha nel classpath la cartella delle classi compilate e l’elenco dei JAR delle dipendenze. Provare ad istanziare una classe con il class loader di default genera un’eccezione.

...
for (String item : classNameList) {
    try {
        // istanziamo ora delle classi con il DynamicClassLoader
        Class<?> clazz = Class.forName(item, true, dyn);
        temp = "Class instance " + clazz.toString() + " is ok!!";
        buffer.append(temp + "\n");
        log.info(temp);
    } catch (Exception e1) {
        log.info("Error " + e1.toString());
        e1.printStackTrace();
    }
}
...

Scrittura di un file nella cartella di output

Nell’ultima parte della definizione del metodo execute è contenuto il codice necessario a scrivere su un file di testo nella cartella di output.

...
log.info("output file:" + outputDirectory.getAbsolutePath());
File f = outputDirectory;
if (!f.exists()) {
    f.mkdirs();
}
File file = new File(f, "dummyplugin.txt");
FileWriter w = null;
try {
    w = new FileWriter(file);
    w.write(buffer.toString());
} catch (IOException e) {
    throw new MojoExecutionException("Error creating file " + file, e);
} finally {
    if (w != null) {
        try {
            w.close();
        } catch (IOException e) {
            // ignore
        }
    }
}
...

Installazione

Per compilare il plug-in e installarlo nel repository locale è sufficiente eseguire Maven con il goal install. Per fare questo, con Eclipse è sufficiente eseguire la voce del menù di contesto del progetto Run as --> Maven install.

Figura 5 - Voce del menù di contesto per eseguire il goal install.

 

Uso del plug-in

Per sperimentare l’impiego del plug-in si è creato un altro progetto di esempio, basato sull’archetipo maven-archetype-webapp e denominato webprova. Con il menù di contesto dell’applicazione, si è aggiunto al progetto il plug-in dummy e la dipendenza a logj4. Il pom.xml di webprova si ritrova così a contenere il seguente codice:

<dependencies>
...
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
...
</dependencies>

e per il plugin:

<plugins>
    <plugin>
        <groupId>dummy</groupId>
        <artifactId>Dummy</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <executions>
        <execution>
        <phase>compile</phase>
        <goals>
            <goal>mygoal</goal>
        </goals>
        </execution>
        </executions>
        <configuration></configuration>
    </plugin>
</plugins>

Oppure mediante l’interfaccia che Eclipse propone per la gestione grafica del pom.xml.

Figura 6 - Interfaccia per la gestione del pom.xml




L’unico aspetto della configurazione del plugin che deve essere scritto direttamente nel file pom.xml è la definizione dei parametri. Per il plugin dummy vengono definiti i parametri di configurazione greetings e classNameList:

<plugin>
    <groupId>dummy</groupId>
    <artifactId>Dummy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <executions>
        <execution>
        <phase>compile</phase>
        <goals>
            <goal>mygoal</goal>
        </goals>
        </execution>
    </executions>
    <configuration>
    <greeting>Ciao mondo!!!</greeting>
    <classNameList>
        <param>webprova.Prova</param>
        <param>org.apache.log4j.Logger</param>
    </classNameList>
    </configuration>
</plugin>

Il parametro greetings è di tipo stringa e di conseguenza il suo valore può essere definito come corpo del tag omonimo. Il classNameList invece è stato definito come una lista di stringhe i cui singoli valori devono essere incapsulati in un tag param.

Dummy per essere eseguito correttamente, richiede la compilazione dei sorgenti dell’applicazione. Di conseguenza è stato agganciato alla fase di compilazione. Ogni volta che si procederà con la compilazione del progetto, verrà eseguito anche il goal del plugin. È anche possibile lanciare il goal stand alone del plugin mediante la sintassi groupID:artifactID:[version:]goal. Per l’esecuzione stand alone del plugin d’esempio si deve eseguire il goal:

mvn dummy:Dummy:mygoal

È possibile eliminare dalla linea di comando il package, includendolo nei package di default di ricerca nel file ${user.home}/.m2/settings.xml:

<pluginGroups>
    <pluginGroup>dummy</pluginGroup>
</pluginGroups>

In questo caso il comando diverrebbe

mvn Dummy:mygoal

A console, eseguendo il plugin si ottiene:

[INFO] ------------------------------------------------------------------------
[INFO]  *** Dummy Plugin ***
[INFO] ------------------------------------------------------------------------
[INFO] input dir:D:\progetti\mavenPlugin\wokring3.5\webprova\src\main\java
[INFO] build dir:D:\progetti\mavenPlugin\wokring3.5\webprova\target\classes
[INFO] output dir:D:\progetti\mavenPlugin\wokring3.5\webprova\target
[INFO] Artifact=junit
D:\Documents and Settings\Dummy\.m2\repository\junit\junit\3.8.1\junit-3.8.1.jar
[INFO] Artifact=log4j
D:\Documents and Settings\Dummy\.m2\repository\log4j\log4j\1.2.15\log4j-1.2.15.jar
[INFO] messaggio=Ciao mondo!!!
[INFO] Class instance class webprova.Prova is ok!!
[INFO] Class instance class org.apache.log4j.Logger is ok!!
[INFO] output file:D:\progetti\mavenPlugin\wokring3.5\webprova\target
[INFO] ------------------------------------------------------------------------


E il file dummyplugin.txt nella cartella di output.

Conclusioni

Nel corso dell’articolo è stato realizzato Dummy, un plug-in la cui finalità era presentare alcuni aspetti basilari per la realizzazione di un plug-in per Maven. Si è visto come ricevere dei parametri in ingresso, come visualizzare dei messaggi mediante log e come recuperare le dipendenze definite per un progetto. Si è visto inoltre come caricare le classi del progetto al fine poter disporre da plugin delle stesse. Nell’ambito di un progetto che seguo, ho utilizzato le nozioni esposte in questo articolo per scrivere un plug-in che analizza le classi di un progetto al fine ricavare le dipendenze tra di esse. Per chi volesse iniziare a scrivere plug-in per Maven si consiglia di proseguire con la lettura di [6].

In allegato a questo articolo sono stati messi i sorgenti del plug-in dummy e del progetto webprova.

Riferimenti

[1] Maven

http://maven.apache.org/


[2] Eclipse

http://www.eclipse.org/


[3] Eclipse plug-in for Maven

http://maven.apache.org/eclipse-plugin.html


[4] Mojo API specification

http://maven.apache.org/developers/mojo-api-specification.html


[5] Maven properties

http://www.sonatype.com/books/mvnref-book/reference/resource-filtering-sect-properties.html


[4] Martin Fowler: The New Methodology

http://maven.apache.org/guides/plugin/guide-java-plugin-development.html


[6] Plug-in Developers Centre

http://maven.apache.org/plugin-developers/index.html

 

 

Invia un messaggio alla redazione su questo articolo

Mittente (facoltativo - immetti una email valida se desideri ricevere una risposta)

Come hai trovato questo articolo?

 

Mi sono addormentato

Interessante con il giusto taglio

Argomento non interessante

Molto interessante

Argomento interessante, ma non approfondito

Efficace, ha risolto i miei problemi

Interessante

Mi sono commosso

 

Lascia un commento su questo articolo