Mon premier plugin Maven

Il y a des outils qui sont bien pratiques, voire parfois nécessaires à tout développeur qui se respecte. Un logiciel de gestion de build comme Ant ou Maven en fait partie. Outre l’avantage indéniable de permettre de télécharger la moitié de l’internet lors du premier build sur une machine clean, Maven permet aussi via son système de plugins d’effectuer tout un tas d’actions allant plus loin que la compilation des classes et leur packaging dans un JAR. Je vous propose de regarder comment créer son premier plugin Maven.

D’abord il faut un besoin

Eh oui, parce que se contenter de créer un plugin qui affiche “Hello World” à chaque build, c’est pas très utile… Alors tant qu’à faire, mettons nous en situation réelle. J’aurais pu prendre comme exemple un plugin qui lance l’Annotation Processing Tool à chaque compilation (par exemple pour Android Annotations), mais je vais plutôt vous parler du premier plugin que j’ai eu à développer, qui permet de faire de la génération de code.

Ce générateur prend en entrée un modèle décrit dans un fichier XML, et produit en sortie des classes Java correspondant aux entités et aux DAO CRUD, ainsi qu’un script SQL de création de tables. Ces classes sont ensuite utilisables dans les services écrits à la main. Certaines parties du code généré sont configurables :

  • le fichier XML décrivant le modèle
  • le package Java de base des classes générées
  • le nom du script SQL de créations de tables

Création du squelette de plugin Maven

Un plugin Maven n’est rien d’autre qu’un projet Maven avec un packaging de type maven-plugin :

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

    <groupId>com.excilys.labs.maven.plugin</groupId>
    <artifactId>generator-maven-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>maven-plugin</packaging>
</project>

La convention veut que les plugins soient nommés xyz-maven-plugin, ce qui permet d’appeler les goals de la manière suivante lors d’un build :

1
$ mvn xyz:some-goal

Notez qu’il n’est pas nécessaire de spécifier le nom complet du plugin, Maven va compléter avec “-maven-plugin”, ça fait quelques caractères en moins à écrire ;-) . Si la convention de nommage n’est pas suivie, il faut impérativement spécifier le nom complet du plugin dans la ligne de commande.

Ensuite, un plugin contient un ou plusieurs MOJO, qui vont exécuter le “vrai” code du plugin. Un MOJO correspond en fait à un goal. Par exemple, dans le plugin dependency-maven-plugin, on retrouve les goals “tree” (mvn dependency:tree) et “resolve” (mvn dependency:resolve)

Créons donc un squelette de MOJO pour notre générateur de code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.excilys.labs.maven.plugin.generator;

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

/**
 * A goal to generate code.
 *
 * @goal generate
 * @phase generate-sources
 */

public class CodeGeneratorMojo extends AbstractMojo {
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("Hello World!");
    }
}

Notre MOJO étend AbstractMojo, qui fournit toute l’infrastructure utile à la création d’un goal. La méthode abstraite execute() contient le code à exécuter, c’est donc à nous de l’implémenter. Pour l’instant elle ajoute une ligne de log saluant le monde (je sais, j’ai dit tout à l’heure qu’on ne se contenterait pas de faire un hello world…). La méthode getLog() est un exemple de facilités fournies par l’AbstractMojo.

Notez également la Javadoc, qui contient deux tags particuliers. @goal spécifie le nom du goal correspondant à notre MOJO, ce tag est obligatoire. @phase permet de dire à Maven que notre plugin intervient durant la phase de génération des sources du lifecycle Maven (avant la compilation des sources, donc).

Notre plugin est prêt à être exécuté. Configurons le pom.xml pour permettre l’exécution du plugin lors du build :

1
2
3
4
5
6
7
8
9
    <build>
        <plugins>
            <plugin>
                <groupId>com.excilys.labs.maven.plugin</groupId>
                <artifactId>generator-maven-plugin</artifactId>
                <version>1.0-SNAPSHOT</version>
            </plugin>
        </plugins>
    </build>

Puis lançons un build Maven :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mbp:GeneratorMavenPlugin bastien$ mvn generator:generate
[INFO] Scanning for projects...
[INFO]                                                                        
[INFO] ------------------------------------------------------------------------
[INFO] Building generator-maven-plugin 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- generator-maven-plugin:1.0-SNAPSHOT:generate (default-cli) @ generator-maven-plugin ---
[INFO] Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.322s
[INFO] Finished at: Mon May 14 14:47:54 CEST 2012
[INFO] Final Memory: 2M/81M
[INFO] ------------------------------------------------------------------------

Ajout de paramètres au plugin

Notre cahier des charges requière la possibilité de configurer certaines parties du générateur. Pour cela, ajoutons des paramètres à notre MOJO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CodeGeneratorMojo extends AbstractMojo {

    /**
     * @parameter alias="model.location"
     * @required
     */

    private String modelLocation;

    /**
     * @parameter
     * @required
     */

    private String basePackageName;

    /**
     * @parameter default-value="${project.build.directory}/create.sql"
     */

    private String sqlOutputLocation;

    // ...
}

Nos trois attributs utilisent également des tags Javadoc pour indiquer que ce sont des paramètres du plugin. Les deux premiers sont obligatoires, le troisième est facultatif et prend une valeur par défaut. ${project.build.directory} sera remplacé au runtime par le chemin du dossier target.
L’attribut “alias” de @parameter permet de définir le nom du paramètre s’il est différent du nom de l’attribut Java. Ainsi, par défaut il y aura un paramètre nommé “basePackageName”, puisqu’on ne lui a pas donné d’alias. Une liste plus exhaustive des tags Javadoc est disponible sur le site de Sonatype.

Ouvrons à nouveau le pom.xml pour ajouter la configuration du plugin :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    <build>
        <plugins>
            <plugin>
                <groupId>com.excilys.labs.maven.plugin</groupId>
                <artifactId>generator-maven-plugin</artifactId>
                <version>1.0-SNAPSHOT</version>

                <configuration>
                    <model.location>src/main/resources/model.xml</model.location>
                    <basePackageName>com.excilys.labs.generated</basePackageName>
                </configuration>
            </plugin>
        </plugins>
    </build>

Pour vérifier que la configuration est bien injectée au runtime, ajoutons un log :

1
getLog().info("Classes will be generated in package " + basePackageName);
1
2
3
4
5
6
7
8
mbp:GeneratorMavenPlugin bastien$ mvn generator:generate
...
[INFO] --- generator-maven-plugin:1.0-SNAPSHOT:generate (default-cli) @ generator-maven-plugin ---
[INFO] Hello World!
[INFO] Classes will be generated in package com.excilys.labs.generated
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...

Maintenant que nous savons créer, configurer et exécuter un plugin Maven, il ne reste plus qu’à implémenter la méthode execute() pour appeler les vraies classes de génération de code. Comme ce n’est pas vraiment l’objet de cet article, je ne détaillerai pas plus cette partie. Cependant un FakeGenerator est disponibles dans les sources complètes.

J’aime pas les XDoclet !!

Comme vous l’avez vu, la configuration du Mojo se fait par défaut dans des tags Javadoc (des sortes de XDoclet). Ce n’est pas terrible dans la mesure où il est facile de faire une faute de frappe (ex @paramter au lieu de @parameter) qui ne sera pas détectée par le compilateur ni par Maven. Pour pallier ce problème, il existe un set d’annotations Java, le maven-plugin-anno. Après avoir configuré la dépendance dans votre pom, il est ensuite possible de tout annoter :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    <dependencies>
        <dependency>
            <groupId>org.jfrog.maven.annomojo</groupId>
            <artifactId>maven-plugin-anno</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>2.6</version>
                <dependencies>
                    <dependency>
                        <groupId>org.jfrog.maven.annomojo</groupId>
                        <artifactId>maven-plugin-tools-anno</artifactId>
                        <version>1.4.0</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <pluginRepositories>
        <pluginRepository>
            <id>jfrog-plugins</id>
            <name>jfrog-plugins-dist</name>
            <url>http://repo.jfrog.org/artifactory/plugins-releases-local</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>
    <repositories>
        <repository>
            <id>jfrog-plugins</id>
            <name>jfrog-plugins-dist</name>
            <url>http://repo.jfrog.org/artifactory/plugins-releases</url>
            <layout>default</layout>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@MojoGoal("generate-annotated")
@MojoPhase("generate-sources")
public class AnnotatedMojo extends AbstractMojo {
   
    @MojoParameter(alias = "model.location", required = true)
    private String modelLocation;

    @MojoParameter(required = true)
    private String basePackageName;

    @MojoParameter(defaultValue = "${project.build.directory}/create.sql")
    private String sqlOutputLocation;

    public void execute() throws MojoExecutionException, MojoFailureException {
        // ...
    }
}

Sachez également que le support des annotations est en train d’arriver nativement dans Maven (Java5 annotations support for Maven plugins).

Conclusion

Comme vous avez pu le voir, créer un plugin Maven n’est pas très compliqué. Les plugins sont suffisamment souples et configurables pour pouvoir exécuter du code déjà existant (par exemple remplacer un main() par un plugin Maven) sans trop de difficultés. La palette de plugins déjà disponibles est assez vaste, mais si un jour vous avez un besoin spécifique vous saurez que le coût de création d’un plugin n’est pas très élevé ;-) .

Liens utiles

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Maven | Tagged , , | 2 Comments

Git-SVN – Soignez votre addiction à Git

Le vrai problème avec Git, c’est qu’une fois qu’on l’a utilisé c’est très dur de revenir vers un SCM non distribué comme Subversion, voire CVS.
La solution évidente est de lancer une migration vers Git (ou un autre SCM distribué comme Mercurial ou Bazaar, ne soyons pas sectaires). Toutefois cette migration  n’est pas toujours simple, voire possible, à mettre en place, pour diverses raisons qui n’ont habituellement rien à voir avec la technique.
Et si on pouvait avoir les avantages de Git sur son poste de développeur, tout en gardant le repository SVN central historique du projet ? Et bien si vous n’avez pas encore vu le titre de cet article, apprenez donc que c’est justement ce que propose Git-SVN.

Continue reading

VN:R_U [1.9.17_1161]
Rating: +1 (from 1 vote)
Share
Posted in Outils | Leave a comment

Le javascript, ça sert à tout et n’importe quoi. Surtout n’importe quoi.

Disclaimer : cet article va parler d’une librairie js à laquelle j’ai contribué, et plus particulièrement d’une fonctionnalité que j’ai développé. J’ai tenté au maximum d’éviter les dérives nombrilistes pénibles à base de “moi je”, mais cassons le suspens tout de suite : j’ai échoué.

D’un projet WTF

Le javascript est à la mode, il est donc utilisé à toutes les sauces. On le trouve côté serveur, on compile du bytecode llvm vers du js, on fait des jeux avec, on peut même l’utiliser pour attaquer les libertés fondamentales des utilisateurs. Dans toutes ces librairies, on trouve par exemple JSZip, une librairie js qui permet de générer des fichiers zip côté client. Sans intervention du serveur (parce que utiliser java.util.zip, c’est tricher), on pourra donc créer le binaire représentant un HelloWorld.txt zippé :

1
2
3
4
var zip = new JSZip();
zip.file("HelloWorld.txt", "Hello, world !");
var content = zip.generate();
location.href="data:application/zip;base64,"+content;

Récupérer ce binaire n’est pas facile, mais on a vu pire.

À une contribution OMGWTFBBQ

L’élément déclencheur est ici une feature request dans le bug tracker sur github :

It would be great to have a two-way zipfile object that can be created from a file, modified and downloaded as a file.

Avec des motivations telles que :

  • for the lulz
  • because I can

Il m’était impossible de ne pas tenter l’écriture d’un parser zip en js.

CHALLENGE ACCEPTED

Les principales difficultés furent :

  • un fichier zip se lit à l’envers. Commencer par le début pose trop de problèmes (mais je l’ai su après).
  • IE. Charger un fichier binaire en ajax demande du code vbscript. DU CODE VBSCRIPT.
  • mes idées du type “tiens, et si j’ajoutais la gestion du ZIP64, prévu pour les fichiers de plus de 4Go ?”

Au final, ça donne une pull request conséquente (+7472, -1092) qui ajoute la possibilité de lire des zip dans un navigateur. Même des jar (avec leurs data descriptor), même des stream zippés (avec leur extension ZIP64), même des zip avec des symboles UTF8 bizarres dans le nom des fichiers, même dans IE6.

1
2
3
4
5
var zip = new JSZip();
zip.load(zipBinaryFromAjaxCall);
zip.file("HelloWorld.txt").data; // "Hello, world !"
zip.file("new_file.txt", "yeeeeah !");
zip.generate(); // zip with 2 files

Ça donne également un sacré sentiment d’accomplissement quand la pull request est acceptée :)

DONE

Contrat Creative Commons Article écrit sous Creative Commons 3.0 BY-SA.

VN:F [1.9.17_1161]
Rating: +6 (from 6 votes)
Share
Posted in WTF | Tagged , , , | 4 Comments

Préparez-vous à réécrire l’histoire avec Git rebase

L’un des intérêts de Git est qu’il vous permet d’organiser vos développements locaux comme vous le souhaitez et de pratiquer la politique du “commit early, commit often”.
Cependant, Si vous commitez souvent vos sources, vous vous rendrez compte que vos révisions peuvent parfois perdre de leur sens. Vous verrez ainsi certaines tâches s’étaler sur plusieurs révisions, plusieurs tâches dans la même révision ou des révisions de merge (pas toujours significatives). Pour vous faciliter la lecture de l’historique des sources, vous aurez besoin de le retravailler avant de le pousser sur le dépôt distant de référence. Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Outils, Trucs & astuces | Tagged , | 1 Comment

Hibernate n’aime pas la discrimination

Travailler sur des applications legacy nous amène parfois à faire face à des difficultés qui nous gênent dans la mise en place d’outils modernes tout en conservant le modèle existant.
Cela m’est arrivé récemment lorsque j’ai du mapper une table de jointure un peu spéciale sur un projet de portage. Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Non classé | Tagged , | Leave a comment

Les Apache Commons

Apache Commons est l’un des principaux projets de la fondation Apache.
Il est composé d’un ensemble de bibliothèques open-sources et réutilisables mettant une multitude d’utilitaires à la disposition des développeurs Java. Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Java, Non classé, Trucs & astuces | 1 Comment

Args4j – CLI Parser (Command Line Interface Parser)

Args4j est un projet open-source, sous licence MIT, permettant de parser nos arguments/options de la ligne de commande. Le responsable du développement est Kohsuke Kawaguchi. Args4j est un sous-projet de Java Tools, qui est un ensemble de petits outils assez pratique pour tout développeur Java. On y trouve notamment Visual VM, pour monitorer notre VM, ou encore Hudson dont Kohsuke a forké le projet pour devenir Jenkins.

Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Non classé, Outils | Tagged | Leave a comment

SpringFuse, un générateur intéressant ?

Pour mon premier article, j’ai décidé de vous présenter SpringFuse. Pourquoi ? En fait, je suis récemment rendu à une conférence sur le générateur de code SpringFuse et je me suis dit :

« Pourquoi ne pas faire découvrir à ceux n’ayant pas eu la chance d’assister à cette conférence ce qu’est cet outil ?».

Je tiens à souligner que cet article est avant tout un article « découverte », je ne rentrerai donc pas aujourd’hui dans les détails. Cependant, je pense creuser un peu plus durant les mois à venir. Je posterai donc très probablement d’autres articles sur cet outil.

Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Outils | Tagged , , , , , | 3 Comments

Binder une date en Grails

Grails est un framework web pour Groovy, qui facilite beaucoup l’écriture d’une appli web.

Le binding est une des facilités de Grails : disons qu’on ait un objet User, contenant les propriétés lastName, firstName et birthdayDate. Notre formulaire de création/édition d’utilisateurs aura les champs correspondants. Dans le controlleur, on peut remplir notre objet User en écrivant simplement :

1
bindData(user, params)

Mais pour binder la date birthdayDate, la map params doit avoir les champs birthdayDate, birthdayDate_year, birthdayDate_month, birthdayDate_day, birthdayDate_hour et enfin birthdayDate_minute. Ces champs sont automatiquement générés par la balise <g:datePicker /> fournie par Grails, mais dès qu’on n’utilise plus cette balise (date picker personnalisé, utilisation d’un input text classique…), il faut générer soi-même tous les champs. C’est beaucoup trop long, lourd et complétement inadapté.
Continue reading

VN:R_U [1.9.17_1161]
Rating: 0 (from 0 votes)
Share
Posted in Grails, Spring | Tagged , , , | Leave a comment

DevOps : c’est à la mode !

Depuis très récemment (2009), une nouvelle tendance, ou plutôt mouvement, est apparue au sein des développements des projets informatiques et commence à être de plus en plus adoptée : je parle du DevOps. Encore peu répandu en France, certains d’entre … Continue reading

Share
More Galleries | Leave a comment