Thématiques principales

Affichage des articles dont le libellé est Maven. Afficher tous les articles
Affichage des articles dont le libellé est Maven. Afficher tous les articles

dimanche 23 février 2020

Spring-boot: RabbitMQ

On a jamais parler de Spring-boot [9], ça fait même assez longtemps que l’on a pas parler Java! Du coup comme nous sommes lancé dans les articles sur RabbitMQ et que ce message broker est assez courant dans ces écosystèmes, on va en profiter!

On va donc finir cette liste d’articles [2, 3] avec une mise en situation en réalisant une application client server implémentée avec Spring-boot (sur lequel nous reviendrons de façon plus général dans un autre article).


samedi 2 mars 2019

OSGI : SpringDM et BluePrint

Nous revoilà avec OSGI. Nous parlions à la fin de l’article sur Karaf et Service-mix de SpringDM et BluePrint… mais qu’est ce que c’est?

SpringDM et Blueprint [gemini] sont des frameworks Java et XML permettant la simplification de la description et la gestion des services OSGI de nos Bundles.

SpringDM a été le précurseur et a été suivi par Blueprint grâce à une normalisation (plus une adhésion) au sein de la norme OSGI.

Le principe est d’utiliser les mécanismes de Spring pour réaliser la déclaration de Beans au sein des bundles qui seront chargé par un bundle context dédiés. Cela ne va pas nous empêcher de devoir déclarer nos services sous la forme d’interfaces et d’en réaliser des implémentation mais cela va nous permet de ne plus avoir à implémenter toute la partie Activator, Factory et Tracker que nous avions vu dans les précédents articles sur le sujet…

Quoi ? mais du coup il n’y à plus rien dans le Bundle?

mercredi 27 février 2019

OSGI : Karaf ServiceMix

On est presque à la fin du mois et je n’ai écrit qu’un seul article! Heureusement j’ai un sujet au chaud pour combler le vide.

On avait parlé du framework OSGI Felix [osgi-felix] la dernière fois et nous avions vu dans un précédent article qu’il en existe d’autres packages sous la forme de serveur d’application (comme le serait un Jboss [jboss] ou un Weblogic [weblogic]).

Nous allons justement nous intéresser à deux de ses serveurs:
  • Karaf [karaf]
  • ServiceMix [servicemix]

Bien sur ceux les connaissant diront que c’est grosso merdo la même chose, et effectivement, ServiceMix s’appuie sur Karaf mais en apportant un ensemble de bundle dédié à la communication et aux services, c’est à dire les WebServices et les services de messageries (SMTP, JMS, etc…). Il était donc important de le connaître également.

dimanche 3 février 2019

OSGI : Felix

Nous revoilà avec OSGI mais cette fois ci pour aller dans le concret, plus de blabla, maintenant on fait chauffer la JVM.

Le problème du choix

Bien tout d’abord il nous faut choisir un framework, c’est à dire une implémentation. Nous en avons vu plusieurs dans l’article précédent [osgi-fwk] et il nous faudra faire un choix. À mon sens il y avait plusieurs possibilité:

  • Knoplerfish [knoplerfish] est trop compliqué bien qu’à jour dans l'implémentation des versions OSGI
  • Karaf [karaf] est un peu trop lourd pour ce que nous voulons montrer ici (mais nous ferons un article dessus)
  • Equinox [equinox] est sympas je l’aurais choisi si je ne prévoyais pas de présenter mon framework equinox-loader [eq-loader] qui l’utilise
  • Concierge [concierge] est hors propos, on ne va pas faire de Iot
  • Donc en toute logique, on va essayer l’utilisation de OSGI avec Felix [felix]

Tout d’abord il faut télécharger la version courante: c’est ici [felix-down]

Dockerisation

Pour simplifier son utilisation, nous allons préalablement le dockeriser pour faciliter son exploitation, on construit donc un fichier Dockerfile dans un répertoire (moi je l’ai nommé felix-framework) contenant le processus de construction suivant:

FROM openjdk:8-jre-alpine

RUN wget http://mirror.ibcp.fr/pub/apache//felix/org.apache.felix.main.distribution-6.0.1.tar.gz \
&& tar zxvpf org.apache.felix.main.distribution-6.0.1.tar.gz && rm org.apache.felix.main.distribution-6.0.1.tar.gz

COPY . /felix-framework-6.0.1/
CMD cd /felix-framework-6.0.1; java -jar bin/felix.jar


lundi 31 décembre 2018

Docker - Plugin Maven

Un petit écart dans la liste des articles qui devaient être publiés prochainement en abordant le sujet du packaging de nos builds maven dans des images Docker.

On parle d'écart mais en fait pas tant que ca car nous verrons que OSGI se prête bien à une intégration dans docker. Bref ca c’est une autre histoire.

Tout le monde connaît Docker, au moins de nom. Il s’agit d’une solution de conteneurisation [docker-pub], assimilable à de la “virtualisation allégé” (ok le raccourci est rapide) permettant de construire des architectures scalables [scala] de type [microservice] (nous en reparlerons).

L’approche classique pour construire une image docker et d’utiliser la commande “docker build”, si vous êtes un habitué de l’outil, sera de construire un Dockerfile. Je vous laisse pour cela aller consulter l’article [docker-base].

Cependant cette approche en ligne de commande, bien que pratique car universelle n’est pas forcément immédiate dans le cadre d’une industrialisation comme d’un build maven qui aurait pour résultat non pas un artifact java classique [integ-java] mais une image docker prête à l’emploi pour les tests ou la production (vous le voyez poindre le concept de Déploiement Continu?).

Heureusement pour répondre à ce problème il existe deux plugins maven [compar]:
  • spotify/maven-docker-plugin [spotify], et sa nouvelle version [spotify-2] spotify/dokerfile-maven-plugin
  • fabric8io/maven-docker-plugin [fabric8io]

Spotify

Ce plugin maven est celui ci qui collera le plus avec la logique d’utilisation de docker.

En effet, ce plugin s’utilise de façon assez simple soit par configuration xml sur les éléments basiques qu’un dockerfile pourrait contenir où en spécifiant directement un répertoire où se trouvera un dockerfile.

Ainsi ce plugin va permettre de réaliser la phase de build de la commande docker mais aussi en complément la phase de push pour livrer l’image ainsi construite dans un repository comme le docker hub [DocHub].

On pourra regretter que ce plugin ne permet pas de lancer lui même des conteneurs, chose qui aurait pu être intéressant pour la réalisation de phase de tests.

Dans le cadre de cet article, comme dans celui de [compar], nous ne nous attarderons pas sur ce plugin qui finalement fait bien son taff mais ne fournira pas en dehors du cadre de l'intégration à maven beaucoup plus de fonctionnalité que de faire son Dockerfile nous même.

Fabric8io

Le plugin fabric8io sort quand à lui plus des sentier battu en ne s’appuyant pas du tout sur un dockerfile mais sur une approche plus classique dans un environnement maven. En effet, ce plugin propose d’utiliser un descriptif xml du Dockerfile [doc-fabric] ainsi que le mécanisme des assembly [assembly-maven].

A cela, il permet aussi de gérer le cycle de vie des conteneurs en activité (start/stop/kill etc...) ainsi que de publier les images sur un repository (comme le DockerHub [DocHub])

Le plus simple pour présenter le plugin est d’en faire la démonstration.

Ainsi considérons un projet java construit forcément avec un process maven. L’idée d’un build est de généralement construire jar. Dans une approche standard de packaging, le process maven appliquera une phase d’assemblage dans laquelle, le jar, sa conf et ses éventuellement dépendances seront mis dans un targz, un paquet debian où tout autre format de livraison.

Avec docker, le concept permettant d'exécuter un conteneur à partir d’une image quelque soit la plateforme permet d’imaginer une rationalisation du moyen de livraison et d'exécution.

Ainsi, parce que nous utilisons maven, nous allons enrichir un pom parent avec une configuration pluginManagement du plugin fabric8io. L’idée est alors de permettre à tout pom héritant de celui-ci de construire une image docker de packaging et d'exécution.


 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <configuration>
                            <images>
                                <image>
                                    <name>${docker.login}/${project.artifactId}:%l</name>
                                    <alias>${project.artifactId}</alias>
                                    <build>
                                        <maintainer>Collonville Thomas collonville.thomas@gmail.com</maintainer>
                                        <!-- <dockerFile>Dockerfile</dockerFile> -->
                                        <assembly>
                                            <mode>dir</mode>
                                            <permissions>auto</permissions>
                                            <targetDir>/</targetDir>
                                            <descriptor>../assembly/assembly-docker.xml</descriptor>
                                        </assembly>
                                    </build>
                                </image>
                            </images>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
    <profile>
        <id>PUSH</id>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>io.fabric8</groupId>
                        <artifactId>docker-maven-plugin</artifactId>
                        <version>0.27.2</version>
                        <executions>
                            <execution>
                                <id>start</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>build</goal>
                                    <goal>push</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </profile>
</profiles>

Ainsi la pré configuration maven du plugin permet de façon générale de donner un nom à l’image et spécifier ses informations générales (nom, mainteneur) dans la section “image”.

Ensuite dans une section build on va préciser le contenu de l’image en utilisant les mécanismes d’assembly maven que l’on devra préciser dans le fichier “assembly/assembly-docker.xml”

Enfin en coherence avec la logique des plugins maven se constituant d’une partie configuration et d’une partie exécution, dans cette dernière, on va préciser quelle commande docker nous allons appliquer.

Ici on notera qu’il y a deux profiles maven, l’un DOCKER de base qui apporte la configuration et la construction de l’image et un second PUSH dans lequel on appliquera la commande “push” de docker (cela nécessitera l’utilisation de la propriété docker.login précisant le compte du DockerHub associé et présent dans le nom de l’image)

L’utilisation de ce pom parent passe ensuite par la surcharge du profile DOCKER dans une section plugin (le profile PUSH étant directement hérité).

La surcharge est rapide bien qu’un peu verbeuse à cause du profile maven. (à noter que dans un process complet et générique on pourra in fine s’en passer)


 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
<profiles>
    <profile>
        <id>DOCKER</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>io.fabric8</groupId>
                    <artifactId>docker-maven-plugin</artifactId>
                    <configuration>
                        <images>
                            <image>
                                <build>
                                    <from>openjdk:8-jre-alpine</from>
                                    <cmd>/opt/equinox-loader/docker.sh</cmd>
                                    <ports>
                                        <port>${jmx.port}</port>
                                    </ports>
                                </build>
                            </image>
                        </images>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Dans ce pom, il s’agira juste de completer les parties manquantes c’est à dire:

  • l’image source utilisé
  • une commande de lancement
  • les ports et les volumes à déclarer
  • pour finir le contenu de l’assembly, faisant office de commande ADD et COPY d’un Dockerfile

Ici nous utiliserons l’image openjdk:8-jre-alpine comme image source que nous allons configurer le port JMX de la JVM et que nous lancerons l’application via un script sh.

Pour ce qui concerne l’assembly, il s’agit à ce niveau d’une assembly standard utilisant des section “dependencySets” et “fileSets”. Je vous invite à consulter la documentation du plugin [plugass] et/ou de lire l’article [assem].

Voilà avec ce plugin, du coup il est possible de construire des images docker contenant directement notre build prêt à l’emploi nous facilitant d’une part la diffusion du livrable mais aussi l’exploitation dans des tests et surtout l’exploitation en production.

Références

[docker-pub] https://un-est-tout-et-tout-est-un.blogspot.com/2017/10/docker-publier.html
[docker-base] https://un-est-tout-et-tout-est-un.blogspot.com/2017/09/docker-construire-son-image.html
[scala] https://fr.wikipedia.org/wiki/Scalability
[integ-java] https://runnable.com/docker/java/dockerize-your-java-application
[microservice] https://fr.wikipedia.org/wiki/Microservices
[spotify] https://github.com/spotify/docker-maven-plugin
[spotify-2] https://github.com/spotify/dockerfile-maven
[fabric8io] https://github.com/fabric8io/docker-maven-plugin
[doc-fabric] http://dmp.fabric8.io/
[compar] https://dzone.com/articles/meet-the-docker-maven-plugin
[assembly-maven] http://maven.apache.org/plugins/maven-assembly-plugin/
[DocHub] https://hub.docker.com/
[assem] https://un-est-tout-et-tout-est-un.blogspot.com/2018/03/maven-assembly-targz-et-jdeb.html
[plugass] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html

lundi 26 novembre 2018

dimanche 25 novembre 2018

Liquibase sources

En juin dernier, j'avais écris un article sur Liquibase [1] avec des extraits de code, seulement depuis, j'ai lancer un repository git contenant les sources des articles. Du coup cet article n'est que la que pour signaler que le code source de cet ancien article est disponible dans le repository [2]

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2018/06/sgbd-liquibase.html
[2] https://github.com/collonville-tom/tc-un-est-tout-et-tout-est-un

mercredi 14 novembre 2018

Asciidoctor

Suite a l'article d'hier [1],  on peut quand même se demander :Comment augmenter même un peu l’attractivité de la présentation?

En passant à Asciidoctor [2,3]!

Quoi un autre outil! non mais tu nous arnaques la, il va falloir tout refaire!

Non non ne vous inquiétez pas! Asciidoctor est un outil qui implémente (en ruby) tout le corpus de Asciidoc avec quelques petits trucs en plus.

Du coup oui il va falloir changer d’outil mais…. pas autant que vous le croyez [4]! n’oubliez pas ce dont nous parlions dans l'article précédent [1], comment rendre plus simple, plus attractive la rédaction documentaire dans le cadre d’un projet?

Et bien à l'instar d’un maven site [5] (qui est très très basique il faut l’avouer) on va également builder notre documentation technique avec Maven tout en restant (peu ou prou) compatible avec Asciidoc et sa simplicité (syntaxiquement parlant).

Avec Maven

Bien, on a déjà parlé de Maven [6] , et effectivement surtout dans le cadre de projet Java. Pourtant, si on prend un peu le point de vue du MDE/IDM, on peut considérer la documentation comme un modèle comme un autre de notre application alors si c'est un modèle, comme de l'UML ou même du code, pourquoi ne pas le compiler? Finalement il n'est qu une façon de voir une application ou un concept.

Ici on va rejoindre cette idée et nous allons définir un contexte de build maven pour faire notre documentation.

Des parents

Comme nous l'avions vu dans notre article sur Maven, on ne va pas partir bille en tête dans la simple définition d’un pom. On va se débrouiller pour augmenter la reutilisabilité. Du coup on va faire des pom parent!

On sait que asciidoc est naturellement fait pour produire des documents on va donc d’abord définir un parent permettant de construire ce type de doc, disons sous le format pdf puis par extension, on va tirer partie de ce premier pom pour l'étendre vers la production de présentations (il va de soit que le contenu sera le même, seul la forme change)

Pour faire ces deux pom parent, je citerai que je me suis beaucoup inspiré d’un fork d’un ami  sur github [7].

Ainsi le premier pom nommé tc-asciidoctor-pdf-parent va s’appuyer sur le plugin maven dédié à asciidoctor: asciidoctor-maven-plugin [8]. Il est complété par deux plugin asciidoctor que sont asciidoctorj-pdf [9] et asciidoctorj-diagram [10, 11] permettant de générer du pdf et de gérer des diagrammes au sein du document (comme UML, Graphviz, etc…) Pour en connaître un peu plus je vous invite à consulter le source du fichier sous github ici [12].

Le second pom nommé tc-asciidoctor-html-parent propose d'étendre le premier en spécialisant la génération documentaire au profit d’une interface html supportant la présentation. De façon à rendre celle ci plus dynamique, le pom va intégrer dynamiquement lors du build, une librairie Javascript revealjs [13] qui sera downloader via le plugin maven download-maven-plugin [14]. Ensuite, le plugin asciidoctor-maven-plugin va intégrer cette librairie pour construire la présentation en html. Pour en connaître un peu plus je vous invite à consulter le source du fichier sous github ici [15].

Utilisation

Il faut maintenant réintégrer les sources précédemment utilisées et les mettre dans un projet maven héritant du dernier pom présenté (tc-asciidoctor-html-parent)

On build :

mvn clean process-resources

et cela nous donne brutalement la présentation accessible ici [16].

Déjà c’est cool notre présentation à déjà bien évolué mais on voit que la compatibilité n’est pas présente à 100%.... le cadrage n’est pas parfait... Sachant cela, chacun verra midi à sa porte et choisira son approche préférée.

Conclusion

Nous n’avons pas été très loin dans l’utilisation de Asciidoc ni de Asciidoctor son équivalent, mais sont utilisation étant simple, il ne m'a pas semblé pertinent de s’attarder sur ce genre de problématique. Par contre son utilisation et les différents de logique de build existant entre les deux outils me semblait plus intéressant à montrer (sachant que Asciidoctor étant en ruby, via l’utilisation de l’installeur Ruby et de l’application Gem il est aussi possible de l’utiliser sans une industrialisation maven qui pourtant est, il me semble pertinente)

Voilà ainsi dans l’avenir, si je réalise une présentation en association avec un article, je procéderai comme pour le code source, je le déposerai dans le dépôt github du blog. Cela permettra de rendre plus dynamique les articles.

Pour ceux voulant aller un peu plus loin, les références suivantes sont pour vous: [17,18,19]

Remerciements (j’en fais pas souvent):

Merci à David et Christophe qui ont piqué ma curiosité avec asciidoc. Moi qui venait de Latex et de son formalisme jusqu’au boutiste, j’ai découvert un compromis et un outil fort pratique qui je pense va devenir un compagnon régulier de ma veille. (Il ne me reste qu'a tester la construction et l’intégration des diagrammes, mais on verra ça a la volée)

Références

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2018/11/asciidoc.html
[2] https://asciidoctor.org/docs/
[3] https://asciidoctor.org/docs/what-is-asciidoc/
[4] https://asciidoctor.org/docs/asciidoc-asciidoctor-diffs/
[5] https://maven.apache.org/plugins/maven-site-plugin/
[6] https://un-est-tout-et-tout-est-un.blogspot.com/2018/01/maven-preparons-des-parents.html
[7] https://github.com/dvaillant/coursAsciidoc
[8] https://asciidoctor.org/docs/asciidoctor-maven-plugin/
[9] https://github.com/asciidoctor/asciidoctorj-pdf
[10] https://github.com/asciidoctor/asciidoctorj-diagram/
[11] https://asciidoctor.org/docs/asciidoctor-diagram/
[12] https://github.com/collonville-tom/tc-parent/blob/master/tc-asciidoctor-pdf-parent/pom.xml
[13] https://revealjs.com/
[14] https://mvnrepository.com/artifact/com.googlecode.maven-download-plugin
[15] https://github.com/collonville-tom/tc-parent/blob/master/tc-asciidoctor-html-parent/pom.xml
[16] https://collonville-tom.github.io/tc-un-est-tout-et-tout-est-un/tc-asciidoctor-article/ascii-article.html
[17] http://www.vogella.com/tutorials/AsciiDoc/article.html
[18] https://powerman.name/asciidoc/
[19] https://riduidel.wordpress.com/2017/06/16/generer-mon-asciidoc-dans-docker/




dimanche 10 juin 2018

SGBD : Liquibase

La réalisation d’un système logiciel fait intervenir de nombreux types de composants. Qu’ils soient dédiés à l’IHM, à la logique métier ou aux données, leur conception, réalisation et maintenance sont des défis pour les équipes de développement.

Différentes avancées ont permis de rationaliser un certains nombre de ces activités en proposant des solutions afin de gérer le versionnage des sources. Ainsi des outils comme CVS ou SVN, dans un premier temps, ou Git et Mercurial sont couramment employés afin de gérer le code sources, gérer les versions et faciliter l’industrialisation.

Pourtant il existe un type de composant que ne permet pas de gérer ce type d’outil : les SGBD. En effet, si les outils comme git ou svn sont capable de tracer dans le temps l’ensemble des modifications apportés à un code source, il demeure très complexe d’avoir une vision similaire sur un SGBD car même si il est toujours possible de gérer son schéma statiquement, cela n’aide pas à la gestion des modifications au sein même de la base ni du devenir des données.

Approche proposée : Liquibase

Pour répondre à cette problématique, je vous propose de vous présenter l’outil Liquibase [6]. Nous allons au travers de cet article nous référer à certaines définitions évoquées dans les articles précédents sur les SGBD, je vous invite donc à vous y reporter [1-5]

Liquibase est un outil permettant la gestion du cycle de vie d’une base de données comme le ferait un gestionnaire de code sur du source. Il s’utilise sous deux formes soit en stand alone, c’est à dire à partir de binaire (la version 3.5.5 de liquibase [7] ), soit en utilisant son plugin maven (la version 3.6.1 [8-9]) .

Dans son principe, l’utilisation de l’un ou de l’autre importe assez peu puisque les deux s’appuient sur les mêmes processus et les même types de données cependant, afin d’affiner notre compréhension de l’outil, nous présenterons les deux approches.

Principes

Avant cela, il faut décrire le fonctionnement de liquibase. L’outil s’appuie des fichiers de type databasechangelog (au format YAML ou JSON mais sont préférentiellement en XML) qui recensent des instructions de gestion.

<databasechangelog xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemalocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd
        http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
</databasechangelog>

Ces instructions peuvent être de différents types. Elles peuvent être soit:
  • des préconditions 
  • des properties 
  • des changeset 
  • des includes

Les preconditions

Les préconditions se déclarent soit dans un databasechangelog, soit dans un changeset. Elles permettent de définir les conditions d'exécutions pour les autres types d’instructions auxquelles elles sont associées selon le type de base de donnée utilisé ou selon des paramètres internes à la bd (des données particulières dans certaines relations) où selon des paramètres externes (données d’environnement) ou encore selon l'état de la base (lorsqu’il s’agit d’appliquer ou non un patch sur le schéma par exemple)

<preconditions>
     <dbms type="oracle">
     <runningas username="SYSTEM">
 </runningas></dbms></preconditions>

Les propriétées

Les propriétés sont des données basiques permettant de paramétrer les autres instructions contenus dans les fichiers databasechangelog avec un formalisme proche de celui utilisé par les paramètres maven. Par exemple


<property dbms="oracle" name="clob.type" value="clob">
</property>

permet lors la déclaration d’un attribut de relation de préciser:

<column name="${column1.name}" type="${clob.type}">
</column>

Les includes

Les includes sont des instructions permettent d’ajouter d’autres fichiers de type databasechangelog contenant eux-même d’autres instructions ou scripts SQL. Leur utilisation est importante car elles permettent de dissocier physiquement les différentes évolutions de la base de données mis en oeuvre tout au long du cycle de vie du projet. C’est une instruction simple permettant d’inclure soit un fichier spécifique, soit le contenu d’un répertoire:

<include file="com/example/news/news.changelog.xml">
<includeall path="src/main/resources/liquibase/changelogs">
</includeall></include>

Les changeSet

Enfin les instructions de type changeset portent les informations sur les changements à apporter à la base de données. Ces changement concernent soit des instructions de création, des modifications, des injections de données, des suppressions et/ou filtrage et nettoyage des données ou, de façon plus définitive, des suppressions de relations.


<changeset author="bob" id="1">
  <createtable tablename="user">
   <column name="id" type="int">
   <column name="nom" type="varchar(32)">
   <column name="age" type="int">
  </column></column></column></createtable>
 </changeset>


A ces modifications d’ajout où de suppression, les changeset permettent également (et c’est même plutôt conseillé) de définir une procédure de rollback. Ces procédures sont quasi aussi importante que les évolutions car elles permettent de garantir l’annulation des modifications faites sur la base lors d’un échec d’un déploiement logiciel par exemple.


<rollback>
   <droptable tablename="testTable">
   </droptable>
</rollback>


Chaque changeset implique un nouvel état de la base. Ils sont identifiables via un identifiant et doivent être porté par un utilisateur. Ainsi, pour chaque nouveau changeset de déclaré, un nouvel état possible de la base peut être considéré et ce de façon séquentielle (ordre de lecture du fichier, peut importe les identifiants).



Ce processus de versionnage de base va remplir 80% des cas d’utilisations de liquibase en donnant des moyens simples de suivre les versions et les modifications apportés au fil du temps à la base de données. Cette utilisation va surtout faciliter la reconstruction des versions successives du schéma d’une base.

Il est pourtant possible d’aller plus loin dans l’utilisation de Liquibase et de réaliser des conceptions plus modulaire de la base de données en s’appuyant sur les contextes.

Les contextes

Les contextes sont des sortes de tags (attentions ce ne sont pas des tags car ce concept existe aussi dans liquibase mais nous verrons cela plus loin) permettant d'exécuter qu’un sous ensemble des changeSet. Sans forcément s’appuyer sur des données environnementale comme pour les préconditions, l’utilisation de contexte va permettre de classer les changeset dans des ensembles exécutables ensemble ou non selon le besoin.

Ainsi, l’utilisation première que l’on fera des contextes est de différencier les changeset permettant de construire la base dans une version iso de la prod et les changesets de test dont la seul vocation est de fournir des jeux de données à injecter pour les phases IVQ. Ceci est un premier use case de l’emploie des contextes, mais les contextes effectivement peuvent permettre aussi de concevoir une base de schéma distribué et de façon modulaire en intégrant les spécificités selon l’application qui l'exploite. Cette approche permet d’avoir une approche globale de conception tout en gardant une approche locale de déploiement.


<changeset author="bob" context="schema" id="1">
  <createtable tablename="testTable">
   <column name="id" type="int">
   <column name="nom" type="varchar(32)">
   <column name="age" type="int">
  </column></column></column></createtable>
  <rollback>
   <droptable tablename="testTable">
  </droptable></rollback>
 </changeset>

 <changeset author="bob" context="test" id="2">
  <insert tablename="testTable">
   <column name="id" value="2">
   <column name="nom" value="tata">
   <column name="age" value="59">
  </column></column></column></insert>
  <rollback>
   <delete tablename="testTable">
    <where>id=2</where>
   </delete>
  </rollback>
 </changeset>

 <changeset author="bob" context="schema" id="3">
  <addcolumn tablename="testTable">
   <column name="address" type="varchar(255)">
  </column></addcolumn>
  <rollback>
   <dropcolumn columnname="address" tablename="testTable">
  </dropcolumn></rollback>
 </changeset>


Dans cet exemple, un changeset construit une première partie de la base dans un contexte schema et suivit par une injection de données dont la finalité est de faire des tests (contexte test). Ces deux parties du processus pourront alors être appelé indépendamment et géré de façon séparé (en terme de correction). Ainsi, si une nouvelle modification doit être appliqué au schéma de base (ici le changeSet d’id 3), il sera possible de valider différents scénarios de mise à jour et construction de la base:

tout le processus d’initialisation du schéma peut être exécuté sur une base vierge de données (appel du contexte schema seul) et vérifier ensuite que l’injection des données est toujours faisable (en appliquant ensuite le contexte test). appliquer les deux contextes simultanément afin de vérifier que la modification du schéma sur une base contenant des données ne pose pas non plus de problème afin de garantir qu’en production, la migration se réalise sans problème malgré les données

Exécution

Nous en avions parlé au début de l’article, l’utilisation de liquibase peut se faire soit en ligne de commande soit via un plugin maven. Bien sur l’utilisation en ligne de commande fournira plus de finesse dans la mise au point et le debugage, cependant l’utilisation du plugin maven permettra une industrialisation plus efficace dans les phase de compilation, test, intégration et mise en production.

CLI

Appliqué en ligne de commande, liquibase nécessite quelques ressources telles que le fichier changelog.xml, les login/pwd de la base de données, l’url de la base, son driver, et le classpath dans lequel se trouve ce driver. Ensuite quelques paramètres optionnels peuvent être positionnés comme le niveau de debug des logs lors de l'exécution ou l’application spécifique de tel où tel contexte (qui va permettre de filtrer l'exécution des changeset selon les besoins locaux). A cela, il faudra ensuite utiliser la commande voulu comme suit avec la commande update.


$LIQUIBASE_HOME/liquibase --logLevel debug --contexts=schema \
--changeLogFile src/main/resources/liquibase/changelog.xml \
--username test --password test --url=jdbc:h2:file:./target/test \
--driver=org.h2.Driver --classpath=$H2_HOME/bin/h2-1.4.197.jar  update

Toutes ces commandes vont alors servir outils pour renforcer les moyens de gestion du cycle de vie de la base de données, il y a bien sur ceux que nous avons déjà vu : update et rollback mais aussi des commande pour vider la base, ou poser un tag sur un état particulier de la base pour pouvoir y revenir le cas échéant. En voici la liste :
  • update qui applique tous les changeset 
  • updateCount qui applique les n changeset suivant 
  • rollback qui revient a la version portant le tag 
  • rollbackCount qui exécute un rollback sur les n précédent changeset 
  • tag qui applique un tag sur la version courante 
  • dropAll qui vide la base 
Bien sur il y en a d’autres et je vous invite à consulter la documentation pour cela [10].

Ainsi par exemple, l’application de la commande précédente va permettre l’execution des changesets exclusivement associé au contexte schema. Ces changeset vont alors initialiser une base de type H2 dans le répertoire target du projet à partir du changelog.xml du projet.

En utilisant la commande dropAll, il va être possible de rincer complètement la base nouvellement creer. Il sera possible alors d’utiliser la commande updateCount afin de créer la base en mode pas à pas avec un incrément de la valeur du paramètre. De la même manière, il va être possible de réaliser des retours arrières avec la commande rollbackCount ou rollback en spécifiant un tag si la commande tag a ete préalablement utilisé également. De nombreuses possibilités de tests et de manipulations sont réalisable afin de garantir que la base de données sera toujours fonctionnelle.

Maven

Il est aussi possible de réaliser l’application des commandes liquibase lors de l'exécution d’un processus maven. Pour cela il est nécessaire d’employer le plugin maven liquibase-maven-plugin [9]. Ce plugin permet l’utilisation des goals reprenant les différentes commandes exploitable en CLI [11].

Ce plugin est un plugin dont l’utilisation est très classique. Une partie configuration permettant de soit definir les parametres url, driver and coe, directement dans le plugin [12] ou plus simplement de les définir dans un fichier dédié [13].

Ensuite, soit le goal est exploité par un appel avec la commande maven soit appelé par un appel synchronisé avec la phase adéquat.



<build>
  <plugins>
   <plugin>
    <groupid>org.liquibase</groupid>
    <artifactid>liquibase-maven-plugin</artifactid>
    <version>3.6.1</version>
    <configuration> 
       <propertyfilewilloverride>true</propertyfilewilloverride>
       <propertyfile>src/main/resources/liquibase/h2-embedded.properties</propertyfile>
    </configuration>
    <executions>
     <execution>
      <phase>process-resources</phase>
      <goals>
       <goal>update</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>

Conclusion

Nous venons de passer en revu les fonctionnalités de liquibase et ses possibilités de gestion du versioning d’une base de données. Il fournit de nombreuses possibilités de manipulations afin d’affiner les tests de migrations des bases contenant où non des données en accompagnant les mises à niveau mais aussi les rollback. Finalement par une gestion plus cohérente du cycle de vie de la base de données, liquibase permet également de concevoir des bases plus flexibles et plus modulaires.

Nous avons aussi vu qu’il était possible de l’utiliser soit de façon manuelle au travers d’une interface CLI permettant de simplifier la mise au point mais aussi de l’employer avec un plugin maven en vue d’automatiser les processus de migration et de modifications.

Cet outil est clairement un incontournable de l’industrialisation du développement en fournissant des moyens pour maîtriser le cycle de vie de la base de données nécessaire à notre application que ce soit en terme de test ou de mise en production.

Références

[1] http://un-est-tout-et-tout-est-un.blogspot.fr/2017/09/postgres-commandes-de-base.html
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/sgbd-r-introduction.html
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/sgbd-r-normalisation.html
[4] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/sgbd-r-algebre-relationnelle.html
[5] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/sgbd-r-jointures.html
[6] https://www.liquibase.org/
[7] https://github.com/liquibase/liquibase/releases/download/liquibase-parent-3.5.5/liquibase-3.5.5-bin.zip
[8] http://www.liquibase.org/documentation/maven/index.html
[9] http://mvnrepository.com/artifact/org.liquibase/liquibase-maven-plugin
[10] https://www.liquibase.org/documentation/command_line.html
[11] http://www.liquibase.org/documentation/maven/index.html#using_configuration_property_files
[12] https://www.yegor256.com/2014/07/20/liquibase-in-maven.html
[13] http://www.liquibase.org/documentation/liquibase.properties.html

samedi 24 mars 2018

Maven : assembly, targz et jdeb

Aujourd’hui nous allons nous intéresser à la production du livrable issue de la production logicielle. Le sujet ne va pas etre forcement tres long a traiter tant maven permet de chose si l’on utilise les bons plugins.

La production du livrable impose avant tout la définition de celui ci. Souvent, il va être dépendant de la plateforme et du langage. Dans notre cas, nous allons tâcher de produire nos composants dans le contexte d’utilisation du langage Java et nous nous projeterons dans une plateforme linux, la debian et la plateforme windows.

Bien sûr on aurait pu passer par l’utilisation de profile maven dans lequel nous aurions spécifier selon l’OS différents plugins afin de répondre à cette problématique de production. Par exemple en définissant les profiles suivants:

<profiles>
 <profile>
  <activation>
   <os>
    <family>windows</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
 <profile>
  <activation>
   <os>
    <family>linux</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
</profiles>


Nous n'utilisons pas cette approche car d’une part non nécessaire puisque Java est multiplateforme et d’autre part elle complexifie lourdement la production (l’utilisation de profile n’etant une bonne chose que si on en peut vraiment pas faire autrement)

Au lieu de cela, nous allons plutôt tricher un peu et prendre le parti pris que nous souhaitons livrer simplement un targz pour windows et un deb sous linux (comme cela, on pourra produire le même jar et livrer d’un côté un script bash pour lancer notre appli et de l’autre un bat.) Pour répondre à ce problème nous allons utiliser le plugin maven-assembly-plugin.

Ce plugin est un plugin indispensable pour la plupart des builds maven car au delà de l’utilisation ce que nous allons en faire, il permet de faire surtout toute sorte de compositions, de construire tout type de paquet de livraison mais aussi de pouvoir construire des jar au contenu spécifique (nous y reviendrons mais pas dans cet article) .

Nous avons donc deux types de packaging à réaliser, un pour le targz et un autre pour le deb. On va configurer le plugin pour qu’il fasse les deux en même temps en définissant deux fichiers d’assembly:

<plugin>
 <groupid>org.apache.maven.plugins</groupid>
 <artifactid>maven-assembly-plugin</artifactid>
 <version>2.5.3</version>
      <configuration>
  <finalname>${project.artifactId}-${project.version}</finalname>
  <ignoredirformatextensions>true</ignoredirformatextensions>
  <outputdirectory>${project.build.directory}</outputdirectory>
       </configuration>

 <executions>
  <!-- Packaging debian -->
  <execution>
   <id>assembly-debian</id>
   <phase>prepare-package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-debian.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
  <execution>
   <id>assembly-targz</id>
   <phase>package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-targz.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
 </executions>
</plugin>


Quelques explications sur l’utilisation du plugin dans le pom. On précise le répertoire de sortie et le nom général de la composition. On spécifie ensuite par type de composition un élément de configuration en utilisant appendAssemblyId. Ceci permet d’ajouter le classifier “assembly” au nom de la composition, mais ici on va s’en passer. On met false. On précise également pendant quelle phase et quel goal les assembly devront être construit. Enfin on donne au plugin le fichier d’assembly qui précise le contenu de la composition.

Le fichier assembly est la dernière partie permettant de construire la composition. Il s’agit d’un fichier xml définissant plusieur choses: le format de sortie, les dépendances à joindre et ou les déposer et enfin les répertoires annexes que la composition doit contenir. Regardons celui du targz:

<assembly>
 <id>bundle</id>
 <formats>
  <format>tar.gz</format>
 </formats>
 <includebasedirectory>false</includebasedirectory>

 <dependencysets>
<!--  Inclusion interfaces classifier et librairies-->
  <dependencyset>
   <unpack>false</unpack>
   <scope>runtime</scope>
   <useprojectartifact>false</useprojectartifact>
   <useprojectattachments>true</useprojectattachments>
   <outputdirectory>/opt/monappli</outputdirectory>
   <includes>
    <include>*:${project.artifactId}:*:assembly:*</include>
   </includes>
  </dependencyset>
 </dependencysets>

  <filesets>
      <fileset>
          <directory>src/main/debian/etc/init.d</directory>
          <outputdirectory>/etc/init.d</outputdirectory>
          <usedefaultexcludes>true</usedefaultexcludes>
          <filtered>true</filtered>
          <includes>
              <include>monappli</include>
          </includes>
      </fileset>
  </filesets>
</assembly>


Inutile de commenter le format, il parle de lui-même. Les dependencySet se construisent pour incorporer à la composition les éléments des dépendances qu’il faudra ajouter et sous quel forme. Par exemple on peut spécifier si elles seront dézipper dans la composition (cela est pratique par exemple lorsque l’on veut fusionner des artifacts ensemble pour faire un jar), le répertoire ou ces éléments seront déposé et enfin un ensemble d'artifact déclarer sous la forme d’une regex.

Les fileSet précisent de la même manière que pour les artifacts l’ensemble des fichiers et répertoires à construire dans notre composition. Ces éléments sont généralement issus des ressources du projets et il est possible de réaliser du remplacement clef/valeur dedans (via le resources filtering). A l’issu de l’application de l’assembly, on aura alors tous ces éléments dans un tar.gz… cool!

Bon bien du coup on fait pareil pour le paquet debian non? et bien pas tout à fait. sur le principe, on va procéder de la même manière en utilisant un fichier d’assembly rigoureusement identique sauf sur le format que nous définirons a “dir”. Ce format ne fait rien a part préparer les éléments de la composition. Pourquoi prendre ce format? Tout simplement parce que le plugin assembly n’est pas capable de construire quelque chose d’aussi complexe qu’un paquet debian. On va donc s’appuyer d’un autre plugin pour faire cela: jdeb. Avant cela je vous renvoie à [3] pour compléter vos ressources avec les fichiers de contrôles qui vont bien et les intégrer à l’assembly.

Ceci étant fait, penchons nous sur le plugin jdeb. Celui ci se configure assez classiquement en précisant sa phase de build et son goal. A cela, on va preciser le nom du paquet debian, le repertoire des fichiers de controles, et enfin un ensemble de dataset precisant tous les repertoire, fichiers et autres resources qui va falloir ajouter au paquet. Attention, a ce nieau, il faut avoir une vision clair des droits qui seront appliqué lors de l’installation, puisque l’on va les preciser ici. Voyons cela:

<plugin>
 <artifactid>jdeb</artifactid>
 <groupid>org.vafer</groupid>
 <version>1.5</version>
 <executions>
  <execution>
   <phase>package</phase>
   <goals>
    <goal>jdeb</goal>
   </goals>
   <configuration>
    <deb>${project.build.directory}/${project.artifactId}-${build}_${versiontimestamp}_all.deb</deb>
    <verbose>true</verbose>
    <controldir>${project.build.directory}/${project.artifactId}-${project.version}/DEBIAN</controldir>
    <dataset>
     <data>
      <src>${project.build.directory}/${project.artifactId}-${project.version}/opt</src>
      <type>directory</type>
      <mapper>
       <type>perm</type>
       <prefix>/opt</prefix>
      </mapper>
     </data>
    </dataset>
   </configuration>
  </execution>
 </executions>
</plugin>


Voilà, nous venons de passer en revu comment construire les livrables de nos projets. Bien sur faire du java permet déjà de packager les sources mais pour être propre, l'idéal est de fournir un composant standard comme un targz ou mieux un deb contenant tous les éléments permettant de faire vivre l’application (fichier de conf, de scripts, etc…)

Références: 

[1] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html
[2] https://github.com/tcurdt/jdeb/blob/master/src/examples/maven/pom.xml
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/construire-un-paquet-debian.html

jeudi 15 mars 2018

Maven : Faire un plugin

La constitution d’un plugin maven est souvent une étape indispensable pour régler certaines problématiques de nos projets.

En réaliser un n’est pas très compliqué, cela consiste essentiellement en un packaging maven un peu particulier et en la définition d’un point d’entrée spécifique, le MOJO.

Le packaging doit être de type maven-plugin, il suffit donc de créer un projet maven classique intégrant ce packaging [1]. Attention par contre car le nommage de votre plugin conditionnera la commande qu’il faudra employer pour l’appeler.

Ainsi, imaginons un plugin maven permettant d’extraire les données du projet (issu du pom, on va pas faire trop compliqué….).

Ainsi on va définir notre projet maven comme suit:

<groupid>org.tc.maven.plugin</groupid>
<artifactid>tc-project-info-extractor-maven-plugin</artifactid>
<name>${project.artifactId}-${project.version}</name>
<version>${build}${snapshot}</version>
<packaging>maven-plugin</packaging>


On va utilise le plugin maven maven-plugin-plugin [2] :

<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-plugin-plugin</artifactid>
<version>3.5.1</version>
</plugin>


On va ajouter des dépendances maven qui nous permettront de construire notre Mojo:

<dependencies>
 <dependency>
  <groupid>org.apache.maven.plugin-tools</groupid>
  <artifactid>maven-plugin-annotations</artifactid>
  <version>3.3</version>
  <scope>provided</scope>
 </dependency>
 <dependency>
  <groupid>org.apache.maven</groupid>
  <artifactid>maven-plugin-api</artifactid>
  <version>3.2.3</version>
 </dependency>
 <dependency>
  <groupid>org.apache.maven</groupid>
  <artifactid>maven-project</artifactid>
  <version>2.2.1</version>
 </dependency>
</dependencies>


Et c’est tout pour avoir une build par défaut sachant que pour appeler notre plugin pendant un build il faudra appliquer soit le nom complet du plugin :

$ mvn org.tc.maven.plugin:tc-project-info-extractor-maven-plugin:extract


soit son nom court:

$ mvn tc-project-info-extractor:extract


Le nom court étant la simple réduction du nom de l’artifactId sans son extends maven-plugin.

Alors dans cette commande on identifie le mot clef extract. Ce mot clef est le goal de notre plugin (bien sur on peut en définir plusieurs). Ce goal, nous allons le voir est directement relié à notre Mojo, maven se chargeant lorsque l’on appelle ce goal d'exécuter le Mojo associé.

Voyons donc comment construire ce Mojo. Un Mojo [6] est un simple Pojo avec un M pour Maven. Ce Mojo va hériter de la classe AbstractMojo nous incitant à définir la classe exécute qui servira de point d’entrée pour l'exécution du plugin pour le goal associé. Pour définir justement le goal associé, on va s’appuyer l’annotation @Mojo et utiliser @Component pour injecter le projet maven dans notre mojo (en profitant des fonctionnalités d’injection de dépendance de plexus, nous y reviendrons).

A noter qu’il existe les annotations @Execute permettant de forker l'exécution du build maven et @Parameter qui permet de rendre d’injecter des données au plugin au moment de son appel (soit via -DmonParam soit directement dans le pom sous la forme de valeur de configuration) mais on entrera pas dans les détails de ces dernières.

@Mojo(name = "extract", defaultPhase = LifecyclePhase.CLEAN)
public class ProjectExtractorMojo extends AbstractMojo {

    @Component
    MavenProject project;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("ProjectExtractorMojo execution begin");
        getLog().info(project.getName());
        try {
            final BufferedWriter bos = new BufferedWriter(new FileWriter("target/resultFile.csv", true));
            bos.newLine();
            bos.write(project.getPackaging());
            ...
            bos.write(project.getName());
            bos.flush();
            bos.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        ...
        getLog().info("ProjectExtractorMojo execution finish");
    }
}


Voila, à l'exécution de la commande maven, le mojo sera exécuté générant un fichier csv dans lequel il déposera pour chaque projet les caractéristiques de celui ci. Ca ne fait pas grand chose mais ca mange pas de pain. A bientôt et pour ceux qui voudront d’autres références, elles sont ici:

Références


[1] http://maven-guide-fr.erwan-alliaume.com/maven-guide-fr/site/reference/writing-plugins-sect-plugin-prefix.html
[2] https://maven.apache.org/plugin-tools/maven-plugin-plugin/examples/using-annotations.html#POM_configuration
[3] https://dzone.com/articles/a-simple-maven-3-plugin
[4] https://www.codeproject.com/Articles/1013928/Maven-Plugin-Development
[5] http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
[6] http://maven.apache.org/plugin-developers/
[7] https://maven.apache.org/plugin-tools/maven-plugin-plugin/descriptor-mojo.html#skipErrorNoDescriptorsFound

lundi 12 mars 2018

Les annotations


Nous avions parlé des annotations lorsque nous avions traité des evolutions Java 5 [1]. Aujourd’hui je vous propose de nous intéresser à en faire quelques unes et de réaliser le ou les processeurs d’annotations qui vont bien pour les traiter dans les contextes de la compilation puis at runtime. Nous tâcherons de traiter les différents cas que nous pourrions rencontrer en définissant des annotations sur des cibles variées.

Tout d’abord, premier point, une annotation c’est quoi? Alors pour présenter cela, je vais reprendre l’exemple que nous avions vu alors dans [1]:

@SuppressWarnings(value = { "static-method" })
public <S> S functionGen(S s)
{
 return s;
}

Comme vous l’avez deviné, l’annotation ici est évidemment SuppressWarnings. Elle est identifiable via un @ et prend ici une liste de paramètres réduits à un seul. Le détail nous importe peu, ce qui est intéressant est l’information que l’annotation va transporter.

Une annotation a un nom, potentiellement des variables, et si ce n’est pas visible ici, possèdes des informations sur son contexte d’utilisation, et de sa porté. Ces deux derniers éléments sont positionnés lors de la définition de l’annotation.

Son contexte d’utilisation permet de préciser où celle ci pourra être utilisé. Ici dans l’exemple, l’annotation est positionné sur la méthode mais celle-ci peut être positionné sur une classe, une donnée membre ou même un package, tous ces éléments n'étant pas en exclusion mutuelle. Potentiellement une même annotation peut être utilisé sur plusieurs type de structures différentes.

De façon complète, il est possible de positionner une annotation (sauf définit contrairement) sur:

  • les classes 
  • les méthodes 
  • les interfaces 
  • les variables 
  • les packages


Au delà du cadre de son utilisation, la porté a par contre plus d’importance sur l’utilisation fonctionnelle de l’annotation. En effet, la porté définit à quel moment l’annotation doit être traitée comme une information utile au programme java. Celle ci peut être soit intégré qu’au code source (et ne sera plus visible ensuite, on utilise ce type d’annotation essentiellement pour donner de l’information au développeur ou aux analyseurs de code), soit intégré au code binaire (permettant au compilateur de réaliser des tâches supplémentaires) soit intégrée pour être traité au runtime afin de permettre d'adjonction de comportement dynamique (beaucoup de framework utilise aujourd’hui ce type d’API)

Bien sur ces différents cas d’utilisation des annotations nécessitent des mécanismes et des traitements différents. Autant on comprend vite comment va fonctionner les traitements d’annotations associé au code source qui auront surtout un rôle informatif. Par contre lorsque les annotations sont traité lors de la phase de compilation, il sera nécessaire d’utiliser le mécanisme des processeurs d’annotations (l’API Pluggable Annotation Processor). Enfin lorsque celles ci sont définies pour être traité at runtime, il faudra user de l’introspection et de certains type de pattern afin de profiter intelligemment des possibilités des annotations.

Mais avant de pouvoir traiter les annotations, je vous propose de reprendre tous ces points en définissant notre propre annotation.

Prenons d’abord l’exemple d’une annotation fournissant un lien documentaire explicitant la référence fonctionnelle ou technique amont. Pour la définir, nous allons créer une pseudo interface en utilisant le caractère @ avant le mot clef interface. Nous y déclarent une propriété document et une propriété chapitre et une propriété page.

Ces informations portent le côté fonctionnel, il faut maintenant définir le côté technique pour spécifier le contexte d’utilisation et de traitement.

Pour cela il faut utiliser des méta-annotations (une annotation pour annotations):

  • @Documented : Permet de préciser la présence de l’annotation dans la java doc de l'élément portant celle ci 
  • @Inherit : Permet de transmettre les propriétés informationnelles de l’annotation aux classes filles de l'élément portant celle-ci 
  • @Retention : précise le niveau de traitement (source, bitcode ou runtime) 
  • @Target: précise la ou les cibles sur lesquels l’annotation peut être apposé.


Dans le contexte très basique de cet exemple, nous nous limiterons a simplement a specifier la Retention a source.

@Retention(RetentionPolicy.SOURCE)
public @interface Reference
{
 String document();
 String chapitre();
 int page();
}

Voilà dans nos classes nous pourrons alors par exemple préciser à quelle partie de la documentation de la spécification cliente les classes font références:

@Reference(document=”docAmont.doc”,chapitre=”Service Client”,page=44)
public class ServiceClient
{
 public void service();
}

Imaginons maintenant une nouvelle annotation nécessitant un traitement plus profond à effectuer lors de la compilation. Par exemple une annotation @Relation qui associe a une classe permettra la production d’une table du même nom dans une bd (pour faire simple, nous passerons sur la production des attributs de relation mais on imagine facilement qu’il faudrait faire une annotation pour les données membres de la classe)

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation
{
}

Ainsi, sur une classe de notre application, nous placerons tout simplement notre annotation ainsi:

@Relation
public class HelloWorld
{
}

Alors bien sur si ceci nous permet de sémantiquement faire un lien entre notre classe et la relation d’une BD, il nous faut procéder à la mise en oeuvre d’un Processeur d’Annotation dont le but sera de récupérer toutes les classes postant celle-ci et exécutera un CREATE TABLE en sql sur la BD en question.

Pour réaliser un processeur d’annotation, il n’y a rien de complexe, il suffit de produire un simple artifact maven de type jar dans lequel nous trouverons:

  • une classe RelationProcessor dérivant la classe AbstractProcessor portant les annotations SupportedAnnotationTypes (précisant quelles annotations nous traitons) et SupportedSourceVersion (précisant dans quel version java la compilation aura lieu) 
  • un fichier de conf dans le répertoire META-INF/services nommé javax.annotation.processing.Processor et contenant le nom complet de notre classe processor.

A noter qu’il sera nécessaire de désactiver l’utilisation des processeurs d’annotations pour le packaging de notre propre processeur (sinon celui ci tentera se traiter lui même), il s’agit de l’option de configuration -proc:none du maven-compiler-plugin. Ensuite il suffira de faire une dépendance à la compilation pour que notre logiciel, lors de sa compile trouve par inspection le fichier de conf de notre jar contenant le processeur et l'exécute.

Voyons comment cela se presente:

Tout d’abord nous allons definir un pom module pour plus de simplicité:

   <modules>
    <module>tc-annotation</module>
    <module>tc-processor</module>
    <module>tc-helloworld</module>
   </modules>


Le premier module contient nos annotation, le second contiendra le processeur et le troisieme sera notre classe helloworld

Le module d’annotation n’a rien de special, c’est un projet maven classique produisant un jar (donc l’utilisation du plugin maven-compiler-plugin est largement suffisant) et contenant la définition de notre annotation:

package annot;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation {}



Le module processor est un peu plus complexe, comme nous l’avons vu il faut definir une classe heritant de la classe AbstractProcessor. Cette classe va recuperer l’ensemble des elements portant notre annotation et envera une requete en BD pour creer la table associée.

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({ "annot.Relation" })
public class HWProcessor extends AbstractProcessor {

    public static final String JDBC_DRIVER = "org.postgresql.Driver";
    public static final String DB_URL = "jdbc:postgresql://localhost:5432/test";
    public static final String USER = "postgres";
    public static final String PASS = "";
    
    public static String CREATE="CREATE TABLE ";

    @Override
    public boolean process(Set arg0, RoundEnvironment arg1) {
        System.out.println("HWProcessor running");
        Connection conn =null;
        try {
            Class.forName(JDBC_DRIVER);
            conn = this.getConnection();
        
        for (Element e : arg1.getRootElements()) {
            if (e.getAnnotation(Relation.class) != null) {
                this.createRelation(e,conn);
            }
        }
        } catch ( SQLException | ClassNotFoundException e1) {
            System.out.println(e1);
            return false;
        }
        return true;
    }
    
    public void createRelation(Element e,Connection conn) throws SQLException
    {
        PreparedStatement statement=conn.prepareStatement(this.CREATE+ e.getSimpleName().toString()+"();");
        System.out.println("Mon element : "+e.getSimpleName().toString());
        statement.executeQuery();
    }
    
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DB_URL, USER, PASS);
       }

}


a noter de ne pas oublier d’ajouter le fichier javax.annotation.processing.Processor

au niveau du build maven, il vous faudra ajouter la ligne -proc:none a la configuration du plugin maven-compiler-plugin. Ne pas oublié non plus de bien sur ajouter les dependances a l’artifact contenant l’annotation et ici en plus a postgres puisqu’il s’agit de la BD que nous utilisons.

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <compilerargument>-proc:none</compilerargument>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>postgresql</groupid>
   <artifactid>postgresql</artifactid>
   <version>9.1-901-1.jdbc4</version>
  </dependency>
 </dependencies>


Enfin il reste a utiliser notre annotation dans notre projet. Donc evidement on depose l’annotation sur la classe:

import annot.Relation;

@Relation
public class HelloWorld {}


Enfin il faudra bien sur configurer notre build. Alors soit on precise dans le plugin maven-compiler-plugin que l’on va utiliser un artifact contenant un processeur d’annotation en eventuellement precisant le processeur :

   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <testsource>1.8</testsource>
     <testtarget>1.8</testtarget>
     <annotationprocessorpaths>
      <path>
       <groupid>org.tc.test</groupid>
       <artifactid>tc-processor</artifactid>
       <version>0.1.0-SNAPSHOT</version>
      </path>
     </annotationprocessorpaths>
     <!-- Soit les deux lignes suivantes soit le fichier dans le repertoire 
      services du jar contenant le processor -->
     <annotationprocessors>
      <annotationprocessor>proc.HWProcessor</annotationprocessor>
     </annotationprocessors>
     <!-- Ne pas faire de fork quand on utilise un processeur -->
     <!-- <fork>true</fork> -->
    </configuration>
   </plugin>



Mais cela, on peut aussi se reposer sur la capacité du jdk a decouvrir le processeur et ne declarer l’artifact que comme une dependance de compilation et ne pas mettre les champs annotationProcessors et annotationProcessorPaths (simplifiant largement la complexité du pom au passage). on prefere donc:

<dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-processor</artifactid>
   <version>0.1.0-SNAPSHOT</version>
   <scope>compile</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
    </configuration>
   </plugin>
  </plugins>
 </build>



Voila il ne reste plus qu’a builder notre module:

mvn clean install


ok ca marche:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] tc-annotation-0.1.0-SNAPSHOT ....................... SUCCESS [  4.078 s]
[INFO] tc-processor-0.1.0-SNAPSHOT ........................ SUCCESS [  1.929 s]
[INFO] tc-helloworld-0.1.0-SNAPSHOT ....................... SUCCESS [  1.223 s]
[INFO] tc-annotation-module-0.1.0-SNAPSHOT ................ SUCCESS [  0.038 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------



Voyons voir les log de notre processeur:

HWProcessor running
Mon element : HelloWorld
org.postgresql.util.PSQLException: Aucun résultat retourné par la requête.


Ok donc ca a l’air de s’etre bien passé vérifions la BD:

 psql -U postgres -w -d test -c "SELECT table_name from information_schema.tables where table_name='helloworld';"
 table_name
------------
 helloworld
(1 ligne)


Bonne nouvelle notre table est la dans le schéma!!

Bien voila, nous venons de passer en revu les deux ca d’utilisation que sont les annotations sur les sources et sur les classes. Leur objectifs ne sont clairement pas les même et si le premier cas ne nécessite pas beaucoup de travail, on peut voir qu’il faudra en fournir un peu plus sur le second en élaborant des processeur d’annotations.

Maintenant il reste un dernier cas d’utilisation, les annotations utilisés at runtime. Cependant désolé mais je ne traiterai pas de leur utilisation dans cet article. En effet, d’une part car cela relève globalement de la même approche introspective que pour la conception d’un processeur mais en employant plutôt quelques patterns bien senti comme l’Invocation Handler. Ce dernier est d’ailleur l’objet d’un prochain article et nous en profiterons alors pour jouer avec les dites annotations at runtime et nous verrons que le gros de travail sur le sujet a globalement été réalisé ici.

Références :

[1] Evolution Java 5