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 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. 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 ${}. 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 set=(Set)mavenProject.getDependencyArtifacts(); List jars=new LinkedList(); ...
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 + " "); 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:
... log4j log4j 1.2.15 ...
e per il plugin:
dummy Dummy 0.0.1-SNAPSHOT compile mygoal
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:
dummy Dummy 0.0.1-SNAPSHOT compile mygoal Ciao mondo!!! webprova.Prova org.apache.log4j.Logger
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:
dummy
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:progettimavenPluginwokring3.5webprovasrcmainjava [INFO] build dir:D:progettimavenPluginwokring3.5webprova argetclasses [INFO] output dir:D:progettimavenPluginwokring3.5webprova arget [INFO] Artifact=junit D:Documents and SettingsDummy.m2 epositoryjunitjunit3.8.1junit-3.8.1.jar [INFO] Artifact=log4j D:Documents and SettingsDummy.m2 epositorylog4jlog4j1.2.15log4j-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:progettimavenPluginwokring3.5webprova arget [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
[2] Eclipse
[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