#devoxxfr – Maven, Java9 et Jigsaw

Après le gentil JGiven, on passe au film d’horreur modulaire. Coup de bol, Rémi Forax m’a donné quelques clés pour comprendre l’histoire (ça fait vraiment trop people de sortir des phrases comme ça).

Java6 est mort, Java7 est mourant, tout comme maven2 …
Et Java9 commence à faire du Jigsaw (visible danbs le build java9 depuis le début du mois).

Actuellement, un build simple avec le dernier JDK 9 fonctionne avec maven. Et même la création d’un JAR fonctionne. Par contre, générer un JAR de javadoc ne fonctionne pas du tout.

Et donc, clairement, maven marche en java9. MAIS certains plugins devront évoluer (jar, javadoc, …). Mais heureusement, il y a une page dans le wiki de Maven qui liste les versions minimales et toutes les informations associées.

Juste en passant, le build de maven ne dure qu’une minute. C’est rapide !

Une fois qu’on ajoute des modules dans le code, il faut une version récente du maven-compiler-plugin pour supporte Jigsaw. Parce que l’équipe Maven l’a déja corrigé.
Et une fois que ça compile … on teste. Mais là, Surefire ne gère pas encore les modules, et du coup les tests s’exécutent en-dehors des modules. C’est moins bien, mais c’est déja ça.

Mais globalement, tant qu’on ne joue pas à builder de l’OSGi/Jigsaw, tout à l’air bien facile à faire.

Et maintenant, chez Maven, on essaye de modulariser. Mais c’est moins facile, puisqu’il faut nettoyer tous les arrangements faits avec le passé (plusieurs modules qui utilisent le même package, par exemple).

Bon, et maintenant, le morceau compliqué des multi-releases jars : des jars buildables avec plusieurs versions. Ca sera implémenté (joliment, je trouve) avec un projet multi-module : un module basique, et un module par version de Java séparée, et enfin un assembly. ca fait vraiment bien sens, je trouve.

Et lors de la compilation, Hervé nous rappelle qu’il faut utiliser le toolchain maven, pour garantir que le bon compilateur est utilisé.

Cinq ans d’ALM, putain !

Encore une suite à cinq ans, putain !

Oui, je mets le terme ALM à toutes les sauces, parce que c’est mon bon plaisir.

Donc, sur ce gros projet, j’ai fait ce qu’on pourrait de l’ALM. Qu’est-ce que je mets sous ce terme ?

L’ensemble des tâches qui permettent de passer du code au produit.

Ca inclut, de façon non exhaustive

  • Les outils de build
  • Les outils d’intégration continue
  • L’intégration entre ces outils et les outils de suivi de bugs.

Et évidement, je vais vous (re)parler de Tuleap.

Mais avant, je vais reprendre les étages un par un

Compiling !

Aimablement fourni par http://xkcd.com/303/ mais vous vous en doutiez, non ?

Quand on a commencé notre projet, j’avais déja touché du build maven multi-module. Maias je n’imaginais pas que j’en ferais autant, puisque notre build intègre maintenant

  • De la compilation flex avec flexmojos (raisonnablement facile)
  • De la compilation C++ … et selon la plateforme, cette compilation utilise soit Visual Studio, soit XCode … alors ne me parlez pas de maven-nar-plugin, parce qu’il est notoirement insuffisant pour nos besoins.
  • Du packaging sous d’innombrables formes (war, ear, zxp, …)

Qu’est-ce que ça m’a appris ?

It’s alive

Que la vraie force de maven n’est pas la création d’un format de dépendance universellement utilisé.

Je veux dire, c’est vrai que c’est pratique, ces dépendances, mais quand on arrive à du build « d’entreprise », c’est-à-dire moche et farci de profils, la vraie force de maven, ça n’est pas ça. Non.

La vraie force de maven, c’est de définir un cycle de vie universel. Parce que ce cycle de vie permet, quelquesoit le type de projet, le langage utilisé, ou quelque concept que ce soit, de positionner les différentes opérations dans un tout cohérent. Ca n’a l’air de rien, comme ça, mais c’est réellement indispensable pour les gens qui s’occupent de faire un packaging réaliste. Parce que je sais, quand j’en arrive à la phase « packaging », que tout mon code est compilé, testé, que les ressources ont été traité.

Franchement, quand je regarde les alternatives à maven, je me dis qu’ils n’ont rien compris : ne pas reprendre ce cycle de vie, c’est juste irresponsable maintenant.

Groovy, baby

Alors évidement, vous me direz qu’écrire un plugin maven pour les cas non standard, c’est l’enfer. Et c’est vrai.

Heureusement, j’ai également découvert une gradation dans la personnalisation que j’applique de façon systématique (vous savez, pour tous ces trucs foireux – comme par exemple construire un fiochier de config requirejs à partir de dépendances Javascript) :

  1. Je trouve un plugin maven qui convient ? C’est la fête
  2. Je trouve un ensemble de tâche ANT qui colle ? Cool
  3. Je peux écrire un script groovy avec gmaven ? Bien (dans ce cas-là, n’écrivez pas le script dans le build, mais mettez-le dans un dossier clairement séparé, par exemple src/build/gmaven)
  4. Aucun des trois n’est suffisant ? Alors il est temps d’écrire un plugin maven.

Avec ça, rien n’est impossible dans un build.

Et notez que je ne suis passé du point 3 au point 4 que dans deux cas. Et dans les deux cas, c’est parce que je devais en fait écrire un plugin avec cycle de vie complet. Dans tous les autres cas, groovy a été suffisant, et je dirais même fun.

Non mais tout ce XML, quand même …

Tout ce XML de maven ? A priori, on le regardait sous tes les angles dans m2e quand on ajoutait un nouveau type de build, et après, il n’y avait plus vraiment besoin d’intervenir dessus. Alors dire que c’est pénible, faut pas déconner. Le pire, en fait,a vec maven, c’est qu’Eclipse ne différencie pas les scopes des dépendances. Heureusement pour nous, Jenkins compilait en-dehors de tout IDE.

Make me a sandwich

Et encore, on n’a pas besoin de sudoer Jenkins

Ben oui, Jenkins, le gagnant de la grande guerre du fork.

Lui aussi, on l’a bien tanné.

Justement à cause de notre build natif multi-plateforme. Parce que si on compile une partie de notre application selon la plateforme, on fait comment pour avoir les artefacts dans un build commun ?

Eh bien on se fait un profil spécial Jenkins, dans lequel le build maven est découpé en parties multi-plateformes et dépendant de la palteforme, et on joue du build matrix plugin pour que tout compile et se package sans problème.

Et ça marche, mpême si c’est pas très facile.

Ca chauffe !

A vrai dire, le plus grand échec a été l’implication de l’équipe : même avec un bon radiateur, personne (sauf la testeuse en chef – que toujours elle marche sur un chemin de roses fraîchement coupées) ne s’y est jamais vraiment intéressé. Et ça, c’est triste.

Parce que ça veut dire que cette histoire d’intégration continue n’a été qu’une lubie de ma part, et que je n’ai pas réussi à en communiquer la valeur pour l’équipe, le projet, le produit.

Là-dessus, je peux dire que je retiens la leçon, et que je ferais en sorte que les builds en échec ne puissent plus être ignorés.

Tester c’est douter

Cela dit, tout n’est pas si sombre, puisque le projet a quand même une bonne partie de son code testé, et que TDD a été utilisé dans la plupart des acceptations du terme. La meilleure, et la plus efficace étant qu’aucun bug ne devait être marqué corrigé sans qu’un test ait été ajouté spécifiquement. Parce que

ce qui a foiré foirera

Ca a l’air con dit comme ça, mais ça c’est vérifié à chaque fois : les parties de code ayant connu un bug en ont toujours connu plus, et plus encore.

Heureusement qu’il n’y avait pas d’exploitation statistique des liens entre code et bugs pour vérifier ça, sinon c’aurait été l’enfer.

Et mylyn ? Et Tuleap ?

Là, par contre, l’échec est patent.

Plus encore quand je regarde ce que j’ai pu faire avec gaedo où, là, chaque bug est corrigé à travers un commentaire de commit.

Imaginez que le seul lien dont on ait pu dispoiser était, éventuellement, un lien HTTP dans un commentaire de commit référençant un bug. Comme ça, par exemple :

http://mantis/bugs/view.php?id=7137
the error was stupid : I did not add children to parents when they were needed

ca, c’est moi qui l’ait écrit aujourd’hui.

Ce qui est bizarre, c’est que j’avais parlé de mylyn, de Tuleap, et qu’un autre collègue avait parlé de svn commit hooks … mais aucune de ces tentatives n’a pris. Et pour des raisons diverses … La pire étant celle qui nous a empêché d’utiliser Tuleap : « j’ai pas vraiment eu le temps de regarder ». je dois dire que ça, ça m’a tué.

Alors, content ?

En fait, la plupart des choses installées vont continuer à fonctionner … jusqu’à ce qu’on les arrête.

En revanche, je reste convaincu de l’intérêt de ces outils, et je n’hésiterai pas à pousser leur adoption la prochaine fois.

Après deux mois de Javascript, on se sent comment ?

La question peut paraître … curieuse. Elle n’en demeure pas moins valide : quand on est comme moi un développeur Java un peu expérimenté, et qu’on passe d’un coup à ce langage aussi ubiquitaire que malaimé, qu’est-ce qui surprend ? Qu’est-ce qui étonne ? Qu’est-ce qui déçoit ?

J’ai vu des choses …

Avant d’aller plus loin, une petite mise au point. Je ne vais pas vous parler du dernier projet à la mode de lad ernière startup qui démarre. Non. J’ai fait du Javascript ces deux derniers mois pour la même raison qui m’avait forcé à faire du Flex pendant trois ans en pointillés sur le même projet : si vous voulez faire une extension pour indesign CC 2014, vous n’avez pas vraiment le choix, vous devez faire du HTML+Javascript. Alors du coup, je ne vais pas vous parler de Javascript qui communique avec son serveur via HTTP, mais de Javascript qui communique avec une extension Java/C++ via le système de communication créé par Adobe. Et, il faut le dire, Adobe fait des choix techniques … surprenants (j’y reviendrais).

En dehors de ces contraintes techniques spécifiques, l’application sur laquelle j’ai bossé n’a pas vraiment de spécificités. Enfin, pas trop 🙂

Alors, grunt, bower, npm ?

hrm hrm …

Je vais essayer d’expliquer ça de façon claire, même si ça va choquer.

Le projet pour lequel on fait cette interface est un projet multi-module de taille « moyenne » : 32 modules différents, compilant et générant tout un tas d’artefacts. Est-ce que j’allais introduire dans ce gros paquet de modules, et dans mon Jenkins, un autre outil, ou plutôt ensemble d’outils ? Non. Je voulais (peut-être de façon dogmatique) que cette interface soit packagée par maven.

Tester du JS dans maven, c’est possible ?

A la lumière de mes expériences avec Wisdom, je me suis dit que j’allais jeter un oeil à frontend-maven-plugin histoire de profiter de ses fonctionnalités (en particulier les tests). Mais, curieusement, je n’ai pas réussi à lui faire faire quoi que ce soit. Alors j’ai arrêté de faire le mariole, et j’ai dit à l’un de mes collègues de faire un système de test Javascript à base de Fluentlenium (qui est devenu pour une raison que j’ignore du Selenium pur et dur) et de JunitParams.

Autrement dit, pour chaque test Javascript, on crée un fichier HTML le chargeant via requirejs (j’y reviendrai) et on récupère grâce à Selenium le résultat du test via un peu de Javascript. Ca pique un peu les yeux la première fois, mais ça marche en fait très bien (même si tester du code asynchrone avec Jasmine est un peu compliqué).

Et les dépendances ?

Bon, évidement, comme notre projet n’utilise pas HTTP, nos dépendances Javascript (pour lesquelles Webjars rempalce très avantageusement NPM) nécessitent un peu de travail, essentiellement pour faire une translation de chemin qui les rende facilement utilisables.

Je m’explique.

Notre projet a (par exemple) ces dépendances :

		
	<dependency>
            <groupId>org.webjars</groupId>
            <artifactId>requirejs</artifactId>
            <version>2.1.14-3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>requirejs-text</artifactId>
            <version>2.0.10</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>log4javascript</artifactId>
            <version>1.4.9</version>
        </dependency>
		

Comment je fais pour utiliser log4javascript sans m’emmerder à mettre son numéro de version partout ?

Parce que, par exemple, avec Wisdom (désolé de prendre cet exemple de façon systématique, mais son utilisation m’a appris quelques trucs vraiment chouettes), je n’ai pas besoin de mettre le numéro de version grâce au WebJarController.

Eh bien c’est … assez compliqué, et ça se fait en plusieurs étapes

  1. On a un script groovy (encore merci à gmaven) qui nous permet de créer pour les dépendances Javascript des propriétés maven. Les artefacts mentionnés me donnent donc les propriétés org.webjars:requirejs:javascript:require, org.webjars:requirejs-text:javascript:text et org.webjars:log4javascript:javascript:log4javascript qui deviennent faciles à inclure dans un <script src="${org.webjars:log4javascript:javascript:log4javascript}"></script>
  2. Le même script maven crée également une grosse variable ${requirejs.dependencies.compile.paths} qui sera bien utile pour générer le shim de requirejs.
  3. Et tous nos artefacts javascript sont dézippés grâce à amven-assembly-plugin pour générer l’application.

Alors évidement, toutes ces technologies sont infiniment moins hypes que les build tools Javascript, mais elles nous permettent d’intégrer proprement des dépendances Javascripts dans notre build et ça, ça fait plaisir.

Et requirejs, c’est vraiment bien ?

Vous allez rire (ou pas), mais pour l’instant, deux choses ont été embêtantes (mais résolues)

  1. Si vous chargez tous vos scripts avec le protocole file://, vous serez limités par la longueur maximale des chemins de Windows. Et si vous avez des gros chemins, par exemple pour des classes Java dans des packages profonds pour lesquelles vous générez des stubs Javascript à l’aide de flexmojos (essentiellement parce que GraniteDS intègre un super générateur pour les entités et les services – et ne me demandez pas comment je transforme mes fichiers .as en .js), eh bien vous allez pouvoir vous amuser à écrire un algorithme de translation de chemin et un plugin amven (ou un script gmaven) pour copier les bons fichiers aux bons endroits au bon moment (parce que amven-assembly-plugin est clairement dépassé, là).
  2. Et si vous avez des dépendances en scope compile et d’autres en scope test, ça ne posera pas vraiment de problème à requirejs, mis à part bien sûr qu’il faudra distinguer le data-main de l’un et de l’autre.

Mais dans l’ensemble, require me réconcilie vraiment avec le côté « packageless » de Javascript. Je n’ai pas besoin de faire ces fameuses fonctions anonymes auto-appelées. Je déclare chacun de mes fichiers presque comme je le ferais en Java, et tout (ou presque) est explicite. Le fait de travailler dans un environnement contrôllé donne un sentiment de sécurité asssez plaisant, et des fichiers déclarés selon une syntaxe vraiment agréable.

Et Ractive, c’est pas du flan ?

Ce qui est assez marrant, c’est que plus je m’en sers, moins je comprends les mecs qui font de l’angular (oui, ça sonne comme du bashing, mais c’est plutôt de l’incompréhension).

Il nous a été assez facile d’écrire des composants pas forcément triviaux, et à chaque fois qu’on se dit que Ractive pose un problème, il s’agit en fait d’un problème entre la chaise et le clavier (une erreur ID10T comme dirait mon collègue).

Et le fait de ne pas s’embarasser d’une gestion du modèle nous a permis d’utiliser ce fameux protocole de communication Adobe sans « trop » de problèmes.

Ah bon, alors vous faites pas de JSON ?

J’ai dit protocole, je n’ai pas dit format d’échange.

En fait, on utilise du JSON, mais sans passer par la librairie JSON fournie par Adobe (et je vous conseille de faire de même parce que cette librairie est buggée).

En revanche, échanger du JSON entre du Java et du javascript pose une question sérieuse : on part d’un monde typé, dans lequel chaque objet peut avoir des méthodes associées, méthodes pour lesquelles on aimerait bien disposer d’équivalents en Javascript. Par exemple, l’un de nos objets doit s’afficher en utiliser le resourceBundle fourni, on a donc envie de mettre la méthode dans le fichier Javascript généré par Flexmojos (pas de panique, Flexmojos en génère en fait deux : un qui sera réécrit à chaque build pour contenir les champs définis par le Java, et un autre généré une seule fois, dans lequel on peut ajouter notre code).

Mais comment fait-on pour que le JSON reçu du Java s’interprète non pas sous forme de hash anonyme, mais grâce à notre classe ?

Eh bien là, c’est vraiment compliqué :

  1. On demande à Jackson d’envoyer les informations de type dans le flu JSON grâce à son système de déserialisation polymorphe (on a utilisé jsonMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT))
  2. Du coup, côté Javascript, on récupère des hashes qui associent à une classe le hash correspondant à l’objet, et là, c’est l’enfer : on a dû écrire un système qui « unmarshalle » ces hashes anonymes pour retourner des instances des classes Javascript correspondant aux aux classes Java. Et comme ça passe par require, il faut faire ce truc qui fait toujours peur : du code récursif asynchrone. Heureusement que Deferred est là, parce qu’il me semble que de tout le code Javascript, c’est la partie la plus compliquée que j’ai eu à écrire. Un enfer, je vous dit
  3. Ah, et en bonus, il ne faut pas oublier le code inverse : celui qui va prendre des objets Javascript ou des hashes anonymes pour créer de beaux obejts Java à l’autre bout du fil … Pas gagné.

Tiens, d’ailleurs, c’est le moment idéal pour vous rappeler que, comme les maps javascript n’ont pas de clés non string, ça ne peut pas être le cas non plus pour le JSON. Alors comme vous ne voulez pas réécrire tout votre code serveur pour votre nouveau client moderne, eh bien vous aller devoir vous taper la (dé)sérialisation des maps sous forme de tableaux de tableaux … Bon courage 😉

Autrement dit, comme d’habitude, la couche de communication va vous plonger dans un enfer sans nom, et surtout sans aucune raison valable.

Oui mais alors, le fait que ce soit pas compilé, qu’il n’y ait pas vraiment d’objets, c’est galère ?

Pour être honnête, oui.

Et pour être encore plus honnête, c’est pas trop grave.

Parce qu’en fait, il s’agit vraiment d’une interface HTML/Javascript : la partie complexe de l’application est bien au chaud, dans du code Java qui est correctement testé, qui a été écrit de façon à être raisonnablement robuste et qui est surtout déja utilisé en prod, donc qui a passé l’épreuve du feu.

Du coup, même si on galère parfois sur des histoires de types, ou de paramètres manquants/mal typés que Javascript ne voit pas parce que ça n’est pas son problème, ça n’est pas vraiment dramatique.

Et donc tu referais sans hésiter la même chose en plus gros ?

A dire vrai, mon Wiko a une version portable de Firefox qui fait fonctionner Ractive sans problème (comme Chrome, d’ailleurs). Alors du coup, demain, si je dois développer une application web, je peux vous assurer que le serveur se cantonnera à envoyer du JSON à mon client require/Ractive qui me fera, lui, une chouette interface.

Au passage, le bonus caché de Ractive, c’est que la philosophie est vraiment très proche de celle des web components, ce qui est loin d’être avantage mineur.

Eventuellement, j’ajouterais un coup de Facebook Flow pour valider mon code, et peut-être aussi que j’utiliserais plus frontend-maven-plugin pour minifier et toutes ces sortes de choses, mais dans l’ensemble, je comprend mieux ces histoires de microservices : si le client peut s’occuper d’aggréger et de présenter les données, pourquoi s’embêter à utiliser toute la pile JavaEE là où Wisdom suffit largement ?

Un peu de wisdom, ça fera du bien

Bon, cet après-midi, j’ai un peu de temps, donc je vais vous live-blogger une expérimentation.

L’expérimentation,, en l’occurence, est l’utilisation de wisdom-framework pour créer l’exemple canonique d’AngularJS : une liste de tâches mises à jour de façon « moderne ». L’objectif est d’arriver avant la find e la journée à une application web qui enregistre les TODOs dans un fichier au format todo.txt.

EDIT : cet objectif n’a pas été atteint, parce que j’ai perdu un peu de temps sur l’install, et surtout parce que le fameux exemple de todo avec angular ne semble pas exister … bizarre.

Ah, mais vous ne connaissez pas wisdom ? C’est normal, c’est assez jeune, mais pourtant porteur d’innombrables promesses (et j’exagère à peine en disant ça). C’est donc un framework pour applications web basé sur la modularité et le dynamisme.

La modularité, grâce à OSGi, permet de découper une application web en paquets de tout petits blocs faciles à modifier, faciles à faire évoluer à peu près indépendament, mais pourtant utilisant des ressources communes.

Le dynamisme permet, encore grâce à OSGi, mais aussi grâce à maven, de développer aussi vite qu’avec une application pure Javascript, tout en bénéficiant de l’écosystème et des capacités de typage de Java. Enfin bon, vous allez voir avec moi.

Créer le projet

Facile, c’est documenté (la doc de wisdom me paraît d’ailleurs dans l’ensemble fort bien fichue – de toute façon, il suffit d’aller sur GitHub ou dans le Google Group pour poser des questions …). Donc allons-y :

mvn org.wisdom-framework:wisdom-maven-plugin:0.5.1:create -DgroupId=fr.fot.java.wisdom -DartifactId=wisdomTodo -Dversion=0.0.1-SNAPSHOT

Et normalement, c’est bon … sauf qu’entre ma version de maven incorrecte (3.0.4), mon Java incorrect (1.6.0_40), ça ne marche pas en ligne de commande et, de la même manière, créer le projet à partir de l’archétype dans Eclipse échoue lamentablement avec un message … cryptique :

Unable to create project from archetype [org.wisdom-framework:wisdom-maven-plugin:0.5.1 -> ]
The desired archetype does not exist (org.wisdom-framework:wisdom-maven-plugin:0.5.1)

Ca fait bizarre … Bon, je crée le projet à la main et je reviens …

Et pour créer le projet à la main, histoire que vous ne perdiez pas autant de temps que moi, si vous êtes dans Eclipse, la commande d’au-dessus se traduit sous la forme

Run Configurations_2014-06-20_16-03-09

A lancer dans un « Base directory » vide pour que ça se passe bien.

Et donc, une fois que c’est fait, je tape mvn wisdom:run, je vais sur http://localhost:9000 (ne craignez rien, ces instructions sont, encore une fois, indiquées dans la doc), et j’ai droit à une très jolie page m’indiquant que faire après …

Une page fournie par le WelcomeController. Le meilleur étant évidement que, quand je renomme le package de ce contrpoleur, je vois la console wisdom s’agiter un moment, puisque l’application est rechargée dans le dos d’Eclipse (c’est bon ça Clément !).

Ajouter une route à l’application

Donc maintenant, pour ajouter une nouvelle page (oui, je vais ajouter ma page avant de remplacer la @Route par défaut dans le contrôleur), ça me paraît assez facile :

    /**
     * Ici todo est le nom du fichier du template, sans l'extension thl.html
     */
    @View("todo")
    Template todoList;

    /**
     * Et donc quand on va aller sur http://localhost:9000/list, ça va afficher le template #todoList qui n'existe pas encore ... d'où une erreur.
     * @return
     */
    @Route(method = HttpMethod.GET, uri = "/list")
    public Result todoList() {
    	return ok(render(todoList));
    }

Et quand j’essaye de taper http://localhost:9000/list dans mon navigateur j’ai droit … évidement à une belle page d’erreur, puisque le tempalte n’existe pas ! Donc je le crée avec un contenu … quelconque.

Et devinez quoi ? Eh bien évidement, ça marche !

Bon, je n’ai plus qu’à rempalcer ce contenu par celui de l’exemple d’Angular JS  … qui a malheureusement été remplacé par exemple commercial. Pas grave, on peut le trouver ailleurs (la deuxième version est bien plus simple pour un noob comme moi).

Et là c’est le drame : les attributs ng-app d’Angular viennent foutre le bazar dans le parsing XML :

Caused by: org.thymeleaf.exceptions.TemplateInputException: Exception parsing document: template="bundle://58.11:0/templates/todo.thl.html", line 13 - column 13
	at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.parseTemplateUsingPool(AbstractNonValidatingSAXTemplateParser.java:167) ~[na:na]
	at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.parseTemplate(AbstractNonValidatingSAXTemplateParser.java:117) ~[na:na]
	at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:277) ~[na:na]
	at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104) ~[na:na]
	at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1060) ~[na:na]
	at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1011) ~[na:na]
	at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:955) ~[na:na]
	at org.wisdom.template.thymeleaf.impl.WisdomTemplateEngine.process(WisdomTemplateEngine.java:85) ~[na:na]
	at org.wisdom.template.thymeleaf.impl.ThymeLeafTemplateImplementation.render(ThymeLeafTemplateImplementation.java:120) ~[na:na]
	at org.wisdom.api.templates.Template$$Proxy.render(Unknown Source) ~[na:na]
	at org.wisdom.api.DefaultController.render(DefaultController.java:188) ~[wisdom-api-0.5.1.jar:na]
	at fr.fot.java.wisdom.todo.WelcomeController.__M_todoList(WelcomeController.java:65) ~[na:na]
	at fr.fot.java.wisdom.todo.WelcomeController.todoList(WelcomeController.java) ~[na:na]
	... 33 common frames omitted
Caused by: org.xml.sax.SAXParseException: Le nom d'attribut "ng-app" associ� � un type d'�l�ment "html" doit �tre suivi du caract�re '='.
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:441) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1375) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanAttribute(XMLDocumentFragmentScannerImpl.java:1489) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1279) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2715) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:607) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:488) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:835) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1210) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:568) ~[na:1.7.0]
	at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:302) ~[na:1.7.0]
	at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.doParse(AbstractNonValidatingSAXTemplateParser.java:210) ~[na:na]
	at org.thymeleaf.templateparser.xmlsax.AbstractNonValidatingSAXTemplateParser.parseTemplateUsingPool(AbstractNonValidatingSAXTemplateParser.java:134) ~[na:na]
	... 45 common frames omitted

En même temps, je sais que c’est ça essentiellement grâce à une très bonne remontée d’erreur (c’est un avantage certain). Du coup je replonge dans la doc de wisdom pour voir comment angular et thymeleaf peuvent vivre ensemble … Ah en fait ça n’est pas dans la doc de Wisdom, mais sur la ML de Thymeleaf.

Bon, et du coup, une fois que j’ai modifié l’entête pour

<!doctype html>
<html data-ng-app="">

que j’ai fermé les différents tags mal fermés et corrigé deux ou trois boulettes, ma page s’affiche … mochement, je dois dire 🙂

Je peux donc y ajouter le contrôleur pour angular, que je copie-colle également sans m’embêter. Mais avant ça, je vais d’abord ajouter les webjars demandés (quelle chouette idée, ces dépendances javascript utilisables facilement) …

Mais ça, ce sera pour une autre fois.

Parce que les mecs d’angular semblent avoir décidé de changer leur exemple (que j’ai pourtant vu des dizaines de fois) et que, n’ayant plus de tutoriel parfaitement clair, je ne peux même plus tenter d’adapter ce que je vois à ce que wisdom me fournit (parce que figurez-vous que je ne connais malheureusement pas encore l’outil).

Bon, bref. J’y reviendrai sans doute la semaine prochaine. En tout cas, je suis plutôt séduit par la facilité avec laquelle wisdom s’installe, s’adapte, et se met à jour quand on change du code.

Comme quoi l’apprentissage n’est pas toujours un chemin facile. Surtout quand on se met soi-même des bâtons dans les roues.

maven-notifier

J’avais téléchargé il y a quelques temps le jar de maven-notifier histoire de l’installer, et puis je l’avais oublié …

Il faut dire que j’avais même eu à un moment le projet de faire un concurrent de cet outil, et puis, me rendant compte qu’il exisstait déja, j’avais choisi de tenter de l’utiliser pour éventuellement le forker et l’améliorer.

Et j’avais oublié tout ça …

Heureusement, cette semaine, j’ai pris un peu de temps pour comprendre pourquoi, alors que j’avais ce fichier de configuration, aucun message n’était envoyé :

notifier.implementation = growl

J’ai donc décidé d’écrire un test unitaire exposant mon bug (parce que j’étais convaincu qu’il y avait un bug).

Donc j’écris mon test, et je me rends compte que ce test écrit des paquets de logs qui ressemblent à ça (tiré de mon issue github)

11:30:56.251 [New I/O client worker #1-1] 67 DEBUG jgntp.message - Message received
GNTP/1.0 -ERROR NONE
Error-Code: 400
Error-Description: Invalid key hash
Origin-Machine-Name: NDX-PC-W7
Origin-Software-Name: Growl/Win
Origin-Software-Version: 2.0.9.1
Origin-Platform-Name: Microsoft Windows NT 6.1.7601 Service Pack 1
Origin-Platform-Version: 6.1.7601.65536
X-Message-Daemon: Growl/Win
X-Timestamp: 20/05/2014 11:30:56
11:30:56.261 [New I/O client worker #1-1] 40 ERROR c.g.j.m.n.growl.Slf4jGntpListener - Registration error: NOT_AUTHORIZED - Invalid key hash
11:30:56.261 [New I/O client worker #1-1] 158 INFO  c.g.c.j.internal.io.NioTcpGntpClient - Scheduling registration retry in [3-SECONDS]
11:30:59.271 [pool-2-thread-1] 68 DEBUG c.g.c.j.internal.io.NioGntpClient - Registering GNTP application [Maven]
11:30:59.271 [New I/O client worker #1-2] 51 DEBUG jgntp.message - Sending message
GNTP/1.0 REGISTER NONE SHA512:111581B770A50B07372A85ED928A8B6F3C34180782D43E1335BC3737E4FC57E70D23EBEFEFB3BF2363619E80D36707EF121AB8D9DA8C6E638F1927633B0428E6.9F99086EC1E3AD095E60EA607E321303
Application-Name: Maven
Notifications-Count: 1

Notification-Name: build-status-notification
Notification-Display-Name: Build result status
Notification-Enabled: True

Bonc e qui est chouette avec un bon log, c’est qu’on comprend tout de suite le problème, en l’occurence fort simple. Sisi, fort simple, regardez ce message :

Registration error: NOT_AUTHORIZED - Invalid key hash

C’est simple, non ? Je n’ai pas passé de mot de passe.

Parce que si je regarde Growl for Windows, dans son onglet Security, c’est assez clair (limpide, en fait) : si je veux envoyer des notifications en TCP (et c’est précisément ce que permet la librairie JGNTP qu’utilise maven-notifier), il faut donner un mot de passe à Growl et à l’application.

J’ai donc modifié mon fichier maven-notifier.properties pour qu’il soit comme ça

notifier.implementation = growl
notifier.growl.password=NON NON NON VOUS NE SAUREZ PAS CE MOT DE PASSE

J’ai ajouté le même mot de passe dans Growl, et j’ai maintenant de jolies notifications Growl à chaque fin de build !

Merci Jean-Christophe !

Avancement du lifestream

Oui, bon, bonne année.

En cette année qui commence, je dois dire que, pour la première fois depuis bien longtemps, j’ai bon espoir de disposer enfin d’une solution de lifestream qui tienne la route.

Cette idée de générer du markdown puis de le passer à un générateur de site statique (merci JBake) se révèle en effet facile à mettre en place (merci htmlunit), facile à déployer (merci les wagons de maven) et même facile à étenbdre (je me suis ajouté un plugin pour Shaarli qui marche bien).

Cela dit, tout n’est pas rose.

  • Je me suis rendu compte avec effarement que je paye encore aujourd’hui un problème d’encoding lors de la migration à l’arrache posterous => wordpress, problème visible facilement : cherchez simplement « ?? » dans mon WordPress, vous verrez un paquet de messages pour lesquels le contenu a été massacré par la migration. Moche. D’après mes calculs, il me faudra six mois pour corriger tous ces messages foireux … Hein ? Utiliser Amazon mechanical Turk pour ça ? Bande de malades.
  • JBake a quelques bugs embêtants, le pire étant pour moi l’absence de regroupement automatique par date qui fait apparaître dans l’historique chaque mois autant de fois qu’il y a d’entrées. Surprenant ….
  • Le plugin pour StackExchange, bien qu’indispensable, devra être écrit d’une façon très différente des autres : pas d’export, mais une interrogation fine de leur serveur. Ca risque d’être bien plus lent. Cela dit, je sais maintenant optimiser le chargement des données, et c’est bien.
  • Je passe un temps incroyable à peaaufiner l’export de Goodreads … J’imagine que c’est un reflet de mes préoccupations essentielles. Hlas, ça se fait semble-t-il au détriment de mon activité sur Goodreads et, surtout, sur fr.rec.arts.sf que j’ai quasiment déserté ces trois derniers mois. C’est mal.
  • Je pressens une question théorique fondamentale : dois-je exporter juste ce que j’ai écrit, ou également ce sur quoi mon écrit s’appuie. Ca fait peu de de différence pour Goodreads, mais pour WordPress et Shaarli, c’est fondamental : les pages que je relie peuvent avoir disparues; Si c’est le cas, est-ce que je dois faire appel à la web archive via leur API ? C’est tentant, je dois bien l’avouer. Et si je lie une image, une vidéo, est-ce que je dois la télécharger ?

Bon, toutes ces questions, pour gênantes qu’elles sont, ne sont cependant pas bloquantes, et certaines sont même plutôt stimulantes (la wayback machine, par exemple). Ca me donne des idées pour l’avenir, et des axes de collaboration pour faire avancer JBake.

Ah, en bonus, j’ai fait pendant ces vacances un peud e JQuery pour rendre les tags exploitables, et c’est franchement terriblement bien. Tellement, même, que je comprend complètement les idées d’un outil comme riot.js. Dans le même ordre idée, je comprend d’autant moins cet article snobissime sur Twitter Bootstrap que je l’ai facilement adapté à ma charte graphique … Du coup, la critique qui vise à dire que Bootstrap est impossible à modifier prend du plomb dans l’aile …

lifestream version … pfiouh

Le titre dit à peu près tout, mais pas tout.

J’ai donc décidé il y a quelques temps de me relancer dans la n-ième version de mon lifestream.

Tout est parti, en fait, de shaarli. Shaarli, c’est quand même la base de la réappropriation des données : on part d’un service web bien connu (delicious), et on se dit qu’il serait bien mieux à la maison. Et puis après, en discutant avec d’autres shaarlieurs, on se dit que ce serait super cool de pouvoir discuter entre nous via shaarli (comme les tweets, mais en version décentralisée). Et encore après, en fouillant un peu, je tombe sur une communauté de développeurs qui bossent précisément dans ce genre de domaines – hélas, je n’ai apparement pas conservé de lien – ou la recherche de Shaarli ne marche pas encore assez bien).

Ces développeurs expliquent, en gros, qu’il ya  plusieurs façons de faire du lifestream

  1. Tout écrire dans une interface, et faire en sorte que les messages soient postés sur les bons service
  2. Ecrire dans chaque service, et rappatrier les données via un sweetcron, par exemple

Bon, ben j’ai choisi, et c’est la deuxième solution que je prends maintenant avec mon nouveau projet lifestream.

Cette fois-ci, j’assume complètement mon côté vieux en reprenant tout en java/maven tout ce que j’avais déja fait pour Goodreads en Groovy … et je dois bien avouer que ça va vachement vite à écrire pour un monoculturel comme moi.

En bonus, je me suis offert quelques gadgets rigolos : les articles sont tous transformés en Markdown, je vais utiliser JBake pour produire le site web (et les wagons maven pour le distribuer). Bref, je m’amuse pas mal.

D’un autre côté … PluXML pourrait se révéler être l’outil idoine

Ce qui me fait sdire ça, c’est surtout le fait que la communauté semble particulièrement active et francophone.

Après avoir posté mon message hier, j’ai rapidement reçu une réponse sur Twitter me dirigeant vers cet article expliquant le format des noms de fichiers des articles. Du coup …

Et c’est vrai : avec les XSD, je pourrai balancer un coup de xjc pour générer des classes Java qui iront bien (ou faire un peu de Groovy) pour ensuite aspirer WordPress et Goodreads … Et là, ce sera le retour du fameux build maven qui copie internet dans ma machine !

Et ce sera sans PluXML

Puisque je parle de mon serveur, autant le dire, j’ai testé ce soir PluXML, histoire de voir si je pouvais facilement y ajouter des articles.

Je vais quand même vous expliquer pourquoi.

Je suis toujours à la recherche d’une solution robuste (c’est-à-dire facile à faire fonctionner, facile à analyser, facile à étendre) pour récupérer mes traces sur le web. Et, comme PluXML stocke ses données dans des fichiers XML, je m’étais dit qu’il me serait facile de créer des fichiers contenant mes données et les copier dans le bon dossier. Mais hélas il n’en est rien. En créant un fichier pouet.xml et en le copiant dans le dossier data/articles/, je pensais qu’il apparaîtrai. Mais non !

Bien sûr, j’aurais pu demander au support de PluXML un petit coup de main, et je suis sûr qu’ils auraient été fort heureux de m’aider. Mais j’ai eu la flemme (ce qui est également un droit).

Donc, maintenant, j’ai plusieurs alternatives

  • Repartir du code d’export Goodreads/Posterous que j’avais écrit il y a bien longtemps et tenter de le refaire marcher
  • Trouver des gens dont je partage les idées, et faire marcher leurs idées avec mes données. Parce que si j’y ai pensé, quelqu’un l’a fait.

Cette dernière phrase est quand même un motto sacrément important pour ne pas se prendre les pieds dans l’informatique moderne. Et du coup, en cherchant un peu, j’ai quand même trouvé quelques pages donnant des informations sur les générateurs de sites statiques en Java (ou approchants)

Bon, ben pour MiniGal, c’est pas la peine

Avant de vous enflammer, je m’explique.

Je cherche donc une galerie (PHP ou non) capable de m’afficher mes images classées selon différents critères (tous stockés dans les tags IPTC, pas de panique) à partir de mon fidèle DNS-323. MiniGal-Nano partait plutôt bien (même si j’ai dû rajouter un script PHP créant les liens symboliques selon les chemins qui m’intéressaient), jusqu’à ce que je me rende compte que la génération des miniatures sur le serveur est lente. Très lente. Trop terriblement lente pour être même utilisable. Pour être clair, il faut une dizaine de secondes par miniature ! Et ça, dans le genre trop lent, c’est trop lent.

Donc j’ai réfléchi.

Et je me suis rendu compte, d’une part, que je n’en avais pas besoin (parce que je ne m’en suis toujours pas servi), et que d’autre part, si je devais utiliser le meilleur CPU pour générer mes miniatures avant de faire un upload optimisé, j’avais plutôt intérêt (attention les yeux) à m’écrire un plugin maven qui me permettrait de générer tout ça et de l’uploader via un quelconque wagon. Mais bon, ça, c’est si je dois un jour développer un truc analogue (d’ailleurs, si je dois le faire, je m’inspirerai sans doute de certaines fonctionnalités de JoJoThumb), qui correspond au jeux de fonctionnalités qui me plaît, mais pas implémenté d’une façon qui me plaît).