<?xml version="1.0" encoding="UTF-8"?> <rss
version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:media="http://search.yahoo.com/mrss/"
> <channel><title>Blog Xebia France &#187; développement</title> <atom:link href="http://blog.xebia.fr/tag/developpement/feed/" rel="self" type="application/rss+xml" /><link>http://blog.xebia.fr</link> <description>J2EE, Agilité et SOA</description> <lastBuildDate>Wed, 08 Feb 2012 09:23:16 +0000</lastBuildDate> <language>fr</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>http://wordpress.org/?v=</generator> <copyright>CC BY-NC-ND 2.0 http://creativecommons.org/licenses/by-nc-nd/2.0/fr/</copyright> <managingEditor>blog-france@xebia.com (Xebia France)</managingEditor> <webMaster>blog-france@xebia.com (Xebia France)</webMaster> <ttl>1440</ttl> <image> <url>http://blog.xebia.fr/videos/xebia-podcast.png</url><title>Blog Xebia France</title><link>http://blog.xebia.fr</link> <width>144</width> <height>144</height> </image> <itunes:new-feed-url>http://blog.xebia.fr/feed/podcast/</itunes:new-feed-url> <itunes:subtitle>Les podcasts de Xebia France vous permettent de suivre l&#039;actualité autour de Java, de l&#039;agilité, des technologies Web et bien d&#039;autres. Xebia France est une entreprise spécialisée dans les technologies Java et JEE en environnement agi[...]</itunes:subtitle> <itunes:summary>Les podcasts de Xebia France vous permettent de suivre l&#039;actualité autour de Java, de l&#039;agilité, des technologies Web et bien d&#039;autres. Xebia France est une entreprise spécialisée dans les technologies Java et JEE en environnement agile.</itunes:summary> <itunes:keywords>Xebia, Java, JEE, SOA, Agile, Méthodes, Agiles</itunes:keywords> <itunes:category text="Technology" /> <itunes:category text="Technology"> <itunes:category text="Software How-To" /> </itunes:category> <itunes:category text="Technology"> <itunes:category text="Tech News" /> </itunes:category> <itunes:author>Xebia France</itunes:author> <itunes:owner> <itunes:name>Xebia France</itunes:name> <itunes:email>blog-france@xebia.com</itunes:email> </itunes:owner> <itunes:block>no</itunes:block> <itunes:explicit>no</itunes:explicit> <itunes:image href="http://blog.xebia.fr/videos/xebia-podcast.png" /> <item><title>Xebia à la conférence Software Craftsmanship</title><link>http://blog.xebia.fr/2011/05/20/xebia-a-la-conference-software-craftsmanship/</link> <comments>http://blog.xebia.fr/2011/05/20/xebia-a-la-conference-software-craftsmanship/#comments</comments> <pubDate>Fri, 20 May 2011 14:12:52 +0000</pubDate> <dc:creator>Jean-Laurent de Morlhon</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Conference]]></category> <category><![CDATA[Craftsmanship]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[XP]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=7783</guid> <description><![CDATA[La conférence Européene de référence sur le Software Craftsmanship se tiendra cette année à Milton Keynes, en Grande Bretagne, non loin de Londres, ce Jeudi 26 mai. Aprés un conférence 2010 réussie, réunissant les pionniers du mouvement Craftsmanship, cette année s&#8217;annonce tout aussi prometteuse. Ordinateur portable avec IDE obligatoire pour cette journée qui s&#8217;organisera autour [...]]]></description> <content:encoded><![CDATA[<p><img
title="Software craftsmanship 2011" src="http://blog.xebia.fr/wp-content/uploads/2011/05/craft.jpeg" alt="Software craftsmanship 2011" width="162" height="97" style="margin: 1em 1em 1em 1em; float: right;" /></p><p>La conférence Européene de référence sur le Software Craftsmanship se tiendra cette année à Milton Keynes, en Grande Bretagne, non loin de Londres, ce Jeudi 26 mai. Aprés un conférence 2010 réussie, réunissant les pionniers du mouvement Craftsmanship, cette année s&#8217;annonce tout aussi prometteuse.<br
/> Ordinateur portable avec IDE obligatoire pour cette journée qui s&#8217;organisera autour de<br
/> 4 tracks en paralléles, essentiellement des sessions hands-on. On va voir du code.</p><p>Voici quelques sessions issues du <a
title="programme" href="http://www.codemanship.co.uk/softwarecraftsmanship/programme.html">programme</a> :</p><ul><li>Is TDD Slower In The Short Term?</li><li>Enhancing Legacy Test Suites</li><li>How Object Oriented Are You Feeling Today?</li></ul><p>Plus de détails sur les sessions se trouvent sur le site de <a
title="jotform" href="http://www.jotform.com/table/11343938233">jotform</a></p><p>Nous serons 3 sur places et vous retrouverez sur notre blog nos retours sur cette conférence différente et vous pourrez suivre en live sur le compte Twitter de  <a
title="morlhon" href="http://twitter.com/morlhon">@morlhon</a>.</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2011/05/20/xebia-a-la-conference-software-craftsmanship/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>NoThunes, MediaPlayer et moteur de recherche</title><link>http://blog.xebia.fr/2010/10/22/nothunes-mediaplayer-et-moteur-de-recherche/</link> <comments>http://blog.xebia.fr/2010/10/22/nothunes-mediaplayer-et-moteur-de-recherche/#comments</comments> <pubDate>Fri, 22 Oct 2010 09:39:22 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Grails]]></category> <category><![CDATA[Groovy]]></category> <category><![CDATA[NoThunes]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=5687</guid> <description><![CDATA[Pour le quatrième volet de notre projet de plateforme de musique libre, NoThunes, nous allons ajouter les dernières fonctionnalités du backlog original : En tant qu&#8217;internaute, je dois pouvoir écouter un morceau En tant qu&#8217;internaute, je dois pouvoir rechercher des morceaux par mots-clés Pour cela, nous passerons par l&#8217;utilisation de 2 plugins très pratiques : [...]]]></description> <content:encoded><![CDATA[<p><img
style="margin: 1em 1em 1em 1em; float: right;" src="http://blog.xebia.fr/wp-content/uploads/2010/09/nothunes_mini_logo.png" alt="Projet NoThunes" /><br
/> Pour le quatrième volet de notre projet de plateforme de musique libre, NoThunes, nous allons ajouter les dernières fonctionnalités du <a
href="http://blog.xebia.fr/2010/09/09/nothunes-naissance-dun-projet-grails/" title="backlog original" >backlog original</a> :</p><ul><li>En tant qu&#8217;internaute, je dois pouvoir écouter un morceau</li><li>En tant qu&#8217;internaute, je dois pouvoir rechercher des morceaux par mots-clés</li></ul><p>Pour cela, nous passerons par l&#8217;utilisation de 2 plugins très pratiques :</p><ul><li><strong>Sound Manager</strong>, qui offre des tags GSP pour ajouter facilement des fonctions de media player dans les vues</li><li><strong>Searchable</strong>, qui apporte des fonctionnalités de recherche basées sur <a
href="http://www.compass-project.org/" title="Compass" >Compass</a> (et donc indirectement sur le moteur <a
href="http://lucene.apache.org/" title="Lucene" >Lucene</a>)</li></ul><p>Suivez le guide &#8230;</p><h3><a
name="Entantquinternautejedoispouvoi"></a>En tant qu&#8217;internaute, je dois pouvoir écouter un morceau</h3><p>Pour rappel, dans <a
href="http://blog.xebia.fr/tag/nothunes/" title="les pisodes prcdents" >les épisodes précédents</a>, nous avons offerts :</p><ul><li>aux utilisateurs membres : la possibilité d&#8217;uploader des morceaux de musique.</li><li>aux internautes : la possibilité de naviguer dans les morceaux disponibles en filtrant par Band et Album, et de les télécharger.</li></ul><p>Aujourd&#8217;hui nous allons leur permettre d&#8217;écouter les morceaux avant de les télécharger. Précisons tout d&#8217;abord cette User Story un peu vague. Il s&#8217;agit ici de fournir une fonctionnalité très minimaliste, permettant de jouer un morceaux directement dans l&#8217;interface de navigation, pour &laquo;&nbsp;goûter&nbsp;&raquo; avant de télécharger.</p><p>Le périmètre de notre besoin est très restreint et il se trouve qu&#8217;un plugin Grails le comble parfaitement : <strong>sound-manager</strong>. Ce plugin permet de transformer n&#8217;importe quel lien pointant vers un fichier mp3 dans la page en un mini lecteur audio flash.</p><p>Commençons donc par installer le plugin :</p><pre class="brush: java; title: ; notranslate">
grails install-plugin sound-manager
</pre><p>Si vous avez une bonne mémoire, vous vous rappelez de notre GSP <code>grails-app/views/navigation/_trackList.gsp</code> qui sert à afficher la liste des <code>Tracks</code> recherchés. Il nous suffit d&#8217;ajouter en tête de ce fichier :</p><pre class="brush: java; title: ; notranslate">
&lt;head&gt;
  &lt;sm:inlinePlayer/&gt;
&lt;/head&gt;
</pre><p>Ce tag va, à chaque chargement de la page, rechercher les liens pointant vers des fichiers mp3 et les &laquo;&nbsp;entourer&nbsp;&raquo; avec un peu de sound-manager pour obtenir un mini flash-player dédié au lien.</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/play_soundm.png" width="600" /></div><p>Comme nous avons mis une image dans le lien vers le mp3, il reste possible de télécharger le morceaux en cliquant sur l&#8217;icône plutôt que sur le bouton Play.</p><p><em>And voilà !</em> 100% de notre petit besoin est rempli, et l&#8217;opération a dû prendre 20 minutes en comptant :</p><ul><li>la recherche du plugin sur <a
href="http://www.grails.org/plugin/home" title="le portail Grails" >le portail Grails</a>.</li><li>la lecture de <a
href="http://www.grails.org/plugin/sound-manager" title="la documentation" >la documentation</a>.</li><li>l&#8217;installation.</li><li>la mise en œuvre.</li></ul><p>C&#8217;est presque allé trop vite pour en goûter toute la saveur.</p><p>Pour ceux qui veulent aller plus loin, sachez que ce plugin est basé sur <a
href="http://www.schillmania.com/projects/soundmanager2/" title="la librairie du mme nom" >la librairie du même nom</a>, et qu&#8217;il est donc également possible d&#8217;utiliser l&#8217;intégralité de l&#8217;API, qui est très complète, pour obtenir un lecteur plus riche, éventuellement en HTML5.</p><h3><a
name="Entantquinternautejedoispouvoi"></a>En tant qu&#8217;internaute, je dois pouvoir rechercher des morceaux par mots-clés</h3><p>Passons maintenant au moteur de recherche de notre navigation. Le but est ici d&#8217;ajouter un formulaire de recherche sur la page de navigation et de lancer la recherche sur les noms des <code>Bands</code>, <code>Albums</code> et <code>Tracks</code> existant en base. Là il y a un peu plus à faire. Tout d&#8217;abord, installons le plugin <a
href="http://www.grails.org/plugin/searchable" title="Searchable" >Searchable</a> :</p><pre class="brush: java; title: ; notranslate">
grails install-plugin searchable
</pre><p>Ce plugin est basé sur <a
href="http://www.compass-project.org/" title="Compass" >Compass</a>, qui est lui même basé sur <a
href="http://lucene.apache.org/" title="Apache Lucene" >Apache Lucene</a>, le moteur de recherche full-text Java Open Source le plus utilisé (notamment par Twitter depuis peu). Le première étape est de déterminer sur quelles données nous souhaitons baser nos recherches. Dans notre cas, nous avons choisi les champs <code>name</code> des classes de domaines <code>Band</code>, <code>Album</code> et <code>Track</code>.</p><p>Pour renseigner l&#8217;index du moteur Lucene, il suffit de rajouter cette ligne dans nos classes :</p><pre class="brush: java; title: ; notranslate">
static searchable = [only: ['name']]
</pre><p>Si vous souhaitez faire des mappings plus complexes, il existe de nombreuses possibilités : regardez du coté de la section Mappings de <a
href="http://www.grails.org/plugin/searchable" title="la documentation" >la documentation</a>.</p><p>Notre index est maintenant renseigné. Il nous faut une nouvelle méthode de contrôleur pour l&#8217;interroger. Pour plus de simplicité, nous la plaçons dans le <code>NavigationController</code>. Fait notable : la recherche par l&#8217;index Lucene remonte des entités incomplètes. Seuls les champs indexés sont renseignés. Par conséquent, pour l&#8217;affichage final, il faut interroger la base pour avoir tous les champs. Il faut également penser à modifier la chaîne de recherche pour la rendre compréhensible par Lucene. Nous utilisons ici une technique très basique, mais les query Lucene peuvent être autrement plus puissantes. Pour plus de détails, <a
href="http://lucene.apache.org/java/2_4_0/queryparsersyntax.html" title="voir la documentation du moteur" >voir la documentation du moteur</a>.</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/NavigationController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
  /**
    * Methode d'instrumentation pour transformer la chaine de recherche du formulaire en query Lucene-compliant
    * @param query Chaine de recherche
    */
   def instrument(String query) {
      return query+&quot;*&quot;
   }
   /**
    * Methode de recherche full-text
    */
   def search = {
      String searchQuery = instrument(params.searchQuery)
      log.debug &quot;search query is ${searchQuery}&quot;
      def result = searchableService.search(searchQuery)
      // Les *.searchEvery remontent des entités incomplètes, il faudra les récupérer en base pour avoir tous les champs
      def albumResult = Album.searchEvery(searchQuery)
      def bandResult = Band.searchEvery(searchQuery)
      def trackResult = Track.searchEvery(searchQuery)
      def trackList = [] as Set
      def bandList = [] as Set
      def albumList = [] as Set
      // filtrage des tracks pour n'afficher que ceux qui ont un fichier audio
      trackResult.each {
         def fullTrack = Track.get(it.id)
         if (fullTrack.audioPath) {
            trackList.add(fullTrack)
            albumList.add(fullTrack.album)
            bandList.add(fullTrack.album.band)
         }
      }
      // filtrage des albums pour afficher uniquement ceux qui ont au moins un track avec un fichier audio
      albumResult.each {
         def hasOneCompleteTrack = false
         def fullAlbum = Album.get(it.id)
         fullAlbum.tracks.each { aTrack -&gt;
            if (aTrack.audioPath) {
               trackList.add(aTrack)
               hasOneCompleteTrack = true
            }
         }
         if (hasOneCompleteTrack)  {
            albumList.add(fullAlbum)
            bandList.add(fullAlbum.band)
         }
      }
      bandResult.each {
         def fullBand = Band.get(it.id)
         fullBand.albums.each {anAlbum -&gt;
            def hasOneCompleteTrack = false
            anAlbum.tracks.each { aTrack -&gt;
               if (aTrack.audioPath) {
                  trackList.add(aTrack)
                  hasOneCompleteTrack = true
               }
            }
            if (hasOneCompleteTrack)  {
               bandList.add(fullBand)
               albumList.add(anAlbum)
            }
         }
      }
      // Rendu de la vue
      render (view:'list', model: [bandList: bandList, albumList: albumList, trackList: trackList, searchQuery: params.searchQuery])
   }
</pre><p>Dernière chose à ajouter : le formulaire de recherche dans la vue :</p><p><code><strong>grails-app/views/navigation/list.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;g:form action=&quot;search&quot;&gt;
   &lt;input type=&quot;text&quot; id=&quot;searchQuery&quot; name=&quot;searchQuery&quot; value=&quot;${searchQuery}&quot;/&gt;
   &lt;g:actionSubmit  value=&quot;Search&quot; /&gt;
&lt;/g:form&gt;
</pre><p>La chaine est maintenant complète et nous pouvons lancer des requêtes pour interroger notre base.</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/lucene_all.png" width="600"  /></div><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/lucene_smiley.png" width="600"/></div><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/lucene_lune.png" width="600" /></div><h3><a
name="Conclusion"></a>Conclusion</h3><p>L&#8217;ajout de ces deux fonctionnalités a été assez rapide. Si l&#8217;on possède les bons points d&#8217;entrée et que l&#8217;on prend la peine de fouiller le repository de plugins de Grails, il y a de fortes chances de trouver son bonheur rapidement. Il va de soit que les exemples donnés ici sont des usages basique. Les librairies tirées par les plugins sont plus complètes et permettent d&#8217;aller beaucoup plus loin.</p><p>Je me permet ici personnel, mais l&#8217;utilisation de Grails me remplit de joie <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> . C&#8217;est rapide, c&#8217;est concis, et si j&#8217;en veux plus, je peux. Le chemin vers le code qui fonctionne est bien tracé par les documentations.</p><p>Dans le prochain épisode, nous verrons comment tester notre code et contrôler sa qualité.</p><h3><a
name="Ressources"></a>Ressources</h3><ul><li><strong><a
href="http://github.com/aurelienmaury/nothunes/tree/v0.4">NoThunes version 0.4 sur GitHub</a></strong></li><li><strong><a
href="http://www.grails.org/plugin/sound-manager" title="Grails SoundManager Plugin" >Grails SoundManager Plugin</a></strong></li><li><strong><a
href="http://www.grails.org/plugin/searchable" title="Grails Searchable Plugin" >Grails Searchable Plugin</a></strong></li><li><strong><a
href="http://www.compass-project.org/" title="Compass SearchEngine Framework" >Compass SearchEngine Framework</a></strong></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/10/22/nothunes-mediaplayer-et-moteur-de-recherche/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>NoThunes, AJAX-ification sauce Grails</title><link>http://blog.xebia.fr/2010/10/07/nothunes-ajax-ification-sauce-grails/</link> <comments>http://blog.xebia.fr/2010/10/07/nothunes-ajax-ification-sauce-grails/#comments</comments> <pubDate>Thu, 07 Oct 2010 07:53:51 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Grails]]></category> <category><![CDATA[Groovy]]></category> <category><![CDATA[NoThunes]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=5589</guid> <description><![CDATA[Retour sur notre plateforme de musique en ligne préférée. Comme promis, dans ce chapitre, nous allons ajouter une pincée de 2.0 à notre application. Nous allons donc nous pencher sur la mise en oeuvre d&#8217;AJAX à la mode de Grails. Listes déroulantes dynamiques avec JSON Mise à jour de fragments de page avec modèles GSP [...]]]></description> <content:encoded><![CDATA[<style type="text/css">table.tablo { border-collapse:collapse; border: 1px solid #6C626C; width:95%; font: normal normal normal 1.1em/normal Arial, sans-serif; } table.tablo thead { background: #EFEFEF; border-bottom: 1px solid #6C626C; border-top: 1px solid #6C626C; color: #4F2F4F; } table.tablo thead th { padding: 5px; } table.tablo tbody tr { border: 1px solid #6C626C; } table.tablo tbody tr td { padding: 5px; }</style><p><img
style="margin: 1em 1em 1em 1em; float: right;" src="http://blog.xebia.fr/wp-content/uploads/2010/09/nothunes_mini_logo.png" alt="Projet NoThunes" /></p><p>Retour sur notre plateforme de musique en ligne préférée. Comme promis, dans ce chapitre, nous allons ajouter une pincée de 2.0 à notre application. Nous allons donc nous pencher sur la mise en oeuvre d&#8217;AJAX à la mode de Grails.</p><ul><li>Listes déroulantes dynamiques avec JSON</li><li>Mise à jour de fragments de page avec modèles GSP</li></ul><p>Vous allez pouvoir découvrir l&#8217;intégration d&#8217;AJAX dans Grails, et à quel point la vie du développeur est facilitée. Les exemples d&#8217;utilisation sont simples mais déclinables à l&#8217;infini.</p><h3><a
name="SprintBacklogdtaill"></a>Sprint Backlog détaillé</h3><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je dois pouvoir &#8230;</th><th>détail &#8230;</th></thead><tbody><tr><td>membre</td><td>Créer/modifier/supprimer des <code>Tracks</code> liés à mes albums, en uploadant un fichier mp3</td><td>CRUD standard avec upload de fichier.</td></tr><tr><tr><td>internaute</td><td>naviguer dans la hiérarchie de groupes/albums/morceaux existante</td><td>Dans une même page, on offre la sélection par groupe, album et morceaux. Si un groupe est sélectionné, on filtre les albums et les morceaux. Si un album est sélectionné, on filtre les morceaux.</td></tr><tr><tr><td>internaute</td><td>télécharger un morceau</td><td>Dans la navigation offerte aux internautes, on doit disposer d&#8217;un lien pour télécharger le fichier mp3 associé à un Track.</td></tr><tr></tbody></table></div><h3><a
name="GestiondesTracks"></a>Gestion des Tracks</h3><p>Nous sautons tout ce qui concerne la mise en place du CRUD et de la gestion de l&#8217;upload de fichier mp3. La méthode employée est foncièrement identique au <a
href="http://blog.xebia.fr/2010/09/23/nothunes-lespace-vip/">travail déjà fait</a> pour les classes <code>Band</code> et <code>Album</code>. Comme d&#8217;habitude le détail est dans <a
href="http://github.com/aurelienmaury/nothunes" title="le GitHub" >le GitHub</a>.</p><p>Attardons nous, par contre, sur un point particulier du formulaire de création de <code>Track</code> : l&#8217;attribution à un <code>Album</code>. Chaque Track appartient à un <code>Album</code> (par un lien <code>belongsTo</code>). Pour une meilleure ergonomie, nous avons choisi de proposer à l&#8217;utilisateur de sélectionner d&#8217;abord un <code>Band</code> dans un <code>&lt;select ../&gt;</code> et, dynamiquement, nous proposons les <code>Albums</code> de ce groupe dans un second <code>&lt;select ../&gt;</code>. Enfin un peu d&#8217;AJAX dans notre projet !</p><p>Pour ceux qui n&#8217;ont pas fait de veille techologique depuis plusieurs années, voici un <a
href="http://fr.wikipedia.org/wiki/Asynchronous_JavaScript_and_XML" title="petit rappel sur AJAX" >petit rappel sur AJAX</a>.</p><p>Nous commençons par outiller nos pages avec ce qu&#8217;il faut de Javascript :</p><p><code><strong>grails-app/views/layouts/main.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;html&gt;
    &lt;head&gt;
...
      &lt;g:javascript library=&quot;application&quot; /&gt;
      &lt;g:javascript library=&quot;prototype&quot; /&gt;
    &lt;/head&gt;
...
</pre><p>Ces tags ajoutent dans toutes les pages l&#8217;import de la librairie <a
href="http://www.prototypejs.org/" title="Prototype" >Prototype</a>, fournie par défaut avec Grails, et du fichier <code>web-app/js/application.js</code>. Ce dernier est destiné à recevoir le code Javascript spécifique à notre appli. Ensuite nous créons une méthode dans notre <code>BandController</code> pour renvoyer, au format <a
href="http://fr.wikipedia.org/wiki/JavaScript_Object_Notation" title="JSON" >JSON</a>, la liste des albums appartenant au groupe dont l&#8217;id est passé en paramètre :</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/BandController</strong></code></p><pre class="brush: java; title: ; notranslate">
...
// Pensez bien à faire cet import
import grails.converters.*
...
def ajaxGetAlbums = {
   render Band.get(params.id).albums as JSON
}
</pre><p>Comme on peut le voir le package <strong><code>grails.converters</code></strong> nous offre une méthode plus que simple pour la conversion d&#8217;objets en JSON. A noter que, dans la même famille il est possible d&#8217;utiliser <code>as XML</code>.</p><p>Maintenant il faut que nous lancions un appel AJAX vers cette méthode, lors de la sélection d&#8217;un groupe. Grails a prévu pour nous la méthode <code>remoteFunction</code> qui va générer le Javascript nécessaire.</p><p><code><strong>grails-app/views/tracks/create.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
...
&lt;tr class=&quot;prop&quot;&gt;
  &lt;td valign=&quot;top&quot; class=&quot;name&quot;&gt;
    &lt;label for=&quot;band.id&quot;&gt;
      &lt;g:message code=&quot;track.album.band.label&quot; default=&quot;Band&quot; /&gt;
    &lt;/label&gt;
  &lt;/td&gt;
  &lt;td valign=&quot;top&quot; class=&quot;value&quot;&gt;
    &lt;g:select name=&quot;band&quot;
              from=&quot;${bandList}&quot;
              value=&quot;${band?.id}&quot;
              noSelection=&quot;['':'---Choose a band---']&quot;
              optionValue=&quot;name&quot;
              optionKey=&quot;id&quot;
              onchange=&quot;${remoteFunction(
                  controller:'band',
                  action:'ajaxGetAlbums',
                  params:''id=' + escape(this.value)',
                  onComplete:'updateSelectAlbumWithJSON(e,'albums')')}&quot;
    /&gt;
  &lt;/td&gt;
&lt;/tr&gt;
...
</pre><p>Les paramètres de <code>remoteFunction</code> sont assez explicites :</p><ul><li><strong>controller, action :</strong> le contrôleur et l&#8217;action ciblée</li><li><strong>params :</strong> les paramètres de la requête</li><li><strong>onComplete :</strong> la méthode javascript callback de l&#8217;appel AJAX</li></ul><p>Il ne reste plus qu&#8217;à rédiger la méthode Javascript pour mettre à jour le <code>select</code> des <code>Albums</code> :</p><p><code><strong>web-app/js/application.js</strong></code></p><pre class="brush: java; title: ; notranslate">
function updateSelectAlbumWithJSON(e, albumListId) {
   var rselect = $(albumListId)
   // Vidage du select
   var l = rselect.length
   while (l &gt; 0) {
      l--
      rselect.remove(l)
   }
   // Evaluation du JSON retourné par le serveur
   var albums = eval(&quot;(&quot; + e.responseText + &quot;)&quot;)
   if (albums) {
   // Remplissage du select avec les nouvelles options
      addOption(rselect, '---Choose an album---', '')
      for (var i=0; i &lt; albums.length; i++) {
         addOption (rselect, albums[i].name, albums[i].id)
      }
   }
}
function addOption(selectId, text, value) {
   var opt = new Element('option');
   opt.text = text
   opt.value = value
   try {
      selectId.add(opt,null) // Standard, ne fonctionne pas avec IE
   }
   catch(ex) {
      selectId.add(opt) // seulement pour IE
   }
}
</pre><p>En mode développement, c&#8217;est certainement superflu, mais au cas où le listing des albums deviendrait un peu long, il est possible d&#8217;ajouter un gif d&#8217;attente. Cette image est gracieusement fournie par Grails. Ça n&#8217;a l&#8217;air de rien, d&#8217;ailleurs ce n&#8217;est rien, mais ça fait plaisir quand même ! <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /><br
/> Juste à coté du tag &lt;g:select/&gt; des Albums on peut ajouter un petit :</p><p><code><strong>grails-app/views/tracks/create.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;div id=&quot;waitingGif&quot;&gt;&lt;img src=&quot;${resource(dir:'images',file:'spinner.gif')}&quot; alt=&quot;${message(code:'spinner.alt',default:'Loading...')}&quot; /&gt;&lt;/div&gt;
</pre><p>Et dans le javascript, ajouter du <code>$('waitingGif').show()</code> et <code>$('waitingGif').hide()</code>. Ca fait toujours plus joli après tout.</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/create_track.png" border="0" alt="create_track" title="create_track" width="600" /></div><p>Ça y est, pas besoin de plus ! Pour la page d&#8217;édition d&#8217;une instance existante, la méthode est pratiquement la même. il faut juste ajouter dans le <code>trackcontroller</code> le chargement de la liste des <code>albums</code> pour que les 2 <code>select</code> soient remplis au chargement. les gentils membres ont maintenant toutes les fonctionnalités pour alimenter notre plateforme de musique en contenu dûment étiquetté.</p><h3><a
name="Navigationpourlesinternautes"></a>Navigation pour les internautes</h3><p>Il faut maintenant penser aux internautes moyens, avides de pouvoir accéder au contenu des membres. Nous allons donc attaquer la navigation publique. Pour éviter de surcharger les contrôleurs existant, nous créons un <code>NavigationController</code>, qui sera dédié à la page de navigation destinée aux internautes lambdas, ainsi qu&#8217;une page GSP associée à l&#8217;action <code>list</code> (donc dans le fichier <code>grails-app/views/navigation/list.gsp</code>).</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/navig_all.png"  width="600" border="0" /></div><p>La sélection d&#8217;un <code>Band</code> doit déclencher le filtrage de la liste des <code>Albums</code> et des <code>Tracks</code>, et la sélection d&#8217;un <code>Album</code> doit déclencher le filtrage de la liste des <code>Tracks</code> uniquement. Le filtrage des <code>Albums</code> est identique au cas précédent, puisque c&#8217;est un <code>select</code>. Le tableau des <code>Tracks</code>, un peu plus riche, mérite qu&#8217;on s&#8217;y attarde un peu.</p><p>Le principe est globalement le même, à ceci près qu&#8217;on va utiliser un modèle GSP pour créer le fragment HTML qui servira à remplacer dynamiquement la liste des <code>Tracks</code>. Zoomons un peu sur la mécanique de notre page <code>list.gsp</code> :</p><pre class="brush: java; title: ; notranslate">
&lt;html&gt;
   &lt;head&gt;
      &lt;!-- ... --&gt;
      &lt;g:javascript&gt;
         // Fonction de filtrage du select des Albums et de la liste des Track, à partir d'un id de Band
         function filterByBand() {
            var bandId = $F('bandSelect');
            &lt;g:remoteFunction action=&quot;ajaxFilterTrackByBand&quot; params=&quot;'id=' + bandId&quot; onComplete=&quot;updateTrackList(e)&quot;/&gt;
            &lt;g:remoteFunction action=&quot;ajaxFilterAlbumByBand&quot; params=&quot;'id=' + bandId&quot; onComplete=&quot;updateAlbums(e)&quot;/&gt;
         }
         // Fonction de filtrage de la liste des Track, à partir d'un id d'Album
         function filterByAlbum() {
            var albumId = $F('albumSelect');
            &lt;g:remoteFunction action=&quot;ajaxFilterTrackByAlbum&quot; params=&quot;'id=' + albumId&quot; onComplete=&quot;updateTrackList(e)&quot;/&gt;
         }
         // Fonction de mise à jour de la liste des Track avec la réponse (au format HTML) de la requête AJAX.
         function updateTrackList(e) {
            $('navTrack').innerHTML = e.responseText;
         }
         // Fonction de mise à jour du select des Albums avec la réponse (au format JSON) de la requête AJAX.
         function updateAlbums(e) {
            // Le code est identique à l'exemple donné pour la
            // mise à jour de select du paragraphe précédent
         }
      &lt;/g:javascript&gt;
   &lt;/head&gt;
   &lt;body&gt;
      &lt;div class=&quot;body&quot;&gt;
         &lt;h1&gt;Navigation&lt;/h1&gt;
         &lt;div class=&quot;navigation&quot;&gt;
            &lt;!-- ... --&gt;
            &lt;!-- Selection de Band, le onchange déclenche le javascript de filtrage --&gt;
            &lt;g:select id=&quot;bandSelect&quot; name=&quot;band&quot; class=&quot;navigationSelect&quot;
                  from=&quot;${bandList}&quot;
                  noSelection=&quot;['':'All']&quot; optionValue=&quot;name&quot;
                  optionKey=&quot;id&quot; size=&quot;30&quot; onchange=&quot;filterByBand()&quot;/&gt;
            &lt;!-- ... --&gt;
            &lt;!-- Selection d'Album, le onchange déclenche le javascript de filtrage --&gt;
            &lt;g:select id=&quot;albumSelect&quot; name=&quot;album&quot; class=&quot;navigationSelect&quot;
                  from=&quot;${albumList}&quot;
                  noSelection=&quot;['':'All']&quot; optionValue=&quot;name&quot;
                  optionKey=&quot;id&quot; size=&quot;30&quot; onchange=&quot;filterByAlbum()&quot;/&gt;
            &lt;!-- ... --&gt;
            &lt;!-- Selection de Track, encapsulé dans une div pour pouvoir mettre a jour facilement son contenu --&gt;
            &lt;div id=&quot;navTrack&quot; class=&quot;list&quot;&gt;
               &lt;!-- on charge le fragment contenant la liste des Track, pour qu'elle apparaissent dès le premier chargement --&gt;
               &lt;g:render template=&quot;/navigation/trackList&quot;/&gt;
            &lt;/div&gt;
         &lt;/div&gt;
      &lt;/div&gt;
   &lt;/body&gt;
&lt;/html&gt;
</pre><p>Dans le tag <code>&lt;g:render .../&gt;</code> en fin de l&#8217;exemple, on voit qu&#8217;on fait appel au modèle GSP qui contient la liste formatée des <code>Tracks</code>. Passons à sa rédaction. Il faut penser à nommer le fragment qui servira de modèle en commençant par un caractère &#8216;_&#8217;, c&#8217;est la convention. Notez que dans la colonne <strong>Download</strong> on réutilise le <code>DlController</code> créé à l&#8217;épisode 2.</p><p><code><strong>grails-app/views/navigation/_trackList.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;table&gt;
   &lt;thead class=&quot;fixedHeader&quot;&gt;
      &lt;tr&gt;
         &lt;th&gt;${message(code: 'track.name.label', default: 'Name')}  &lt;/th&gt;
         &lt;th&gt;${message(code: 'band.name.label', default: 'Band')}   &lt;/th&gt;
         &lt;th&gt;${message(code: 'album.name.label', default: 'Album')} &lt;/th&gt;
         &lt;th style=&quot;width: 50px; text-align: center;&quot;&gt;Download      &lt;/th&gt;
      &lt;/tr&gt;
   &lt;/thead&gt;
   &lt;tbody class=&quot;navigationTable&quot;&gt;
      &lt;g:each in=&quot;${trackList}&quot; status=&quot;i&quot; var=&quot;trackInstance&quot;&gt;
         &lt;tr class=&quot;${(i % 2) == 0 ? 'odd' : 'even'}&quot;&gt;
            &lt;td&gt;
            ${fieldValue(bean: trackInstance, field: &quot;name&quot;)}
            &lt;/td&gt;
            &lt;td&gt;&lt;g:link url=&quot;${trackInstance.album.band.webSite}&quot;&gt;
               ${fieldValue(bean: trackInstance, field: &quot;album.band.name&quot;)}
            &lt;/g:link&gt;&lt;/td&gt;
            &lt;td&gt;
            ${fieldValue(bean: trackInstance, field: &quot;album.name&quot;)}
            &lt;/td&gt;
            &lt;td style=&quot;width: 50px; text-align: center;&quot;&gt;&lt;a
               href=&quot;${createLink(controller:'dl', action:'audio', params: [id: trackInstance.audioPath])}&quot;&gt;
            &lt;img src=&quot;${resource(dir:'images',file:'filesave.png')}&quot;
               border=&quot;0&quot; /&gt; &lt;/a&gt;&lt;/td&gt;
         &lt;/tr&gt;
      &lt;/g:each&gt;
   &lt;/tbody&gt;
&lt;/table&gt;
</pre><p>Enfin, dans notre méthode du contrôleur on pourra faire usage de <code>render</code> comme ceci :</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/NavigationController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
   def ajaxFilterTrackByAlbum = {
      def trackList = Track.withCriteria {
         ne('audioPath', 'empty')
         if (params.id) {
            album {
               eq('id', new Long(params.id))
               if (params.band) {
                  band {
                     eq('id', new Long(params.band))
                  }
               }
            }
         }
      }
      render (view:'_trackList', model: [trackList: trackList])
   }
...
</pre><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/navig_band.png" border="0" width="600" /></div><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/10/navig_album.png" border="0" width="600"/></div><p>Ça y est nos internautes peuvent naviguer dans les morceaux uploadés par nos gentils membres, et les télécharger à leur guise.</p><h3><a
name="SprintReview"></a>Sprint Review</h3><p>Nous avons pu voir dans cet épisode que la mise en place de méthodes AJAX est relativement intuitive et rapide. Les 2 cas décrits ici sont déclinables à l&#8217;infini pour répondre à beaucoup de besoins. L&#8217;API Grails est correctement documentée, et la librairie Prototype embarquée par défaut offre des outils plus que suffisant pour manipuler le DOM de la page. Pour ceux qui préférent, il existe les plugins <a
href="http://www.grails.org/plugin/jquery" title="JQuery" >JQuery</a> et <a
href="http://www.grails.org/plugin/jquery-ui" title="JQueryUI" >JQuery-UI</a> pour installer en une commande tous les fichiers au bon endroit.</p><p>Le programme du prochain épisode :</p><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je pourrai &#8230;</th></thead><tbody><tr><td>internaute</td><td>écouter un morceau</td></tr><tr><td>internaute</td><td>rechercher des morceaux par mots-clés</td></tr></tbody></table></div><p>Stay (no)tuned &#8230;</p><h3><a
name="Ressources"></a>Ressources</h3><ul><li><a
href="http://github.com/aurelienmaury/nothunes/tree/v0.3">Tag v0.3 du projet NoThunes sur GitHub</a></li><li><a
href="http://www.grails.org/Ajax" title="Grails  AJAX" >Grails : AJAX</a></li><li><a
href="http://www.grails.org/AJAX-Driven+SELECTs+in+GSP" title="Grails  AJAX driven select" >Grails : AJAX driven select</a></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/10/07/nothunes-ajax-ification-sauce-grails/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>NoThunes, l&#8217;espace VIP</title><link>http://blog.xebia.fr/2010/09/23/nothunes-lespace-vip/</link> <comments>http://blog.xebia.fr/2010/09/23/nothunes-lespace-vip/#comments</comments> <pubDate>Thu, 23 Sep 2010 13:52:40 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Grails]]></category> <category><![CDATA[Groovy]]></category> <category><![CDATA[NoThunes]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=5430</guid> <description><![CDATA[Continuons le montage de notre projet OpenSource propulsé par Grails. Au dernier épisode, nous avions démarré le projet, façonné les styles CSS, mis en place la sécurité, mais aussi et surtout, rédigé les classes du modèle de données. Aujourd&#8217;hui nous allons leur donner vie en ajoutant des fonctionnalités aux utilisateurs membres. Nous nous attarderons sur [...]]]></description> <content:encoded><![CDATA[<style type="text/css">table.tablo { border-collapse:collapse; border: 1px solid #6C626C; width:95%; font: normal normal normal 1.1em/normal Arial, sans-serif; } table.tablo thead { background: #EFEFEF; border-bottom: 1px solid #6C626C; border-top: 1px solid #6C626C; color: #4F2F4F; } table.tablo thead th { padding: 5px; } table.tablo tbody tr { border: 1px solid #6C626C; } table.tablo tbody tr td { padding: 5px; }</style><p><img
style="margin: 1em 1em 1em 1em; float: right;" src="http://blog.xebia.fr/wp-content/uploads/2010/09/nothunes_mini_logo.png" alt="Projet NoThunes" /></p><p>Continuons le montage de notre projet OpenSource propulsé par Grails. Au <a
href="http://blog.xebia.fr/2010/09/09/nothunes-naissance-dun-projet-grails/">dernier épisode</a>, nous avions démarré le projet, façonné les styles CSS, mis en place la sécurité, mais aussi et surtout, rédigé les classes du modèle de données. Aujourd&#8217;hui nous allons leur donner vie en ajoutant des fonctionnalités aux utilisateurs membres.</p><p>Nous nous attarderons sur :</p><ul><li>la gestion du profil utilisateur</li><li>la génération des écrans CRUD de nos données</li><li>leur adaptation à nos &#8216;règles métier&#8217;</li><li>la gestion des upload et download</li></ul><p>Ces étapes peuvent paraître triviales à ceux qui ont déjà une expérience de Grails. Cependant, j&#8217;ai choisi de les traiter malgré tout. J&#8217;ai moi-même perdu trop de temps à chercher des exemples concrets et simples sur la toile pour oser faire l&#8217;impasse dessus. Donc, toi qui débute sur Grails, sois le bienvenu.</p><h3><a
name="Sprintbacklogdtaill"></a>Sprint backlog détaillé</h3><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je dois pouvoir &#8230;</th><th>détail &#8230;</th></thead><tbody><tr><td>membre</td><td>voir et mettre à jour mon profil utilisateur</td><td>Un membre authentifié doit pouvoir modifier son profil, sauf son login et ses rôles.</td></tr><tr><td>membre</td><td>créer/modifier/supprimer des groupes de musiques</td><td>Un membre authentifié doit pouvoir créer des groupes de musique et lister/modifier/supprimer les groupes qu&#8217;il a créés.</td></tr><tr><td>membre</td><td>créer/modifier/supprimer des albums liés à mes groupes</td><td>Si un membre a créé un groupe de musique, il peut créer un album pour ce groupe.</td></tr></tbody></table></div><h3><a
name="Gestionduprofilutilisateur"></a>Gestion du profil utilisateur</h3><h4><a
name="Confirmationdesmodificationsde"></a>Confirmation des modifications de mot de passe</h4><p>Lorsque nos membres pourront mettre à jour leur profil, ils pourront également modifier leur mot de passe. Pour commencer, il faut donc  que nous fassions des adaptations pour ajouter la confirmation du mot de passe dans la gestion des comptes utilisateurs. En deux coups de cuillère à pot, cela donne :</p><ul><li>Ajout d&#8217;un champ <code>confirmPasswd</code> dans la classe de domaine User, et d&#8217;un validateur associé :</li></ul><p><code><strong>grails-app/domain/fr/xebia/nothunes/security/User.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
class User {
   ...
   static transients = ['confirmPasswd']
   ...
   String confirmPasswd
   static constraints = {
      ...
      confirmPasswd(validator :{val, obj -&gt;
         if (obj.properties['passwd'] != val) {
            return 'default.invalid.confirmPasswd.message'
         }
      })
   }
}
</pre><p>Le champ nouvellement créé est passé en <code>transient</code> pour éviter que sa valeur ne soit persistée en base. Notez que la valeur retournée par le validateur est un <code>String</code> correspondant au message d&#8217;erreur à afficher. Par conséquent, il faut ajouter ce message dans les fichiers <code>messages.properties</code> pour avoir l&#8217;affichage de l&#8217;erreur dans les GSP :</p><p><code><strong>grails-app/i18n/messages.properties</strong></code></p><pre class="brush: java; title: ; notranslate">
default.invalid.confirmPasswd.message=Bad password confirmation
</pre><ul><li>Modification du <code>UserController</code> :</li></ul><p>Pour que le champ <code>confirmPasswd</code> soit correctement renseigné avant la validation, il faut aller modifier la classe <code>UserController</code>. Dans les méthodes <code>save()</code> et <code>update()</code>, le champ <code>passwd</code> est chiffré avant sauvegarde en base. Il faut appliquer le même traitement au champ <code>confirmPasswd</code>. De cette façon le validateur fait la comparaison entre les deux champs chiffrés. Si les versions chiffrées sont identiques alors la confirmation du mot de passe est valide, CQFD.</p><p><code><strong>grails-app/controllers/UserController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
// à chaque occurence du chiffrage du champ 'passwd' ...
person.passwd = authenticateService.encodePassword(params.passwd)
// on ajoute le chiffrage du champ 'confirmPasswd'
person.confirmPasswd = authenticateService.encodePassword(params.confirmPasswd)
...
</pre><ul><li>Modification des formulaires de création et d&#8217;édition d&#8217;un User :</li></ul><p>Pour l&#8217;instant les formulaires de création et d&#8217;édition des User ne contiennent pas de champ pour <code>confirmPasswd</code> donc le validateur renvoie toujours une erreur. Il suffit d&#8217;ajouter un champ texte, initialisé avec la valeur du mot de passe.</p><p><code><strong>grails-app/views/user/create.gsp</strong></code> ET <code><strong>grails-app/views/user/edit.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
...
&lt;tr class=&quot;prop&quot;&gt;
   &lt;td valign=&quot;top&quot; class=&quot;name&quot;&gt;&lt;label for=&quot;passwd&quot;&gt;Password:&lt;/label&gt;&lt;/td&gt;
   &lt;td valign=&quot;top&quot; class=&quot;value ${hasErrors(bean:person,field:'passwd','errors')}&quot;&gt;
      &lt;input type=&quot;password&quot; id=&quot;passwd&quot; name=&quot;passwd&quot; value=&quot;${person.passwd?.encodeAsHTML()}&quot;/&gt;
   &lt;/td&gt;
&lt;/tr&gt;
&lt;!-- on ajoute notre champ --&gt;
&lt;tr class=&quot;prop&quot;&gt;
   &lt;td valign=&quot;top&quot; class=&quot;name&quot;&gt;&lt;label for=&quot;confirmPasswd&quot;&gt;Confirm password:&lt;/label&gt;&lt;/td&gt;
   &lt;td valign=&quot;top&quot; class=&quot;value ${hasErrors(bean:person,field:'confirmPasswd','errors')}&quot;&gt;
      &lt;input type=&quot;password&quot; id=&quot;confirmPasswd&quot; name=&quot;confirmPasswd&quot; value=&quot;${person.passwd?.encodeAsHTML()}&quot;/&gt;
   &lt;/td&gt;
&lt;/tr&gt;
...
</pre><h4><a
name="Crationduncontrleurddilafficha"></a>Création d&#8217;un contrôleur dédié à l&#8217;affichage et la mise à jour du profil</h4><p>Maintenant que nous disposons d&#8217;une validation correcte pour la mise à jour des mots de passe, nous pouvons attaquer le cœur du sujet. Pour cela, on crée un nouveau contrôleur grâce à la commande :</p><pre class="brush: java; title: ; notranslate">
grails create-controller fr.xebia.nothunes.profile.Profile
</pre><p>Cela génère une coquille vide nommée <strong><code>ProfileController</code></strong>. Le but de ce contrôleur est de permettre d&#8217;afficher le profil de l&#8217;utilisateur courant et de le mettre à jour. Par conséquent il faut créer les méthodes :</p><ul><li><code>index</code> : qu&#8217;on redirigera vers <code>show</code>. Dans le cas où le contrôleur est appelé sans méthode particulière, c&#8217;est <code>index()</code> qui sera lancé.</li><li><code>show</code> : pour récupérer le compte de l&#8217;utilisateur courant en base et l&#8217;afficher.</li><li><code>edit</code> : pour récupérer le compte de l&#8217;utilisateur courant en base et afficher le formulaire d&#8217;édition.</li><li><code>update</code> : pour persister en base un envoi de formulaire d&#8217;édition. Si la sauvegarde se passe bien, à la fin on redirige vers <code>show()</code> avec un message de succès ; sinon on redirige vers <code>edit()</code> avec un message d&#8217;erreur.</li></ul><p>Conjointement, il nous faut 2 vues :</p><ul><li><code>profile/show.gsp</code> : pour afficher le profil. Elle contient un bouton pour passer en édition.</li><li><code>profile/edit.gsp</code> : pour éditer le profil. Elle contient un formulaire qui pointe sur la méthode <code>update</code> du contrôleur.</li></ul><p>Afin de ne pas trop alourdir ce billet, je vous renvoie au GitHub pour le détail des vues et du contrôleur. Le code est <em>très</em> fortement inspiré de ce qui existe déjà dans le <code>UserController</code> et ses vues.</p><ul><li><a
href="http://github.com/aurelienmaury/nothunes/blob/v0.2/grails-app/controllers/fr/xebia/nothunes/profile/ProfileController.groovy" title="grailsappcontrollersfrxebianothunesprofileProfileControllergroovy" ><code>grails-app/controllers/fr/xebia/nothunes/profile/ProfileController.groovy</code></a></li><li><a
href="http://github.com/aurelienmaury/nothunes/blob/v0.2/grails-app/views/profile/show.gsp" title="grailsappviewsprofileshowgsp" ><code>grails-app/views/profile/show.gsp</code></a></li><li><a
href="http://github.com/aurelienmaury/nothunes/blob/v0.2/grails-app/views/profile/edit.gsp" title="grailsappviewsprofileeditgsp" ><code>grails-app/views/profile/edit.gsp</code></a></li></ul><p>Pour être sûr que l&#8217;utilisateur est authentifié quand il accède au contrôleur, on ajoute dans les règles de sécurité la protection de <code>/profile/*</code> directement dans le bootstrap de l&#8217;application :</p><p><code><strong>grails-app/conf/BootStrap.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
class BootStrap {
   def init = { servletContext -&gt;
      ...
      def protectUserProfileManaging = new RequestMap(url: '/profile/*', configAttribute: 'ROLE_ADMIN,ROLE_USER').save()
   }
   ...
}
</pre><p>Une fois tout ceci fait, il suffit de rajouter le lien vers l&#8217;affichage du profil dans la GSP qui contient le menu pour les membres :</p><p><code><strong>grails-app/views/menu/_user.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
...
   &lt;h1&gt;Member manages ...&lt;/h1&gt;
   &lt;ul&gt;
      &lt;li&gt;&lt;g:link controller=&quot;profile&quot;&gt;your profile&lt;/g:link&gt;&lt;/li&gt;
   &lt;/ul&gt;
...
</pre><p>On redémarre notre application et le tour est joué !</p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/09/edit_profil.png" alt="edit_profil" borer="0" title="edit_profil" width="600px" /></div><h3><a
name="GestiondesCRUDmtierslesclasses"></a>Gestion des CRUD métiers : les classes Band et Album</h3><p>Attaquons maintenant la gestion des CRUD de nos objets métiers. Premier de la liste : Band. Pour rappel, cette classe représente un groupe de musique géré par un utilisateur membre. Un membre doit pouvoir créer des groupes et modifier uniquement ceux qu&#8217;il a créé. Pour avoir un petit rappel sur le contenu de la classe Band, <a
href="http://github.com/aurelienmaury/nothunes/blob/master/grails-app/domain/fr/xebia/nothunes/domain/Band.groovy" title="voir sur le GitHub" >voir sur le GitHub</a>. Grails nous offre un démarrage rapide en générant les vues et le contrôleur à partir de la classe de domaine avec la commande :</p><pre class="brush: java; title: ; notranslate">
grails generate-all fr.xebia.nothunes.domain.Band
</pre><p>Le contrôleur et les vues générés sont génériques et doivent être adaptés pour nos besoins particuliers. Ces fonctionnalités doivent être réservées aux membres, donc on ajoute dans le bootstrap les règles de sécurité pour protéger le contrôleur :</p><p><code><strong>grails-app/conf/BootStrap.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
class BootStrap {
   def init = { servletContext -&gt;
      ...
      def protectUserProfileManaging = new RequestMap(url: '/band/*', configAttribute: 'ROLE_ADMIN,ROLE_USER').save()
   }
   ...
}
</pre><p>Ensuite il faut rajouter des contrôles métier pour permettre aux membres de ne lister, visualiser et modifier que les <code>Band</code> qu&#8217;ils ont créés. Pour cela, on va s&#8217;appuyer sur l&#8217;appartenance de la classe <code>Band</code> à un User (lien <code>belongsTo</code>). Pour commencer, la méthode <code>list</code> ne doit afficher que les groupes dont l&#8217;utilisateur est propriétaire :</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/BandController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
def list = {
   params.max = Math.min(params.max ? params.int('max') : 10, 100)
   def owner = User.get(authenticateService.userDomain().id)
   [bandInstanceList: Band.findAllByOwner(owner, params), bandInstanceTotal: Band.count()]
}
...
</pre><p>Ensuite, lors d&#8217;une sauvegarde, on doit définir le propriétaire du Band avant la sauvegarde</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/BandController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
def save = {
   def bandInstance = new Band(params)
   // on définit le propriétaire du Band
   bandInstance.owner = User.get(authenticateService.userDomain().id)
   ...
}
...
</pre><p>Enfin, à l&#8217;entrée des méthodes <code>show</code>, <code>edit</code> et <code>update</code> du <code>BandController</code> on teste la légitimité de l&#8217;utilisateur courant à agir sur le <code>Band</code> demandé de la façon suivante :</p><p><code><strong>grails-app/controllers/fr/xebia/nothunes/domain/BandController.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
def show = {
   def bandInstance = Band.get(params.id)
   // on recupere le user courant
   def currentUser = User.get(authenticateService.userDomain().id)
   // s'il existe et qu'il est propriétaire du Band demandé, on autorise la suite
   if (bandInstance &amp;&amp; currentUser &amp;&amp; bandInstance.owner == currentUser) {
      [bandInstance: bandInstance]
   } else {
   // sinon on affiche un message et on le renvoie sur la liste de ses propres groupes
      flash.message = &quot;${message(code: 'default.not.found.message', args: [message(code: 'band.label', default: 'Band'), params.id])}&quot;
      redirect(action: &quot;list&quot;)
   }
}
...
</pre><p>Et, touche finale, on ajoute le lien vers la gestion des <code>Band</code> dans le menu utilisateur :</p><p><code><strong>grails-app/views/menu/_user.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
...
   &lt;h1&gt;Member manages ...&lt;/h1&gt;
   &lt;ul&gt;
      &lt;li&gt;&lt;g:link controller=&quot;profile&quot;&gt;your profile&lt;/g:link&gt;&lt;/li&gt;
      &lt;li&gt;&lt;g:link controller=&quot;band&quot;&gt;your bands&lt;/g:link&gt;&lt;/li&gt;
   &lt;/ul&gt;
...
</pre><p>La technique est la même pour les Albums :</p><ul><li>génération des vues et du contrôleur</li><li>protection par les règles de sécurité</li><li>ajout de contrôles métier pour filtrer les actions utilisateurs</li></ul><p>Pour le détail du code tournant autour des Albums, faites un tour par le <a
href="http://github.com/aurelienmaury/nothunes/tree/v0.2" title="GitHub" >GitHub</a>.</p><h3><a
name="UploadduneimagelogopourlesBand"></a>Upload d&#8217;une image logo pour les <code>Band</code></h3><p>Pour rendre notre gestion de Band un peu plus sexy, on y ajoute l&#8217;upload d&#8217;une image qui servira de logo. Rien de plus facile. Nous commençons par ajouter dans la configuration de l&#8217;application un chemin vers un répertoire de stockage :</p><p><code><strong>grails-app/conf/Config.groovy</strong></code></p><pre class="brush: java; title: ; notranslate">
...
storage.image.directory='/tmp/nothunes_images/'
...
</pre><p>Pour pouvoir réutiliser la méthode d&#8217;upload de fichier, on crée un service grails dédié :</p><pre class="brush: java; title: ; notranslate">
grails create-service fr.xebia.nothunes.upload.Upload
</pre><p>Puis on crée une méthode <code>saveImage</code>, qui s&#8217;occupe de vérifier le type mime du fichier soumis, et de le stocker dans le répertoire défini dans la <code>Config</code> de l&#8217;application.</p><pre class="brush: java; title: ; notranslate">
class UploadService {
   def authorizedImageContentType = [ 'image/jpeg' :'jpg', 'image/gif' :'gif', 'image/png' :'png']
   def grailsApplication
   def saveImageFile(anImageFile, name) {
      if (!anImageFile.empty) {
         FileNameMap fileNameMap = URLConnection.getFileNameMap();
         def contentType = fileNameMap.getContentTypeFor(anImageFile.originalFilename)
         if ( authorizedImageContentType.keySet().contains(contentType) ) {
            File storageDir = new File(grailsApplication.config.storage.image.directory)
            if (!storageDir.exists()) {
               if (!storageDir.mkdir()) {
                  log.error 'Directory does not exist and cannot be created '+grailsApplication.config.storage.image.directory
                  return false
               }
            }
            // Pour un minimum de standardisation, on génère le nom du fichier à enregistré à partir du paramètre
            // 'name' et du type mime
            def targetFilename = name + '_logo.' + authorizedImageContentType[contentType]
            // sauvegarde du fichier
            anImageFile.transferTo( new File(grailsApplication.config.storage.image.directory + targetFilename) )
            return targetFilename
         } else {
            log.debug 'Someone tried to upload a non-image file : '+contentType
            return false
         }
      } else {
         return false
      }
   }
}
</pre><p>Ensuite on modifie nos GSP de création et d&#8217;édition pour ajouter un champ de type <code>file</code> et faire des formulaires multipart :</p><p><code><strong>grails-app/views/band/create.gsp</strong></code> ET <code><strong>grails-app/views/band/edit.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;!-- Ici on rajoute le enctype --&gt;
&lt;g:form action=&quot;save&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
...
   &lt;tr class=&quot;prop&quot;&gt;
      &lt;td valign=&quot;top&quot; class=&quot;name&quot;&gt;
         &lt;label for=&quot;logoPath&quot;&gt;&lt;g:message code=&quot;band.logoPath.label&quot; default=&quot;Logo Path&quot; /&gt;&lt;/label&gt;
      &lt;/td&gt;
      &lt;td valign=&quot;top&quot; class=&quot;value ${hasErrors(bean: bandInstance, field: 'logoPath', 'errors')}&quot;&gt;
         &lt;!-- et là un input file --&gt;
         &lt;input type=&quot;file&quot; name=&quot;logoFile&quot;/&gt;
      &lt;/td&gt;
   &lt;/tr&gt;
...
&lt;/g:form&gt;
</pre><p>A la soumission du formulaire, on peut désormais récupérer le fichier et le passer à notre service de sauvegarde en appelant :</p><pre class="brush: java; title: ; notranslate">
uploadService.saveImageFile(request.getFile('logoFile'), bandInstance.name)
</pre><p>Le paramètre passé à la méthode <code>request.getFile()</code> correspond à l&#8217;attribut <code>name</code> du champ input de notre formulaire. Encore une fois pour le code complet, voir le <a
href="http://github.com/aurelienmaury/nothunes/tree/v0.2" title="GitHub" >GitHub</a>. Il va de soi que ce code n&#8217;est pas en l&#8217;état complet, et qu&#8217;il faut lui ajouter des fonctionnalités techniques. Par exemple, pour supprimer les fichiers lors d&#8217;un changement d&#8217;image, ou dans d&#8217;autres cas tordus. La plupart de ces cas sont traités dans le code du projet. Je vous laisse lire le détail de l&#8217;implémentation directement dans le code, cet article étant déjà assez (trop ?) verbeux. <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/09/band_created.png" alt="band_created" title="band_created" width="600px" /></div><h3><a
name="Contrleurdetlchargement"></a>Contrôleur de téléchargement</h3><p>Nos images sont maintenant dans le répertoire dédié au stockage, mais on ne peut pas les afficher en faisant un lien directement dans nos pages, puisqu&#8217;elles sont en dehors de l&#8217;arborescence du site. Pour pallier ce problème, on crée un nouveau contrôleur, dédié au téléchargement des images. Le but est de pouvoir insérer dans nos GSP des tag <code>&lt;img/&gt;</code> avec un attribut <code>src="..."</code> pointant sur notre contrôleur. Encore une fois la méthode est assez directe :</p><ul><li>création d&#8217;un <code>DlController</code> :</li></ul><pre class="brush: java; title: ; notranslate">
grails create-controller fr.xebia.nothunes.Dl
</pre><ul><li>création d&#8217;une méthode <code>images</code> pour servir l&#8217;image en fonction des paramètres de requête :</li></ul><pre class="brush: java; title: ; notranslate">
class DlController {
   def images = {
      String filename = params.id
      log.debug &quot;dl image file : ${filename}&quot;
      def file = new File(grailsApplication.config.storage.image.directory + filename)
      if (file.exists()) {
         response.setContentType(&quot;application/octet-stream&quot;)
         response.setHeader(&quot;Content-Disposition&quot;, &quot;attachment; filename=${filename}&quot;)
         response.setContentLength(file.readBytes().size())
         response.getOutputStream() &lt;&lt; file.readBytes()
      }
      response.flush()
   }
}
</pre><p>Nous pouvons maintenant afficher l&#8217;image en créant un lien vers ce contrôleur :</p><p><code><strong>grails-app/views/band/show.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
...
&lt;img src=&quot;${createLink(controller:'dl', action:'images', params: [id: bandInstance.logoPath])}&quot;/&gt;
...
</pre><div
align="center"> <img
src="http://blog.xebia.fr/wp-content/uploads/2010/09/album_created.png" alt="album_created" title="album_created" width="600px"/></div><h3><a
name="Sprintreview"></a>Sprint review</h3><p>Comme je l&#8217;avais annoncé dans l&#8217;intro, ce billet est certainement plus intéressant pour ceux qui débutent avec Grails. Nous avons pu voir une méthode de création d&#8217;un contrôleur de bout en bout, et une façon simple de stocker et servir des fichiers à l&#8217;extérieur de l&#8217;arborescence du site. Retenez que les vues et contrôleurs générés par Grails sont très ouverts, il faut donc bien se souvenir de systématiquement :</p><ul><li>les protéger en rajoutant de filtres URL dans le bootstrap</li><li>ajouter les contrôles métiers si besoin dans le code du contrôleur</li><li>nettoyer les vues des informations techniques, superflues pour les utilisateurs</li></ul><p>Mission accomplie : le sprint backlog est bouclé et notre projet est livrable à la fin du sprint, ce qui reste un point crucial. Au prochain épisode, on va s&#8217;occuper des classes Tracks et de la navigation pour les internautes, le tout en utilisant massivement AJAX.</p><h3><a
name="Ressources"></a>Ressources</h3><ul><li><a
href="http://github.com/aurelienmaury/nothunes/tree/v0.2" title="Dpt GitHub du projet version 02" >Dépôt GitHub du projet, version 0.2</a></li><li><a
href="http://grails.org/File+Upload" title="Grails File upload" >Grails File upload</a></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/09/23/nothunes-lespace-vip/feed/</wfw:commentRss> <slash:comments>6</slash:comments> </item> <item><title>NoThunes, naissance d&#8217;un projet Grails</title><link>http://blog.xebia.fr/2010/09/09/nothunes-naissance-dun-projet-grails/</link> <comments>http://blog.xebia.fr/2010/09/09/nothunes-naissance-dun-projet-grails/#comments</comments> <pubDate>Thu, 09 Sep 2010 06:19:24 +0000</pubDate> <dc:creator>Aurélien Maury</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Grails]]></category> <category><![CDATA[Groovy]]></category> <category><![CDATA[NoThunes]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=5333</guid> <description><![CDATA[Les exemples de prise en main du framework Grails ne manquent pas sur la toile. Nous allons tenter de dépasser les Getting started en déroulant la réalisation d&#8217;une application web de bout en bout : Conception Réalisation Mise en production Evolutions et maintenance Parce qu&#8217;il est toujours plus agréable de travailler sur du concret, nous [...]]]></description> <content:encoded><![CDATA[<style type="text/css">table.tablo { border-collapse:collapse; border: 1px solid #6C626C; width:80%; font: normal normal normal 1.1em/normal Arial, sans-serif; } table.tablo thead { background: #EFEFEF; border-bottom: 1px solid #6C626C; border-top: 1px solid #6C626C; color: #4F2F4F; } table.tablo thead th { padding: 5px; } table.tablo tbody tr { border: 1px solid #6C626C; } table.tablo tbody tr td { padding: 5px; }</style><p><img
style="margin: 1em 1em 1em 1em; float: right;" src="http://blog.xebia.fr/wp-content/uploads/2010/09/nothunes_mini_logo.png" alt="Projet NoThunes" /></p><p>Les exemples de prise en main du framework Grails ne manquent pas sur la toile. Nous allons tenter de dépasser les <em>Getting started</em> en déroulant la réalisation d&#8217;une application web de bout en bout :</p><ul><li>Conception</li><li>Réalisation</li><li>Mise en production</li><li>Evolutions et maintenance</li></ul><p>Parce qu&#8217;il est toujours plus agréable de travailler sur du concret, nous allons réaliser pas à pas une plateforme de musique libre en ligne, que nous baptisons : <strong>Projet NoThunes</strong>, en clin d&#8217;oeil à une autre célèbre plateforme de musique payante. Tout le code du projet est <a
title="disponible sur GitHub" href="http://github.com/aurelienmaury/nothunes">disponible sur GitHub</a>.  Ce projet est donc Open Source mais aussi &laquo;&nbsp;Open méthode&nbsp;&raquo; puisque je vous ferai partager toutes les étapes de conception/réalisation au travers d&#8217;une série d&#8217;article. Chaque article sera associé à un tag du dépôt GitHub, pour que tout le monde puisse savoir à quelle version on se réfère.</p><p>Bien évidemment il y a plus d&#8217;une bonne façon de faire. Ce projet est mené dans une optique d&#8217;amélioration personnelle et de partage donc n&#8217;hésitez pas à discuter les choix techniques et à fournir des solutions alternatives en commentaire.</p><p>Dans ce premier billet, nous allons :</p><ul><li>définir le product backlog du projet</li><li>présenter les classes du modèle</li><li>démarrer notre projet</li><li>mettre en place la sécurité et les classes du modèle</li><li>créer un menu dynamique, différencié par rôle utilisateur</li></ul><h3><a
name="NoteTechnique"></a>Note technique</h3><p>Le projet est réalisé avec <a
title="Grails v133" href="http://grails.org/dist/grails-1.3.3.zip">Grails v1.3.3</a>.</p><h3><a
name="ProductBacklog"></a>Product Backlog</h3><p>On distingue 3 rôles utilisateurs :</p><ul><li><strong>les administrateurs :</strong> par définition omnipotents sur le site pour gérer les comptes utilisateurs, les règles de sécurité, etc.</li><li><strong>les internautes :</strong> les visiteurs lambda de notre site.</li><li><strong>les membres :</strong> des internautes qui se seront enregistrés pour accéder à plus de fonctionnalités.</li></ul><p>En sachant qu&#8217;un membre, s&#8217;il est authentifié, doit conserver l&#8217;accès à toutes les fonctionnalités des internautes, et qu&#8217;un administrateur doit aussi avoir accès aux fonctionnalités des membres.</p><p>Le product backlog est trié par business value décroissante. Je vous épargne le chiffrage agile et les fioritures de Scrum. <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /></p><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je dois pouvoir &#8230;</th></thead><tbody><tr><td>administrateur</td><td>créer des comptes utilisateurs (administrateurs et membres)</td></tr><tr><td>internaute</td><td>m&#8217;enregistrer pour obtenir un compte membre</td></tr><tr><td>membre</td><td>voir et mettre à jour mon profil utilisateur</td></tr><tr><td>membre</td><td>créer des groupes de musique</td></tr><tr><td>membre</td><td>modifier/supprimer les groupes de musique que j&#8217;ai créé</td></tr><tr><td>membre</td><td>créer/modifier/supprimer des albums liés à mes groupes</td></tr><tr><td>membre</td><td>créer/modifier/supprimer des morceaux liés à mes albums, en uploadant un fichier mp3</td></tr><tr><td>internaute</td><td>naviguer dans la hiérarchie de groupes/albums/morceaux</td></tr><tr><td>internaute</td><td>télécharger un morceau</td></tr><tr><td>internaute</td><td>écouter un morceau</td></tr><tr><td>internaute</td><td>rechercher des morceaux par mots-clés</td></tr></tbody></table></div><h3><a
name="Prsentationdumodlededonnes"></a>Présentation du modèle de données</h3><p>Il s&#8217;agit d&#8217;un cas d&#8217;école donc nous simplifions à l&#8217;extrême le modèle de données à gérer. Le but n&#8217;est pas (encore <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> ) de concurrencer les plateformes en place. On considère donc les objets :</p><div><table
class="tablo" border="0"><thead><th>Entité</th><th>représente &#8230;</th></thead><tbody><tr><td>Role</td><td>les rôles utilisateurs</td></tr><tr><td>User</td><td>les comptes utilisateurs administrateurs et membres</td></tr><tr><td>Band</td><td>un groupe de musique, créé par un membre</td></tr><tr><td>Album</td><td>un album enregistré par un groupe</td></tr><tr><td>Track</td><td>un morceau, une piste d&#8217;un Album</td></tr></tbody></table></div><p>Les attributs de chacun de ces objets sont volontairement simplistes. Cela nous donne le modèle suivant :</p><div><img
title="NoThunes" src="http://blog.xebia.fr/wp-content/uploads/2010/09/NoThunes.png" alt="NoThunes" /></div><h3><a
name="Dmarrageduprojet"></a>Démarrage du projet</h3><p>Comme tous ceux qui ont déjà survolé Grails le savent, un nouveau projet démarre toujours par le lancement de la commande :</p><pre class="brush: java; title: ; notranslate">
grails create-app nothunes
</pre><p>qui va générer l&#8217;arborescence initiale du projet. Comme c&#8217;est toujours plus confortable, l&#8217;étape suivante sera d&#8217;importer ce projet dans votre IDE favori. Pour travailler avec Grails le choix est limité et le support inégal : <a
title="NetBeans" href="http://www.netbeans.org/">NetBeans</a>, <a
title="SpringSource Tool Suite" href="http://www.springsource.com/products/sts">SpringSource Tool Suite</a> ou <a
title="IntelliJ IDEA" href="http://www.jetbrains.com/idea/">IntelliJ IDEA</a>. Au pire un <a
title="bon vieux vim" href="http://www.vim.org/scripts/script.php?script_id=3120">bon vieux vim</a> suffira.</p><h4><a
name="Miseenplacedelascurit"></a>Mise en place de la sécurité</h4><p>Le plugin de sécurité officiel du projet Grails est depuis peu le <a
title="Spring Security Core Plugin" href="http://grails.org/plugin/spring-security-core">Spring Security Core Plugin</a>, couplé au plugin <a
title="Spring Security UI" href="http://grails.org/plugin/spring-security-ui">Spring Security UI</a> pour la génération des écrans de CRUD concernant les rôles et utilisateurs. Malgré cela, je lui préfère encore pour quelques temps son prédécesseur : <a
title="Acegi Plugin" href="http://www.grails.org/plugin/acegi">Acegi Plugin</a>, dont le développement a été stoppé, mais qui est plus complet et plus simple à mettre en place. D&#8217;autre part, le côté stable du plugin Acegi, comparé au développement hyperactif des plugins Spring Security est assez confortable pour notre projet.</p><p>Je vous renvoie donc au <a
title="prcdent article" href="http://blog.xebia.fr/2010/02/25/grails-spring-security-plugin-la-securite-facile/">précédent article</a> que j&#8217;ai écrit sur le sujet. Le package de base à utiliser est : <code>fr.xebia.nothunes</code>.</p><h4><a
name="Crationdesclassesdumodle"></a>Création des classes du modèle</h4><p>Maintenant que nous avons les classes utiles pour la gestion des utilisateurs et de la sécurité, ajoutons un peu de métier dans tout ça. Pour cela on utilise la commande <a
title="grails createdomainclass" href="http://www.grails.org/GORM+-+Creating+a+domain+class"><code>grails create-domain-class</code></a> pour chacune des entités qu&#8217;on souhaite obtenir, puis on passe en édition dessus pour ajouter les attributs. Je trouve dommage de n&#8217;avoir pas plus poussé la ressemblance avec Ruby on Rails en autorisant l&#8217;ajout des attributs directement en paramètre de la ligne de commande.</p><p>Parmi les attributs que l&#8217;on donne à chaque classe, il y en a 2 qui sont partout et bien utiles : <code>lastUpdated</code> et <code>dateCreated</code>. Ces 2 attributs indiquent à GORM qu&#8217;il faut gérer le <a
title="timestamping automatique" href="http://www.grails.org/GORM+-+Events">timestamping automatique</a> des objets qui les possèdent.</p><h3><a
name="Dynamisonslemenu"></a>Dynamisons le menu</h3><p>La documentation du plugin Acegi n&#8217;en parle pas mais si vous téléchargez les sources du plugin et que vous allez voir le fichier <code>grails-app/taglib/org/grails/plugins/springsecurity/taglib/AuthorizeTagLib.groovy</code> vous tombez alors sur une taglib tout à fait disponible dès que vous faites usage de ce plugin dans votre projet, et qui offre les tags suivants :</p><ul><li>ifAllGranted</li><li>ifNotGranted</li><li>ifAnyGranted</li><li>loggedInUserInfo</li><li>isLoggedIn</li><li>isNotLoggedIn</li><li>loggedInUsername</li></ul><p>Nous allons baser la différenciation des menus sur ces tags qui permettent de tester, directement dans les GSP si l&#8217;utilisateur courant possède un rôle donné ou non.</p><p>La première étape est de séparer le contenu du menu dans un fichier GSP à part. Pour l&#8217;instant notre application est telle que générée par grails. Dans le fichier <code>grails-app/views/index.gsp</code> se trouve la page d&#8217;accueil par défaut. Elle contient une balise <code>&lt;div id="nav"/&gt;</code> qui encapsule le menu de gauche de la page d&#8217;accueil de l&#8217;application.</p><p>Pour ajouter plus de flexibilité, on sort cette div dans un nouveau fichier qu&#8217;on va placer dans <strong><code>grails-app/views/menu/_nav.gsp</code></strong> (après avoir créé le répertoire menu bien sûr), puis on remplace dans index.gsp la présence de cette div par un tag</p><pre class="brush: java; title: ; notranslate">
&lt;g:render template=&quot;menu/nav&quot;/&gt;
</pre><p>Si vous lancez un <code>grails run-app</code> et que vous allez voir la page d&#8217;accueil vous remarquerez que rien n&#8217;a changé. Le paramètre passé au tag <code>render</code> indique d&#8217;inclure le code du fichier GSP situé dans le répertoire <strong>menu/</strong> et nommé <code>_nav.gsp</code> (le caractère est indispensable en début de nom).</p><p>Ce découpage, allié aux tags du plugin Acegi, nous conduit à un joli découpage propre :</p><p><code><strong>grails-app/views/menu/_nav.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;div id=&quot;nav&quot;&gt;
&lt;ul&gt;
	&lt;li&gt;Welcome,&lt;/li&gt;
	&lt;li&gt;Logout&lt;/li&gt;
	&lt;li&gt;Login&lt;/li&gt;
	&lt;li&gt;No account ? : Register&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;homePagePanel&quot;&gt;
&lt;div class=&quot;panelBody&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre><p><code><strong>grails-app/views/menu/_admin.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;h1&gt;Admin manages ...&lt;/h1&gt;
&lt;ul&gt;
	&lt;li&gt;roles&lt;/li&gt;
	&lt;li&gt;users&lt;/li&gt;
	&lt;li&gt;requestMaps&lt;/li&gt;
&lt;/ul&gt;
</pre><p><code><strong>grails-app/views/menu/_user.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;h1&gt;Member manages ...&lt;/h1&gt;
&lt;ul&gt;
	&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
</pre><p><code><strong>grails-app/views/menu/_all.gsp</strong></code></p><pre class="brush: java; title: ; notranslate">
&lt;h1&gt;Menu&lt;/h1&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;${createLinkTo(dir:'')}&quot;&gt;Home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</pre><p>Maintenant il suffira d&#8217;utiliser le tag <code>&lt;g:render template="menu/nav" /&gt;</code> directement dans le layout global pour appliquer le menu sur toute notre application, et on a maintenant un fichier GSP par section de menu avec juste ce qu&#8217;il faut de tag de sécurité pour gérer l&#8217;affichage contextuel.</p><p>Après ce découpage, j&#8217;ai travaillé un peu le layout et la css afin d&#8217;avoir toutes les pages aux couleurs du projet, c&#8217;est toujours plus agréable pour travailler. Pour éviter de surcharger cet article, si vous voulez voir le détail complet du travail de mise en forme, reportez vous au <a
title="GitHub du projet" href="http://github.com/aurelienmaury/nothunes">GitHub du projet</a>.</p><h3><a
name="Debriefing"></a>Debriefing</h3><p>Voilà un bon démarrage pour notre plateforme de musique. Le projet est lancé et nous pouvons travailler dans de bonnes conditions pour la suite. L&#8217;état du product backlog :</p><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je peux déjà &#8230;</th></thead><tbody><tr><td>administrateur</td><td>créer des comptes utilisateurs (administrateurs et membres)</td></tr><tr><td>internaute</td><td>m&#8217;enregistrer pour obtenir un compte membre</td></tr></tbody></table></div><p>Notre projet est déjà livrable en production avec ces fonctionnalités.</p><div><img
src="http://blog.xebia.fr/wp-content/uploads/2010/09/home_page.png" border="0" alt="" width="600px" /></div><p>A bientôt pour le prochain épisode :</p><div><table
class="tablo" border="0"><thead><th>En tant que &#8230;</th><th>je pourrai &#8230;</th></thead><tbody><tr><td>membre</td><td>voir et mettre à jour mon profil utilisateur</td></tr><tr><td>membre</td><td>créer des groupes de musique</td></tr><tr><td>membre</td><td>modifier/supprimer les groupes de musique que j&#8217;ai créé</td></tr><tr><td>membre</td><td>créer/modifier/supprimer des albums liés à mes groupes</td></tr></tbody></table></div><h3><a
name="Ressources"></a>Ressources</h3><ul><li><a
title="Site officiel de Grails" href="http://www.grails.org/">Site officiel de Grails</a></li><li><a
title="Dpt GitHub du projet NoThunes" href="http://github.com/aurelienmaury/nothunes">Dépôt GitHub du projet NoThunes</a></li><li><a
title="Tag de la version dcrite dans ce billet" href="http://github.com/aurelienmaury/nothunes/tree/v0.1">Tag de la version décrite dans ce billet</a></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2010/09/09/nothunes-naissance-dun-projet-grails/feed/</wfw:commentRss> <slash:comments>1</slash:comments> </item> <item><title>Refactorisation de bases de données avec Liquibase</title><link>http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase/</link> <comments>http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase/#comments</comments> <pubDate>Wed, 03 Dec 2008 15:55:59 +0000</pubDate> <dc:creator>Emmanuel Servent</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[Exploitation]]></category> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[bases de données]]></category> <category><![CDATA[database]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[liquibase]]></category> <category><![CDATA[refactoring]]></category> <category><![CDATA[refactorisation]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=1102</guid> <description><![CDATA[Gérer les modifications d&#8217;une base de données est toujours une affaire délicate, que ce soit lors du développement d&#8217;une application, pendant le déroulement des tests unitaires et même lors du déploiement en production. On observe un grand nombre de difficultés, en particulier dans les projets Agile, où les changements sur le modèle de données sont [...]]]></description> <content:encoded><![CDATA[<p>Gérer les modifications d&#8217;une base de données est toujours une affaire délicate, que ce soit lors du développement d&#8217;une application, pendant le déroulement des tests unitaires et même lors du déploiement en production.<br
/> On observe un grand nombre de difficultés, en particulier dans les projets Agile, où les changements sur le modèle de données sont fréquents.<br
/> A l&#8217;heure actuelle, peu d&#8217;outils sont disponibles sur le marché et on pense rarement à les mettre en place sur nos projets. On passe par des solutions « maisons » avec toutes les difficultés que cela peut engendrer.</p><p>Tout d&#8217;abord, nous ferons un point sur <a
href="http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase#Problmespossparlagestiondesbas" title="les problèmes posés par la gestion des bases de données" >les problèmes posés par la gestion des bases de données</a>, puis nous verrons comment <a
href="http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase#Refactorisationdebasesdedonnes" title="Liquibase, outils de refactorisation" >Liquibase, outils de refactorisation</a>, peut nous aider à les surmonter.<br
/> Nous continuerons par la description de <a
href="http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase#Caractristiquesetfonctionnalit" title="ses caractéristiques et fonctionnalités principales" >ses caractéristiques et fonctionnalités principales</a> et par <a
href="http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase#LancementdeLiquibase" title="les différents moyens de l'exécuter" >les différents moyens de l&#8217;exécuter</a>.<br
/> <a
href="http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase#Petitexercicepoursefamiliarise" title="Un petit exercice" >Un petit exercice</a> pour se familiariser avec cet outil viendra conclure ce billet.</p><h3><a
name="Problmespossparlagestiondesbas"></a>Problèmes posés par la gestion des bases de données</h3><p>Lorsqu&#8217;un projet démarre, on se demande souvent si l&#8217;on doit installer <strong>une base de données par développeur ou une base commune pour tous</strong>.<br
/> Sur des projets de taille réduite, une base peut suffire et une personne est désignée pour centraliser les changements et éviter ainsi des problèmes de redondance, d&#8217;incohérence, etc. Mais dans ce cas-là, dès qu&#8217;un changement est effectué, tout le monde doit impérativement mettre à jour le modèle objet sinon il court le risque de ne plus pouvoir lancer l&#8217;application. Par ailleurs, les jeux de tests de chaque développeur peuvent &laquo;&nbsp;se télescoper&nbsp;&raquo; ce qui peut faire perdre beaucoup de temps.<br
/> Sur des projets plus importants, il est préférable d&#8217;avoir une base par développeur, ce qui cause d&#8217;autres difficultés, telles que les répercutions des changements d&#8217;un développeur à l&#8217;autre ou encore une intégration plus laborieuse.<br
/> Se pose aussi la question suivante : <strong>comment va-t-on tracer les changements successifs ?</strong> Le plus courant reste d&#8217;écrire un fichier d&#8217;update SQL mais ce n&#8217;est pas fait systématiquement pendant les développements et ça manque de structure (au sens XML) et ça pas de signification propre. Par exemple, rien ne permet rapidement de différencier dans un script, le code qui applique les changements de celui qui les rollback (à part les commentaires !); ou les actions comme &laquo;&nbsp;Renommer&nbsp;&raquo; se traduisent par un DROP COLUMN / ADD COLUMN, ce qui fait perdre le sens de l&#8217;action.<br
/> Cette dernière question amène à s&#8217;en poser d&#8217;autres :</p><ul><li>comment versionner les changements effectués sur une base de données ?</li><li>comment appliquer les changements ? Entre développeurs, mais aussi lorsqu&#8217;on passe en intégration, puis en recette, etc. Pour le moment, cette étape prend du temps, que ce soit en amont si on décide de packager correctement les changements, ou en aval lorsqu&#8217;il s&#8217;agit de dérouler les scripts.</li></ul><p>Voilà en quelques phrases les multiples difficultés rencontrées lorsqu&#8217;on souhaite refactoriser une base de données.<br
/> Regardons à présent ce qu&#8217;un outil comme Liquibase peut faire pour nous accompagner dans ce travail délicat.</p><h3><a
name="Refactorisationdebasesdedonnes"></a>Refactorisation de bases de données avec Liquibase</h3><p>Liquibase est un outil qui peut s&#8217;intégrer dans un projet (Ant, Maven, etc.) ou se lancer <em>en mode standalone</em> directement en ligne de commande (jar).<br
/> Son objectif est de faciliter les modifications apportées à une base de données. Il est particulièrement pratique dans des projets ayant adoptés une méthodologie Agile, car les changements sont nombreux, fréquents et importants.<br
/> Le développeur enregistre dans un fichier XML (changelogs.xml), les différentes refactorisations qu&#8217;il souhaite effectuer (création d&#8217;une nouvelle table, renommage d&#8217;un champ, insertion de données, etc.). Ensuite, il suffit de lancer Liquibase en lui donnant les paramètres de connexion à la base de données et le fichier changelogs.xml. Liquibase applique ces modifications et les trace dans une table.<br
/> Les fichiers changelogs.xml sont versionnés dans le gestionnaire de sources, et sont considérés comme n&#8217;importe quelle autre ressource du projet. La base de données est alors complètement intégrée au cycle de développement.</p><p>Après cette brève présentation de Liquibase, intéressons-nous aux caractéristiques principales de cet outil.</p><h3><a
name="Caractristiquesetfonctionnalit"></a>Caractéristiques et fonctionnalités principales</h3><p>Liquibase est Open Source diffusé sous licence LGPL. Ce projet Open Source est actif, la dernière version (1.8.1) est sortie en octobre 2008 et sa road map montre qu&#8217;il veut aller encore plus loin. Il supporte un grand nombre de bases de données (> 10) et propose plus de 30 refactorisations possibles. Et pour certaines, il sait même faire un roll back (Ex: renommer une colonne).<br
/> Les changements peuvent être regroupés dans un changeset, ce qui permet de conserver un sens et une cohérence aux modifications.<br
/> On peut également n&#8217;exécuter des changements que sous certaines conditions, ou selon un certain profil d&#8217;exécution (utile pour les jeux de tests par exemple).</p><p>On peut noter également que Liquibase n&#8217;est pas juste &laquo;&nbsp;braqué&nbsp;&raquo; vers la refactorisation de la base de données, il offre d&#8217;autres fonctionnalit</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/12/03/refactorisation-de-bases-de-donnees-avec-liquibase/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>Ce que vous avez peut-être raté au troisième trimestre 2008</title><link>http://blog.xebia.fr/2008/10/01/ce-que-vous-avez-peut-etre-rate-au-troisieme-trimestre-2008/</link> <comments>http://blog.xebia.fr/2008/10/01/ce-que-vous-avez-peut-etre-rate-au-troisieme-trimestre-2008/#comments</comments> <pubDate>Wed, 01 Oct 2008 06:51:57 +0000</pubDate> <dc:creator>Xebia France</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[annotation]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Exploitation]]></category> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[log]]></category> <category><![CDATA[Spring]]></category> <category><![CDATA[Supervision]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/?p=773</guid> <description><![CDATA[Voici la liste des billets les plus lus sur ce blog en juillet, août et septembre : Les 10 commandements des logs applicatives Tout au long du cycle de vie d&#8217;une application J2EE, il est nécessaire de posséder des traces de qualité : durant le développement, afin de suivre l&#8217;exécution pas à pas et de [...]]]></description> <content:encoded><![CDATA[<p>Voici la liste des billets les plus lus sur ce blog en juillet, août et septembre :</p><h4><a
href="http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/">Les 10 commandements des logs applicatives</a></h4><p>Tout au long du cycle de vie d&#8217;une application J2EE, il est nécessaire de posséder des traces de qualité :</p><ul><li>durant le développement, afin de suivre l&#8217;exécution pas à pas et de détecter d&#8217;éventuelles anomalies.</li><li>durant la recette, afin de corréler anomalies fonctionnelles et exécution du programme.</li><li>durant l&#8217;exploitation, afin de surveiller la &laquo;&nbsp;bonne santé&nbsp;&raquo; de l&#8217;application.</li></ul><p>Mais obtenir des traces de qualité n&#8217;est pas un exercice trivial. C&#8217;est pourquoi nous vous proposons nos 10 commandements des logs.</p><p><a
href="http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/08/08/simplifiez-votre-configuration-spring-25-avec-les-annotations/">Simplifiez votre configuration Spring 2.5 avec les annotations</a></h4><p>Spring 2.5 <a
href="http://www.springframework.org/node/561" title="est sorti" >est sorti</a> depuis le 19 novembre 2007 comme nous l&#8217;annoncions, il y a quelques temps, dans notre <a
href="http://blog.xebia.fr/2007/11/26/revue-de-presse-xebia-33/#Spring" title="revue de presse" >revue de presse</a>. Vous avez comme moi sagement mis à jour vos poms Maven2 vers la dernière release de Spring (normalement et la plupart du temps compatible avec les versions 2.0.x).</p><p>Mais avez-vous vraiment profité des nouveautés de cette version en terme de configuration ?</p><p><a
href="http://blog.xebia.fr/2008/08/08/simplifiez-votre-configuration-spring-25-avec-les-annotations/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/08/13/programmation-concurrentielle-notions-fondamentales/">Programmation concurrente : notions fondamentales</a></h4><p>Jouer avec les Threads n&#8217;est pas trivial. En informatique de gestion, cette difficulté est heureusement masquée par les serveurs d&#8217;application et les API spécifiques. La plupart du temps, ils permettent aux développeurs de s&#8217;abstraire de ces contraintes et de se concentrer sur le code métier, moins technique. Il arrive pourtant qu&#8217;il faille se relever les manches. Certains besoins vous ont certainement déjà poussé à faire communiquer 2 Threads.<br
/> Si le développement n&#8217;est pas facile, le debug peut devenir une calamité ! La programmation concurrente ne soulève pourtant que 3 types majeurs de problèmes. Faites le sondage autour de vous, les développeurs associent trop rapidement le mot clé <code>synchronize</code> au multithreading sans en comprendre véritablement le fonctionnement. Demandez-leur ensuite de vous décrire l&#8217;utilité du mot clé <code>volatile</code> &#8230;</p><p>Cet article revient sur les grands principes de la programmation concurrente.</p><p><a
href="http://blog.xebia.fr/2008/08/13/programmation-concurrentielle-notions-fondamentales/">Lire cet article »</a></p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/10/01/ce-que-vous-avez-peut-etre-rate-au-troisieme-trimestre-2008/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Les 10 commandements des logs applicatives</title><link>http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/</link> <comments>http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/#comments</comments> <pubDate>Fri, 11 Jul 2008 08:34:40 +0000</pubDate> <dc:creator>Pablo Lopez</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Exploitation]]></category> <category><![CDATA[log]]></category> <category><![CDATA[Supervision]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/</guid> <description><![CDATA[Tout au long du cycle de vie d&#8217;une application J2EE, il est nécessaire de posséder des traces de qualité : durant le développement, afin de suivre l&#8217;exécution pas à pas et de détecter d&#8217;éventuelles anomalies. durant la recette, afin de corréler anomalies fonctionnelles et exécution du programme. durant l&#8217;exploitation, afin de surveiller la &#171;&#160;bonne santé&#160;&#187; [...]]]></description> <content:encoded><![CDATA[<p>Tout au long du cycle de vie d&#8217;une application J2EE, il est nécessaire de posséder des traces de qualité :</p><ul><li>durant le développement, afin de suivre l&#8217;exécution pas à pas et de détecter d&#8217;éventuelles anomalies.</li><li>durant la recette, afin de corréler anomalies fonctionnelles et exécution du programme.</li><li>durant l&#8217;exploitation, afin de surveiller la &laquo;&nbsp;bonne santé&nbsp;&raquo; de l&#8217;application.</li></ul><p>Mais obtenir des traces de qualité n&#8217;est pas un exercice trivial. C&#8217;est pourquoi nous vous proposons nos 10 commandements des logs.</p><p>Une application J2EE possède de nombreux types de logs :</p><ul><li>Des logs purement techniques, ponctuelles, générées automatiquement ou à la demande comme les dumps (ThreadDump, MemoryDump&#8230;).</li><li>Des logs provenant de l&#8217;environnement d&#8217;exécution : logs Système (journaux d&#8217;évènements serveur&#8230;), logs du serveur applicatif et logs générées par les librairies tierces utilisées par l&#8217;application.</li><li>Les logs générées par le code développé pour l&#8217;application, que nous désignerons comme logs applicatives, sur lesquelles l&#8217;équipe projet a la main.</li></ul><p>Nous traiterons quasi exclusivement de ces dernières dans cet article.</p><h3><a
title="Lescommandements" name="Lescommandements"></a>Les 10 commandements</h3><ol><li>Les logs sont un <strong>outil collaboratif</strong> issu d&#8217;un processus <strong>itératif</strong>.</li><li>Les logs sont une <strong>brique applicative intégrée</strong> à l&#8217;<strong>environnement d&#8217;exécution</strong> de l&#8217;application.</li><li>Les logs sont <strong>paramétrées</strong> en fonction des <strong>besoins</strong>.</li><li>Les logs doivent avoir un <strong>impact minimal sur les performances</strong> globales de l&#8217;application.</li><li>Les logs ne doivent pas occasionner de <strong>perte d&#8217;information</strong>.</li><li>Une utilisation correcte du logger est la première source d&#8217;information.</li><li>L&#8217;<strong>utilisation</strong> du framework de logs doit être <strong>simplifiée</strong>.</li><li>Une log efficace doit <strong>remplacer le debugger</strong>.</li><li>Le framework mis en place doit servir <strong>exclusivement</strong> à produire des <strong>logs applicatives</strong>.</li><li>Les logs permettent de décrire le <strong>comportement aux frontières</strong> de l&#8217;application.</li></ol><h3><a
title="Entronsdansledtail" name="Entronsdansledtail"></a>Entrons dans le détail</h3><h4>1 &#8211; Les logs sont un outil collaboratif issu d&#8217;un processus itératif</h4><p>Les messages de logs sont destinés à tous les acteurs du projet.<br
/> Il est essentiel de garder à l&#8217;esprit, lors de de tout développement, qu&#8217;une application est vouée à être développée, puis testée, puis exploitée (et éventuellement corrigée). Les logs doivent prendre en compte ces 3 phases de la vie du projet, et permettre à tous les acteurs intervenant lors de ces différentes phases de pouvoir interagir efficacement.<br
/> C&#8217;est pourquoi chaque méthode devrait au minimum contenir des traces de niveau INFO, WARN et ERROR, chaque niveau ciblant un des acteurs intervenant sur l&#8217;application.<br
/> De plus, il est difficile de réaliser une &#8216;bonne&#8217; log du premier coup. La pertinence de celle-ci sera renforcée par des échanges constants entre exploitation et développement (ciblage spécifique d&#8217;un problème de performance, anomalie non remontée initialement&#8230;).<br
/> Chaque correction d&#8217;anomalie devrait entrainer l&#8217;ajout de traces de niveau DEBUG, qui permettront un diagnostic plus fin en cas de nouvelle occurrence.</p><h4>2 &#8211; Les logs sont une brique applicative intégrée à l&#8217;environnement d&#8217;exécution de l&#8217;application</h4><p>C&#8217;est pourquoi il est essentiel de choisir un framework de logs bien intégré dans le middleware utilisé en production.<br
/> Ce framework doit pouvoir être utilisé et paramétré aisément pour obtenir des traces de l&#8217;ensemble des composants du système, que ce soit de la part de librairies embarquées tierces comme des systèmes &#8216;contenants&#8217; (serveur d&#8217;application par exemple).<br
/> Par exemple, Jakarta Commons Logging est délicat à utiliser avec Websphere.</p><h4>3 &#8211; Les logs sont paramétrées en fonction des besoins</h4><p>L&#8217;écriture d&#8217;une log a un coût qu&#8217;il ne faut pas négliger. Les performances d&#8217;une application en production ne doivent pas être plombées par la génération de lignes de log inutiles. Et il est inenvisageable de demander à chaque développeur d&#8217;effacer systématiquement les logs qu&#8217;il ajoute (si ces logs ont été utiles à un moment de la vie du projet, il y a de grandes chances qu&#8217;elles puissent resservir plus tard).<br
/> Il est donc impératif de choisir un framework de logs <strong>facilement administrable</strong> autant en développement qu&#8217;en production :</p><p><strong>Pour le développement :</strong></p><ul><li>Une configuration de logs par défaut quand on fait un checkout du code (typiquement un fichier log4j.properties dans le classpath configuré au niveau WARN qui redirige vers System.out).</li><li>La possibilité de modifier à chaud la configuration des logs lors de l&#8217;éxécution de l&#8217;application dans l&#8217;IDE (typiquement un configureAndWatch sur classpath:log4j.properties).</li></ul><p><strong>Pour l&#8217;exploitation :</strong></p><ul><li>Une configuration par fichiers, séparée du WAR, ce qui permet de livrer / modifier l&#8217;application indépendamment de sa configuration (et vice et versa).</li><li>Des mécanismes de reconfiguration à chaud par modification de fichier et/ou par API (JMX).</li></ul><p>On se doit d&#8217;exploiter au maximum les <strong>différents Appenders</strong>.<br
/> Le système d&#8217;Appender fourni avec les principaux framework de log permet de gérer finement la persistence de la trace : elle peut être éphémère, par exemple dans une sortie Console, ou bien être archivée périodiquement, en utilisant un système de DailyRollingFileAppender.<br
/> Une bonne connaissance des Appenders est essentielle dans la gestion à long terme de la log, aussi bien en terme de performance (utilisation des Loggers asynchrones en utilisant JMS) qu&#8217;en terme d&#8217;espace disque (configurer correctement les FileAppenders afin que les fichiers restent exploitables).</p><h4>4 &#8211; Les logs doivent avoir un impact minimal sur les performances globales de l&#8217;application</h4><p>La majorité des frameworks de log disposent de méthodes conditionnelles de type log.is&lt;Priority&gt;Enabled(). Conditionner l&#8217;écriture de la log par ces méthodes permet d&#8217;économiser des ressources précieuses lors du runtime (construction d&#8217;une aggrégation de chaînes de caractères par exemple). La méthode log.&lt;Priority&gt;(String) effectue bien entendu le même test, mais après la résolution des paramètres.</p><p>Evitez :</p><pre class="brush: java; title: ; notranslate">
log.debug(&quot;Entrée dans la méthode doIt(), avec param1[&quot; + param1 + &quot;], param2 [&quot; + param2 + &quot;]&quot;);
</pre><p>Préférez :</p><pre class="brush: java; title: ; notranslate">
if(log.isDebugEnabled() {
   log.debug(&quot;Entrée dans la méthode doIt(), avec param1[&quot; + param1 + &quot;], param2 [&quot; + param2 + &quot;]&quot;);
}
</pre><h4>5 &#8211; Les logs ne doivent pas occasionner de perte d&#8217;information</h4><p>Ce commandement s&#8217;applique plus particulièrement aux logs des exceptions.<br
/> Il est primordial de qualifier le plus finement possible les exceptions tracées.<br
/> C&#8217;est pourquoi on privilégiera systématiquement les méthodes Logger#error(java.lang.Object message, java.lang.Throwable t) à Logger#error(java.lang.Object message) , afin de ne pas perdre les informations exposées par la stackTrace.<br
/> De plus, on explicitera au maximum le contexte d&#8217;exécution dans le message.</p><p>Evitez :</p><pre class="brush: java; title: ; notranslate">
...
} catch (UnknownUserException exception) {
   log.error(&quot;Une exception s'est produite :&quot; + exception);
   ...
}
...
</pre><p>Préférez :</p><pre class="brush: java; title: ; notranslate">
...
catch (UnknownUserException exception) {
   log.error(&quot;L'utilisateur [&quot; + user :&quot;] n'a pas été trouvé dans l'annuaire [&quot;+ myLdap + &quot;]&quot;, exception);
...
}
...
</pre><p><em>NB : le traitement des exceptions fera l&#8217;objet d&#8217;un prochain article.</em></p><h4>6 &#8211; Une utilisation correcte du logger est la première source d&#8217;information</h4><p>Chaque niveau de log a ses spécificités. Bien définir l&#8217;utilisation de chacun de ces niveaux permet de s&#8217;affranchir du risque de logger trop, ou trop peu, aux différentes étapes du cycle de vie de l&#8217;application. Utiliser des <strong>niveaux de log cohérents et pertinents</strong> donne donc un premier niveau d&#8217;information essentiel.<br
/> Voici les niveaux existants dans les différents frameworks, et leur utilisation communément admise (Log4J/Apache Commons Logging &#8211; java.util.logging) :</p><ul><li>TRACE / FINE : niveau d&#8217;information ultrafin.</li><li>DEBUG / CONFIG : information détaillée pour le suivi d&#8217;exécution du programme (identification et résolution d&#8217;éventuelles anomalies).</li><li>INFO : information essentielle sur le programme, suivi de l&#8217;exécution d&#8217;un point de vue global.</li><li>WARN / WARNING : situation d&#8217;exécution non idéale (utilisation d&#8217;API dépréciées, ressource non critique absente&#8230;).</li><li>ERROR / &lt;pas d&#8217;équivalent&gt; : situation d&#8217;erreur ou inattendue, qui n&#8217;entraine pas forcément une situation de blocage (accès à un service externe non critique et dont l&#8217;accès est re-testé périodiquement).</li><li>FATAL / SEVERE : situation d&#8217;erreur critique, qui entraîne un blocage voire un arrêt du système (problème de connexion à la DB par exemple).</li></ul><h4>7 &#8211; L&#8217;utilisation du framework de logs doit être simplifiée</h4><p>L&#8217;ajout de log doit être le moins contraignant possible pour les développeurs. De plus, dans le cadre d&#8217;une équipe de développement, il n&#8217;est pas toujours facile pour un développeur de savoir quelles sont les informations pertinentes à tracer, s&#8217;il n&#8217;est pas l&#8217;auteur de la classe suivie. Il faut donc faciliter la création de logs de qualité&#8230;</p><ul><li> &#8230; en rendant ses <strong>classes explicites</strong>.<br
/> En surchargeant la méthode Object#toString(), afin qu&#8217;elle renvoie les informations principales d&#8217;une classe, on peut facilement tracer les informations en entrée ou sortie de méthode, et expliciter le contexte applicatif de l&#8217;exécution. Attention, les méthodes toString() ne doivent jamais lancer d&#8217;exception.</li></ul><p>Evitez :</p><pre class="brush: java; title: ; notranslate">
public String toString(){
   StringBuilder sb = new StringBuilder();
   sb.append(getClass().getName())
   // Ici, on peut avoir une NullPointerException
   sb.append(&quot; myfield=[&quot;).append(this.myfield.toString()).append(&quot;]&quot;);
   return sb.toString();
}
</pre><p>Préférez :</p><pre class="brush: java; title: ; notranslate">
public String toString(){
   return new ToStringBuilder(this).append(&quot;myfield&quot;, this.myfield).toString();
}
</pre><ul><li> &#8230; en définissant, documentant et partageant la <strong>hiérarchie de logger</strong><br
/> L&#8217;utilisation la plus répandue de la hiérarchie de Logger consitste à déclarer un logger par classe.</li></ul><pre class="brush: java; title: ; notranslate">
private final static Logger LOGGER = Logger.getLogger(MyClass.class);
</pre><p>Cependant, il est souvent interessant de réfléchir à une hiérarchie plus poussée, par domaines technico-fonctionnels par exemple.<br
/> Il est parfois même utile de déclarer plusieurs Loggers par classe (un logger pour le suivi de l&#8217;exécution générale, un logger particulier pour surveiller une fonction critique)<br
/> Afin d&#8217;avoir des logs cohérentes, il est nécessaire de définir et de documenter la hiérarchie des Loggers au plus tôt de la vie du projet, et de la communiquer à l&#8217;ensemble des équipes, afin d&#8217;unifier les méthodes de logging.</p><h4>8 &#8211; Une log efficace doit remplacer le debugger</h4><p>Il existe un grand nombre de circonstances qui rendent impossible l&#8217;utilisation d&#8217;un debugger, dont la plus évidente, la nécessité de debugger un système en production. Mais on pourrait aussi citer les traitements <span
class="diffaddedchars">multi-threadés, etc</span><br
/> Dans ces cas particuliers, l&#8217;utilisation de la log doit se substituer au débuggeur, et répondre aux mêmes besoins : &laquo;&nbsp;exécution&nbsp;&raquo; pas à pas <span
class="diffaddedchars">des algorithmes</span>, inspection des valeurs d&#8217;entrée et de <span
class="diffaddedchars">sortie des points de passage clefs</span>, parcours des différentes couches de l&#8217;application, surveillance des entrées / sorties du système.<br
/> La mise à disposition d&#8217;un tel outil est vitale pour les développeurs, car trop souvent, les équipes de développements sont dans l&#8217;obligation d&#8217;attendre plusieurs occurrences d&#8217;un même bug en production avant d&#8217;être aptes à déterminer ses conditions précises d&#8217;occurrence.</p><h4>9 &#8211; Le framework mis en place doit servir exclusivement à produire des logs applicatives</h4><p>Les logs sont et doivent être un outil technique. Utiliser le framework de log pour générer des traces &#8216;fonctionnelles&#8217; (audit, génération de traces métier, constitution de fichiers métier) présente un risque majeur de perte de données. En effet, le risque d&#8217;effacement des logs est réel : la configuration des différents Appenders provoquent souvent un effacement périodique des logs, le comportement global des logs peut être totalement modifié dans le cadre d&#8217;une intervention de type &#8216;troubleshooting&#8217; (limitation des traces à un sous-ensemble de classes très restreint)&#8230;<br
/> En revanche, il est possible d&#8217;utiliser les fonctionnalités offertes par le framework de logs (RollingFileAppender, etc) mais il faut que cette utilisation soit dissociée du fonctionnement et de la configuration des logs applicatives.</p><h4>10 &#8211; Les logs permettent de décrire le comportement aux frontières de l&#8217;application</h4><p>L&#8217;un des enjeux majeurs des Systèmes d&#8217;Information actuels est l&#8217;intégration des différentes composantes de ce SI.<br
/> Il est très souvent ardu de suivre intégralement la chorégraphie entre les différents composants, aboutissant à la réponse &#8216;finale&#8217; du système.<br
/> D&#8217;où la nécessité de tracer le plus finement chaque appel entrant ou sortant du système, en utilisant :</p><ul><li>l&#8217;heure de cet appel</li><li>le système appelant / appelé (par HTTP, JMS, SOAP&#8230;)</li><li>une description concise de l&#8217;interaction sollicitée</li><li>éventuellement, au niveau le plus fin (TRACE), le message émis / reçu</li></ul><p>Certaines librairies Open Source, comme commons-http-client (HTTP) et CXF (SOAP) offrent ces fonctionnalités de manière native.</p><h3><a
title="Enconclusion" name="Enconclusion"></a>En conclusion</h3><p>L&#8217;application de ces 10 commandements nous pousse de manière assez naturelle à l&#8217;utilisation de Log4J, qui répond à l&#8217;ensemble des besoins listés ci dessus.<br
/> Le choix de Commons Logging ou de java.util.logging, si il est loin d&#8217;être proscrit, demande une plus grande vigilance (voir les articles &#8216;Critiques&#8217; cités en références).</p><p>Quoi qu&#8217;il en soit, il est primordial d&#8217;avoir en tête ces bonnes pratiques dès le démarrage d&#8217;un projet. En effet, au même titre que les tests unitaires ou que la documentation interne du code, les logs ne peuvent être repoussées à &#8216;plus tard&#8217; : plus le temps passe, plus la complexité et le volume de code à &#8216;tracer&#8217; augmentent.<br
/> Ceux qui ont expérimenté &laquo;&nbsp;l&#8217;intervention pompier&nbsp;&raquo; sur une application peu ou mal loggée savent qu&#8217;il est quasiment impossible d&#8217;otenir des logs globales et/ou pertinentes a posteriori (d&#8217;autant qu&#8217;il est difficile de &#8216;budgeter&#8217; une telle opération, dont la valeur métier est nulle).</p><h3><a
title="Rfrences" name="Rfrences"></a>Références</h3><h4>Les frameworks de logs les plus connus</h4><ul><li><em><a
href="http://www.slf4j.org/" title="Simple Logging Facade for Java (SLF4J)">Simple Logging Facade for Java (SLF4J)</a> : A simple facade for various logging APIs allowing to the end-user to plug in the desired implementation at deployment time.</em></li><li><em><a
href="http://logging.apache.org/log4j/" title="Log4J ">Log4J</a> : Site officiel du framework Log4J </em></li><li><em><a
href="http://commons.apache.org/logging/" title="Java Commons Logging ">Java Commons Logging </a> : Site officiel du framework Commons Logging</em></li><li><em><a
href="http://java.sun.com/j2se/1.4.2/docs/api/index.html" title="java.util.logging ">java.util.logging </a> : API de java.util.logging</em></li></ul><h4>Critiques</h4><ul><li><em><a
href="http://radio.weblogs.com/0122027/2003/08/15.html" title="Rod Waldhoff's Weblog - Commons Logging was my fault ">Rod Waldhoff&#8217;s Weblog &#8211; Commons Logging was my fault </a> : une critique de Commons Logging par son auteur</em></li><li><em><a
href="http://tomcat.apache.org/tomcat-6.0-doc/logging.html" title="Tomcat Docs - Logging">Tomcat Docs &#8211; Logging</a> : The default implemenatation of java.util.logging provided in the JDK is too limited to be useful. &#8230;</em></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/07/11/les-10-commandements-des-logs-applicatives/feed/</wfw:commentRss> <slash:comments>8</slash:comments> </item> <item><title>Ce que vous avez peut-être raté au second trimestre 2008</title><link>http://blog.xebia.fr/2008/07/01/ce-que-vous-avez-peut-etre-rate-au-second-trimestre-2008/</link> <comments>http://blog.xebia.fr/2008/07/01/ce-que-vous-avez-peut-etre-rate-au-second-trimestre-2008/#comments</comments> <pubDate>Tue, 01 Jul 2008 15:31:55 +0000</pubDate> <dc:creator>Xebia France</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[J2EE]]></category> <category><![CDATA[Spring]]></category> <category><![CDATA[Tests unitaires]]></category> <category><![CDATA[urbanisation]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/07/01/ce-que-vous-avez-peut-etre-rate-au-second-trimestre-2008/</guid> <description><![CDATA[Voici la liste des billets les plus lus sur ce blog en avril, mai et juin : Urbanisation pour les nuls Le XKE (Xebia Knowledge Exchange) de mars a été l&#8217;occasion de présenter la démarche d&#8217;urbanisation. L&#8217;exemple présenté ne suivait pas le déroulement type : une démarche d&#8217;urbanisation est adaptée suivant les projets et organisations [...]]]></description> <content:encoded><![CDATA[<p>Voici la liste des billets les plus lus sur ce blog en avril, mai et juin :</p><h4><a
href="http://blog.xebia.fr/2008/04/10/urbanisation-pour-les-nuls/">Urbanisation pour les nuls</a></h4><p>Le XKE (<a
href="http://blog.xebia.fr/2008/03/03/un-xke-chez-xebia/">Xebia Knowledge Exchange</a>) de mars a été l&#8217;occasion de présenter la démarche d&#8217;urbanisation. L&#8217;exemple présenté ne suivait pas le déroulement type : une démarche d&#8217;urbanisation est adaptée suivant les projets et organisations auxquels elle est appliquée.<br
/> Le but de ce billet est de dérouler cette démarche de manière simplifiée. Cela nous donnera l&#8217;occasion de définir le vocabulaire employé, et de faire un constat sur cette démarche.<br
/> Nous déroulerons l&#8217;exemple avec une approche TOP-DOWN, c&#8217;est-à-dire que l&#8217;analyse commence par la définition de la stratégie pour descendre ensuite au travers des différentes strates du SI. Pour l&#8217;exemple, nous prendrons le cas d&#8217;une agence de voyages qui achète et vend des voyages.</p><p><a
href="http://blog.xebia.fr/2008/04/10/urbanisation-pour-les-nuls/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/">Les 10 commandements des tests unitaires</a></h4><p>Les tests unitaires ne sont pas qu&#8217;une bonne pratique des méthodes agiles, ils sont un véritable pré-requis à la mise en place d&#8217;un développement itératif.<br
/> Le refactoring et la modification d&#8217;une base de code existante, bien que facilités par les environnements de développement actuels, comportent un évident risque de régression, en partie couvert par les tests unitaires.</p><p>Vous trouverez dans ce billet nos 10 commandements des tests unitaires.</p><p><a
href="http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/05/01/springsource-application-platform-la-breche-dans-java-ee/">SpringSource Application Platform : la brèche dans Java EE</a></h4><p>L&#8217;<a
href="http://www.springsource.com/web/guest/products/suite/applicationplatform">annonce de SpringSource</a> a des allures de schisme. Après des années à critiquer la complexité et le monolithisme de Java Enterprise Edition (Java EE), les équipes de Rod Johnson ont franchi le Rubicon et proposent un serveur d&#8217;applications Java qui ne reposera pas sur la monolithique spécification Java EE.<br
/> SpringSource Application Platform se limite à quelques fragments de cette spécification (principalement Servlet et JPA) assemblés dans un conteneur OSGI augmenté de quelques particularités Spring. Les applications web ne seront plus assemblées sous forme de WAR avec un fichier web.xml mais sous forme de plusieurs bundles OSGI avec des fichiers de configuration Spring.</p><p><a
href="http://blog.xebia.fr/2008/05/01/springsource-application-platform-la-breche-dans-java-ee/">Lire cet article »</a></p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/07/01/ce-que-vous-avez-peut-etre-rate-au-second-trimestre-2008/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Hands on Wicket &#8211; Partie 4</title><link>http://blog.xebia.fr/2008/04/23/hands-on-wicket-partie-4/</link> <comments>http://blog.xebia.fr/2008/04/23/hands-on-wicket-partie-4/#comments</comments> <pubDate>Wed, 23 Apr 2008 07:31:13 +0000</pubDate> <dc:creator>Manuel Eveno</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Web]]></category> <category><![CDATA[Wicket]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/04/23/hands-on-wicket-partie-4/</guid> <description><![CDATA[Dans les précédents articles sur Wicket, nous avons exploré les bases du framework. Nous allons nous intéresser maintenant à quelques fonctionnalités avancées comme le templating ou l&#8217;internationalisation. Les mécanismes de templating Avec Wicket, même le templating est très simple. L&#8217;implémentation du templating dans Wicket est très proche de la façon de faire de SiteMesh (au [...]]]></description> <content:encoded><![CDATA[<p>Dans <a
href="http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/">les</a> <a
href="http://blog.xebia.fr/2008/02/22/hands-on-wicket-partie-2/">précédents</a> <a
href="http://blog.xebia.fr/2008/03/07/hands-on-wicket-partie-3/">articles</a> sur Wicket, nous avons exploré les bases du framework. Nous allons nous intéresser maintenant à quelques fonctionnalités avancées comme le templating ou l&#8217;internationalisation.</p><h3><a
title="Lesmcanismesdetemplating" name="Lesmcanismesdetemplating"></a>Les mécanismes de templating</h3><p>Avec Wicket, même le templating est très simple. L&#8217;implémentation du templating dans Wicket est très proche de la façon de faire de <a
href="http://www.opensymphony.com/sitemesh/" title="SiteMesh">SiteMesh</a> (au moins pour la partie HTML). La mise en œuvre se fait par des mécanismes d&#8217;héritage : Wicket nomme ceci le <a
href="http://cwiki.apache.org/WICKET/markup-inheritance.html" title="Markup Inheritance">Markup Inheritance</a>. Prenons un exemple concret.</p><p>J&#8217;ai une page de template qui va définir le header et le footer commun de mon site. Ensuite le body va varier selon les pages. Je crée donc un fichier HTML et la classe Java associée. Dans le fichier HTML, j&#8217;ai juste besoin de préciser où va s&#8217;insérer le contenu des fils en positionnant la balise <wicket:child>.</wicket:child></p><p>Ce qui donne dans le fichier HTML du parent :</p><pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
&lt;head&gt;
&lt;wicket:head&gt;&lt;/wicket:head&gt;
&lt;/head&gt;
&lt;body&gt;
	&lt;div id=&quot;header&quot;&gt;&lt;!-- Contenu du header --&gt;&lt;/div&gt;
	&lt;div id=&quot;body&quot;&gt;
		&lt;!-- Positionnement du contenu des fils --&gt;
		&lt;wicket:child /&gt;
	&lt;/div&gt;
	&lt;div id=&quot;footer&quot;&gt;&lt;!-- Contenu du footer --&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre><p>La balise &lt;wicket:head&gt; permettra plus tard d&#8217;ajouter des éléments (comme le titre de la page) dans le header de la page à partir des enfants. Cette zone est en quelque sorte une zone modifiable du template.</p><p>La classe Java (ParentPage.java) associée :</p><pre class="brush: java; title: ; notranslate">
package test.view;
// import ...;
public abstract class ParentPage extends WebPage {
	private static final long serialVersionUID = 1L;
	public ParentPage() {
		super();
		createTemplateComponents();
	}
	protected void createTemplateComponents() {
		// Ajout des composants communs dans le header et le footer
	}
}
</pre><p>Pour les pages du fils, je vais, comme d&#8217;habitude en Wicket, définir le fichier HTML et la classe Java associée. La classe Java va hériter de la classe ParentPage (les composants ajoutés et gérés par la page parente seront donc ajoutés à ma page).</p><pre class="brush: java; title: ; notranslate">
package test.view;
// import ...;
public class ChildPage extends ParentPage {
	private static final long serialVersionUID = 1L;
	public ChildPage() {
		super();
		createComponents();
	}
	protected void createComponents() {
		// Ajout des composants propres à cette page
	}
}
</pre><p>Pour ce qui est du fichier HTML, je dois encadrer le code HTML qui doit être inséré dans le code HTML du parent par la balise &lt;wicket:extend&gt;.</p><pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
&lt;wicket:head&gt;
	&lt;title&gt;Page fille !&lt;/title&gt;
&lt;/wicket:head&gt;
&lt;body&gt;
&lt;wicket:extend&gt;
	&lt;!-- Ajout ici du contenu spécifique du fils --&gt;
	&lt;h2 align=&quot;center&quot;&gt;Hello dans le fils&lt;/h2&gt;
&lt;/wicket:extend&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre><p>Cette fois-ci, dans le header de la page et entre les balises &lt;wicket:head&gt;, j&#8217;insère la balise &lt;title&gt;. Ce titre sera inséré dans la page finale rendue à l&#8217;utilisateur. De même, je peux ajouter des imports CSS ou JavaScript propres à la page fille.</p><p>Et voilà, tout ce que j&#8217;ajouterai dans la page parent apparaitra aussi dans les pages filles !</p><h3><a
title="Linternationalisation" name="Linternationalisation"></a>L&#8217;internationalisation</h3><p>L&#8217;internationalisation se fait de la façon habituelle, c&#8217;est à dire via l&#8217;utilisation de fichiers properties nommés en fonction de la langue supportée. Avec Wicket, il est possible d&#8217;adjoindre à chaque composant (page, formulaire, composant &#8216;maison&#8217;) un fichier properties portant le même nom et situé dans le même package que la classe associée.</p><p>Exemple : Le fichier properties contenant les traductions pour la page fr/xebia/wicket/demo/HomePage.java doit être fr/xebia/wicket/demo/HomePage.properties (<a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/resource/loader/ComponentStringResourceLoader.html" title="par défaut">par défaut</a> bien sûr).</p><p>L&#8217;accès aux traductions se fait de trois manières :</p><ul><li>Dans le code HTML, en ajoutant la balise &lt;wicket:message key=&nbsp;&raquo;stringKey&nbsp;&raquo; /&gt;</li><li>Dans le code Java, en utilisant un type de modèle particulier : le <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/StringResourceModel.html" title="StringResourceModel">StringResourceModel</a>. Ce modèle peut être passé en paramètre de tous les composants Wicket (Label, TextField, etc)</li><li>Toujours dans le code Java, en appelant la méthode <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/Component.html#getLocalizer()" title="getLocalizer().getString()">getLocalizer().getString()</a> sur n&#8217;importe quel composant.</li></ul><p>Cependant, Wicket ne s&#8217;est pas arrêté là. Pour la classe StringResourceModel, la clé passée en paramètre peut être dynamique. Exemple : &laquo;&nbsp;product.${product.id}&nbsp;&raquo;. Le modèle passé en paramètre permet de résoudre la clé (dans ce cas, l&#8217;objet Product contient un champ id).</p><p>L&#8217;autre particularité, ici, réside dans le mécanisme de recherche de la traduction. Wicket va utiliser la hiérarchie de classes pour parcourir les fichiers properties.</p><p>Prenons en exemple la hiérarchie suivante :</p><pre class="brush: java; title: ; notranslate">
+ fr/xebia/wicket/BasePage.java
+ fr/xebia/wicket/MyPage.java
</pre><p>Si dans la classe MyPage (ou la page HTML correspondante), je recherche une traduction, Wicket va rechercher la valeur en parcourant dans cet ordre les fichiers properties :</p><ul><li>fr/xebia/wicket/MyPage.properties</li><li>fr/xebia/wicket/BasePage.properties</li><li>org/apache/wicket/markup/html/WebPage.properties</li><li>Et ainsi de suite, en remontant la hiérarchie de classe &#8230;</li></ul><p>Pour les cas où cette façon de faire implique trop de duplication de properties, il est aussi possible de charger arbitrairement des fichiers properties dans la classe principale de l&#8217;application) en configurant les <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/settings/IResourceSettings.html" title="IResourceSettings">IResourceSettings</a>.</p><h3><a
title="Modifierdynamiquementlesattrib" name="Modifierdynamiquementlesattrib"></a>Modifier dynamiquement les attributs des balises HTML</h3><p>Le cas d&#8217;usage typique pour ce genre de besoin est la modification dynamique de l&#8217;attribut &laquo;&nbsp;class&nbsp;&raquo; d&#8217;un tag HTML pour modifier le rendu en jouant avec les CSS. Un autre type d&#8217;utilisation est d&#8217;écrire un appel JavaScript dynamique en définissant par exemple l&#8217;attribut &laquo;&nbsp;onClick&nbsp;&raquo; d&#8217;un bouton.</p><p>Différentes approches sont proposées dans <a
href="http://cwiki.apache.org/WICKET/how-to-modify-an-attribute-on-a-html-tag.html" title="cette page">cette page</a> du Wiki mais nous n&#8217;en retiendrons ici qu&#8217;une seule. Il s&#8217;agit d&#8217;utiliser la classe <a
href="http://wicket.apache.org/docs/wicket-1.3.2/wicket/apidocs/org/apache/wicket/AttributeModifier.html" title="AttributeModifier">AttributeModifier</a>. Cette classe est très simple dans son usage : on ajoute une instance de cette classe à n&#8217;importe quel élément Wicket héritant de Component comme suit :</p><pre class="brush: java; title: ; notranslate">
TextField textField = new TextField(&quot;myField&quot;, new Model());
textField.add(new AttributeModifier(&quot;class&quot;, true, new Model(&quot;formFields&quot;)));
</pre><p>Dans cet exemple, un attribut &laquo;&nbsp;class&nbsp;&raquo; sera ajouté même s&#8217;il n&#8217;est pas présent dans le HTML (signification du paramètre &laquo;&nbsp;true&nbsp;&raquo;). La valeur de l&#8217;attribut est donnée par le modèle passé en 3ème position.</p><p>Il est aussi possible de modifier le comportement par défaut en surchargeant la méthode getObject() :</p><pre class="brush: java; title: ; notranslate">
textField.add(new AttributeModifier(&quot;class&quot;, new Model() {
   public Object getObject(final Component component) {
         String cssClass;
         if ( test.getAlarmState() )
             cssClass = &quot;alarm&quot;;
         } else {
             cssClass=&quot;noAlarm&quot;;
         }
         return cssClass;
   }
 }));
</pre><p>C&#8217;est tout pour cette fois. Vous en savez déjà assez pour commencer votre nouveau projet avec Wicket. D&#8217;autres aspects de Wicket sont à découvrir : avec Wicket, on peut aussi faire de l&#8217;<a
href="http://cwiki.apache.org/WICKET/ajax.html" title="AJAX">AJAX</a>, des <a
href="http://cwiki.apache.org/WICKET/testing-pages.html" title="tests d'IHM">tests d&#8217;IHM</a> et plein d&#8217;<a
href="http://cwiki.apache.org/WICKET/reference-library.html" title="autres choses">autres choses</a>.</p><p>A l&#8217;occasion de ces articles, un projet de démonstration de wicket a été créé et est disponible (sous License Apache 2.0) sous le <a
href="http://code.google.com/p/xebia-france">projet xebia-france</a> hébergé chez GoogleCode. Voici le <a
href="http://code.google.com/p/xebia-france/source/checkout">lien direct</a> vers le repository Subversion.</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/04/23/hands-on-wicket-partie-4/feed/</wfw:commentRss> <slash:comments>1</slash:comments> </item> <item><title>Les 10 commandements des tests unitaires</title><link>http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/</link> <comments>http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/#comments</comments> <pubDate>Fri, 11 Apr 2008 07:13:51 +0000</pubDate> <dc:creator>Guillaume Carre</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Tests unitaires]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/</guid> <description><![CDATA[Les tests unitaires ne sont pas qu&#8217;une bonne pratique des méthodes agiles, ils sont un véritable pré-requis à la mise en place d&#8217;un développement itératif. Le refactoring et la modification d&#8217;une base de code existante, bien que facilités par les environnements de développement actuels, comportent un évident risque de régression, en partie couvert par les [...]]]></description> <content:encoded><![CDATA[<div
style="float: right;"> <img
src="http://blog.xebia.fr/wp-content/uploads/2008/04/10_commandements.jpg" alt="10 commandements" style="margin-left: 5px;"/></div><p>Les tests unitaires ne sont pas qu&#8217;une bonne pratique des méthodes agiles, ils sont un véritable pré-requis à la mise en place d&#8217;un développement itératif.<br
/> Le refactoring et la modification d&#8217;une base de code existante, bien que facilités par les environnements de développement actuels, comportent un évident risque de régression, en partie couvert par les tests unitaires.</p><p>Vous trouverez ci-dessous nos 10 commandements des tests unitaires.</p><div
style="clear:both;"></div><ol><li>Un test unitaire doit être véritablement <strong>unitaire</strong>. La classe testée doit l&#8217;être en isolation complète, afin de ne tester qu&#8217;une classe (et une méthode) à la fois, le SUT (<a
href="http://en.wikipedia.org/wiki/System_Under_Test" title="System Under Test" >System Under Test</a>).</li><li>Si une classe est difficile à tester, il est temps de <strong>faire du refactoring</strong>. Est-elle trop volumineuse ? Présente-t-elle trop de dépendances ? Profitez de l&#8217;occasion pour la découper et déplacer du code dans des classes annexes.</li><li>Un test unitaire doit <strong>s&#8217;exécuter le plus rapidement possible</strong> afin d&#8217;avoir un retour quasi immédiat. Il faut donc proscrire l&#8217;accès à des fichiers, des bases de données ou des services externes dans un test unitaire. Un test qui dialogue avec une base de données n&#8217;est pas un test unitaire, c&#8217;est un <strong>test d&#8217;intégration</strong>.</li><li>Le code d&#8217;un test unitaire fait partie du code applicatif. Il doit donc, à l&#8217;image du reste de l&#8217;application, respecter des conventions de code. <strong>Chouchoutez vos tests unitaires</strong>, faites du refactoring sur leur code, respectez les bonnes pratiques, présentez le code à vos collègues, codez les tests en <a
href="http://en.wikipedia.org/wiki/Pair_Programming" title="pair programming" >pair programming</a>, sans quoi on trouvera certainement dans votre application des <a
href="http://xunitpatterns.com/Test%20Smells.html" title="tests smells" >tests smells</a>, des <strong>tests unitaires peu lisibles et difficilement maintenables</strong>. De bons tests unitaires doivent permettre à leur lecture de comprendre le comportement du SUT.</li><li>Isolez les dépendances de la classe testée grâce à l&#8217;<a
href="http://martinfowler.com/articles/injection.html" title="injection de dépendances" >injection de dépendances</a>.</li><li>Ne testez qu&#8217;un comportement à la fois. Soyez raisonnable et gardez le test simple, lisible et <strong>concentré sur ce comportement</strong>.</li><li>Pensez à utiliser un <a
href="http://www.easymock.org" title="framework de mocks" >framework de mocks</a> pour <strong>injecter les dépendances sous forme de bouchons</strong>. Ces outils permettent de respecter le commandement n°1. Ne bannissez pas pour autant les bouchons codés à la main ; ils peuvent parfois rendre le test unitaire plus simple et plus lisible qu&#8217;avec un mock provenant d&#8217;un framework.</li><li>Identifiez précisément les étapes <strong>setup, exercise, verify, teardown</strong> dans votre code. On retrouve ces quatre étapes dans tout test unitaire.</li><li>Ne vous concentrez pas sur une <strong>couverture de code à 100%</strong>. Ce n&#8217;est qu&#8217;un indicateur technique, qui doit être examiné dans le contexte de l&#8217;application, et qui ne prouve en rien la qualité du code et des tests unitaires.</li><li>Ne développez pas vos tests unitaires <strong>&laquo;&nbsp;plus tard&nbsp;&raquo;</strong>. Si vous n&#8217;utilisez pas l&#8217;approche TDD, développez-les le plus tôt possible.</li></ol><p>Les deux premières règles sont incontournables.</p><p>Ne tester qu&#8217;une classe à la fois facilite la maintenance et la correction des tests unitaires, et permet de se concentrer sur le comportement de la classe à tester. Quand un test unitaire échoue, on sait exactement d&#8217;où provient l&#8217;erreur (elle est soit dans le test unitaire si le comportement du SUT a changé, soit une modification du SUT a cassé son comportement attendu).</p><p>Si vous développez votre code sans penser aux tests unitaires que vous ferez <strong>&laquo;&nbsp;plus tard&nbsp;&raquo;</strong>, il sera difficilement testable et vous perdrez un temps précieux pour corriger les erreurs de design. <strong>&laquo;&nbsp;Design for testability&nbsp;&raquo;</strong>, en particulier, utilisez au maximum l&#8217;injection de dépendances, qui est obligatoire pour l&#8217;injection des bouchons. Si vous utilisez Spring ou un autre framework d&#8217;injection de dépendances, cela ne devrait pas être trop compliqué &#8230;</p><p>Pour aller plus loin, nous vous recommandons vivement la bible <a
href="http://www.amazon.fr/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054/ref=sr_1_1?ie=UTF8&#038;s=english-books&#038;qid=1207388575&#038;sr=8-1" title="xUnit Test Patterns" >xUnit Test Patterns</a>, de Gerard Meszaros, dans laquelle vous retrouverez une présentation sur l&#8217;automatisation des tests, un catalogue de &laquo;&nbsp;test smells&nbsp;&raquo;, et une liste complète de &laquo;&nbsp;test patterns&nbsp;&raquo;, des meilleures pratiques de tests unitaires qui ont émergé ces dernières années.</p><p>Enfin, vous trouverez également sur les blogs suivants une liste de commandements:</p><ul><li><a
href="http://monkeyisland.pl/2008/01/31/10rules/" title="10 rules of (unit) testing" >10 rules of (unit) testing</a> de Szczepan Faber, auteur du nouveau framework de Mocks qui monte, <a
href="http://code.google.com/p/mockito/" title="Mockito">Mockito</a></li><li><a
href="http://codeforfun.wordpress.com/2007/11/07/ten-tdd-commandments/" title="Ten T.D.D. Commandments" >Ten T.D.D. Commandments</a> de Cliff</li><li><a
href="http://curious-attempt-bunny.blogspot.com/2007/11/ten-commandments-of-unit-testing.html" title="Ten commandments of unit testing" >Ten commandments of unit testing</a> de Merlyn Albery-Speyer</li><li><a
href="http://docs.codehaus.org/display/ASH/Home" title="Ashcroft - Ten Commandments of Unit Tests" >Ashcroft &#8211; Ten Commandments of Unit Tests</a> de Aslak Helles&oslash;y et Shane Duan, une des premières tentatives sur le sujet, hélas interrompue après le quatrième commandement.</li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/04/11/les-10-commandements-des-tests-unitaires/feed/</wfw:commentRss> <slash:comments>3</slash:comments> </item> <item><title>Ce que vous avez peut-être raté au premier trimestre 2008</title><link>http://blog.xebia.fr/2008/04/03/ce-que-vous-avez-peut-etre-rate-au-premier-trimestre-2008/</link> <comments>http://blog.xebia.fr/2008/04/03/ce-que-vous-avez-peut-etre-rate-au-premier-trimestre-2008/#comments</comments> <pubDate>Thu, 03 Apr 2008 16:12:35 +0000</pubDate> <dc:creator>Xebia France</dc:creator> <category><![CDATA[Divers]]></category> <category><![CDATA[Agent]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[eXtreme-Programming]]></category> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[SCRUM]]></category> <category><![CDATA[TDD]]></category> <category><![CDATA[Test-Driven-Development]]></category> <category><![CDATA[XP]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/04/03/ce-que-vous-avez-peut-etre-rate-au-premier-trimestre-2008/</guid> <description><![CDATA[Voici la liste des billets les plus lus sur ce blog en janvier, février et mars : Scrum ou XP ? Scrum ET XP ! Lors des Rencontres Agiles, que nous avons co-organisées en décembre dernier &#8211; et qui, soit dit en passant, ont rencontré un fort et encourageant succès -, plusieurs personnes m&#8217;ont demandé [...]]]></description> <content:encoded><![CDATA[<p>Voici la liste des billets les plus lus sur ce blog en janvier, février et mars :</p><h4><a
href="http://blog.xebia.fr/2008/01/10/scrum-ou-xp-scrum-et-xp/">Scrum ou XP ? Scrum ET XP !</a></h4><p>Lors des <a
href="http://www.rencontresagiles2007.com/">Rencontres Agiles</a>, que nous avons co-organisées en décembre dernier &#8211; et qui, soit dit en passant, ont rencontré un fort et encourageant succès -, plusieurs personnes m&#8217;ont demandé si, pour démarrer un projet agile, il était préférable de choisir eXtreme Programming (XP) ou Scrum. La réponse est simple : il faut adopter les deux !<br
/> La complémentarité de Scrum et XP est communément admise. Scrum se positionne au niveau de la gestion et de l’organisation de projet là où XP se positionne au niveau des activités de développement. C’est la raison pour laquelle ces deux approches fonctionnent si bien ensemble : elles adressent des problématiques différentes et se complètent mutuellement.</p><p>Cet article propose une présentation sommaire de Scrum et de la façon dont elle (il? non, allez, c&#8217;est plutôt une fille) s&#8217;articule avec eXtreme Programming&#8230; Bonne lecture !</p><p><a
href="http://blog.xebia.fr/2008/01/10/scrum-ou-xp-scrum-et-xp/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/">L’analyse de couverture de code en Java</a></h4><p>Il ne reste plus grand&#8217;monde pour soutenir que l&#8217;écriture de tests unitaires automatisés est une perte de temps sur un projet logiciel &#8211; la notion de dette technique entre dans les moeurs. Cette prise de conscience salutaire se heurte pourtant souvent à deux grandes catégories de difficultés :</p><ul><li>Le conservatisme &#8211; pour ne pas dire la stupidité &#8211; de certains chefs de projet, qui persistent à voir dans l&#8217;écriture des tests une activité contre-productive, qui servira de variable d&#8217;ajustement au moindre coup de grisou</li><li>L&#8217;existant : quand le projet n&#8217;a pas systématisé la pratique du test dès son origine et que la multiplication des anomalies tardives le pousse à la réintroduire. Par où commencer&nbsp;?</li></ul><p>Pour le premier point, la solution passe par un effort pédagogique, ou, pour les cas désespérés, par une reconversion imposée ou un congé sabbatique.<br
/> Pour le second, il faut un peu de bon sens, et un peu d&#8217;outillage.</p><p>C&#8217;est sur le deuxième aspect qu&#8217;interviennent les outils d&#8217;analyse de <strong>couverture de code</strong> (ou &laquo;&nbsp;<strong>code coverage</strong>&nbsp;&raquo; en anglais). Dans la suite, nous verrons que ces outils ne permettent pas d&#8217;évaluer les tests unitaires d&#8217;un point de vue qualitatif, mais qu&#8217;ils peuvent en revanche apporter au bon sens un précieux appui en répondant aux questions suivantes :</p><ul><li>Quels sont les tests unitaires déjà en place&nbsp;?</li><li>Les tests sont-ils en phase (à jour) avec le code qu&#8217;ils testent&nbsp;?</li><li>Les fonctions critiques de l&#8217;application sont-elles couvertes par les tests&nbsp;?</li></ul><p><a
href="http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/">Lire cet article »</a></p><h4><a
href="http://blog.xebia.fr/2008/05/02/java-agent-instrumentez-vos-classes/">Un nouveau type d’architecte: l’Architecte Agile</a></h4><p>On observe ces derniers temps de multiples débats sur le fait de savoir si une équipe agile a besoin d&#8217;un architecte en son sein, et sur la valeur ajoutée de ce dernier sur un projet agile alors que l&#8217;architecture évolue à chaque itération.<br
/> Les architectes « Tour d&#8217;Ivoire » se révèlent petit à petit être le maillon faible des projets agiles. Les responsabilités des architectes traditionnels sont distribuées ci et là au sein des équipes agiles, leur supprimant au passage des tâches qui leur étaient auparavant attribuées.<br
/> Une nouveau genre est en train d&#8217;apparaître et de sortir du lot comme le prédit la théorie de l&#8217;évolution de Charles Darwin &#8211; question d&#8217;adaptation. Le rôle d&#8217;un Architecte Agile ne doit pas susciter de questions et tout membre d&#8217;une équipe agile vous le confirmera: ce sont des équipiers qui apportent l&#8217;une des plus grandes valeurs ajoutées.</p><p>Alors qui est cet Architecte Agile ? Comment savoir si l&#8217;architecte de votre équipe est un Architecte Agile ?</p><p><a
href="http://blog.xebia.fr/2008/05/02/java-agent-instrumentez-vos-classes/">Lire cet article »</a></p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/04/03/ce-que-vous-avez-peut-etre-rate-au-premier-trimestre-2008/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Hands on Hibernate Search : Recherche full-text</title><link>http://blog.xebia.fr/2008/03/28/hands-on-hibernate-search-recherche-full-text/</link> <comments>http://blog.xebia.fr/2008/03/28/hands-on-hibernate-search-recherche-full-text/#comments</comments> <pubDate>Fri, 28 Mar 2008 07:29:48 +0000</pubDate> <dc:creator>Christophe Heubès</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Hibernate]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/03/28/hands-on-hibernate-search-recherche-full-text/</guid> <description><![CDATA[Comme nous l&#8217;avions vu dans notre précédent billet (&#171;&#160;Introduction à Hibernate Search (Googling your Persistent Domain Model)&#171;&#160;), Hibernate Search vise à réconcilier la recherche full-text et les modèles de persistance relationnels. Pour ce faire, Hibernate Search se base sur Apache Lucene, un moteur d’indexation et de recherche full-text standalone très puissant et permet ainsi d’utiliser [...]]]></description> <content:encoded><![CDATA[<p>Comme nous l&#8217;avions vu dans notre précédent billet <em>(&laquo;&nbsp;<a
href="http://blog.xebia.fr/2008/03/06/introduction-a-hibernate-search-googling-your-persistent-domain-model/">Introduction à Hibernate Search (Googling your Persistent Domain Model)</a>&laquo;&nbsp;)</em>, Hibernate Search vise à réconcilier la recherche full-text et les modèles de persistance relationnels.<br
/> Pour ce faire, Hibernate Search se base sur <a
href="http://lucene.apache.org">Apache Lucene</a>, un moteur d’indexation et de recherche full-text standalone très puissant et permet ainsi d’utiliser ses capacités dans le cadre d’une couche de mapping Hibernate.</p><p>Ce billet présente, au travers d&#8217;un exemple simple, les capacités de recherche full-text d&#8217;Hibernate Search.<br
/> L&#8217;exemple proposé permet l&#8217;indexation et la recherche de documents auxquels sont attachés des auteurs.<br
/> Vous retrouverez l&#8217;ensemble des sources présentées dans ce billet dans le repository <a
href="http://code.google.com/p/xebia-france/source/browse/trunk/search/hibernate-search-demo/">SVN de Xebia France</a>.</p><h3>Structuration du projet et démarrage</h3><p>Le projet utilise maven. Une fois les sources récupérées depuis notre dépot SVN, vous pouvez donc générer une configuration Eclipse à l&#8217;aide de la commande :</p><pre class="brush: bash; title: ; notranslate">
mvn eclipse:eclipse
</pre><p>Le projet déclare des dépendances vers :</p><ul><li>hibernate-search en version 3.0.0.ga.</li><li>hibernate-annotations en version 3.3.0.ga.</li><li>hibernate-entitymanager en version 3.3.1.ga.</li><li>lucene-analyzers en version 2.3.1.</li></ul><pre class="brush: xml; title: ; notranslate">
&lt;dependency&gt;
	&lt;groupId&gt;org.hibernate&lt;/groupId&gt;
	&lt;artifactId&gt;hibernate-search&lt;/artifactId&gt;
	&lt;version&gt;3.0.0.ga&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
	&lt;groupId&gt;org.hibernate&lt;/groupId&gt;
	&lt;artifactId&gt;hibernate-annotations&lt;/artifactId&gt;
	&lt;version&gt;3.3.0.ga&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
	&lt;groupId&gt;org.hibernate&lt;/groupId&gt;
	&lt;artifactId&gt;hibernate-entitymanager&lt;/artifactId&gt;
	&lt;version&gt;3.3.1.ga&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
	&lt;groupId&gt;org.apache.lucene&lt;/groupId&gt;
	&lt;artifactId&gt;lucene-analyzers&lt;/artifactId&gt;
	&lt;version&gt;2.3.1&lt;/version&gt;
&lt;/dependency&gt;
</pre><p>Le projet est structuré sur l&#8217;arborescence classique maven :</p><ul><li><code>src\main\java</code> contient les sources du projet :<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Le package <code>fr.xebia.demo.hibernate.search.model</code> contient les entités persistées qui seront mappées dans un index Lucene.<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Le package <code>fr.xebia.demo.hibernate.search.analysis</code> contient un analyseur de langue anglaise.</li><li><code>src\main\test</code> contient les sources des tests. Les tests &laquo;&nbsp;unitaires&nbsp;&raquo; ont ici été détournés pour faire la démonstration des capacités d&#8217;indexation et de recherche full-text d&#8217;Hibernate Search.</li></ul><h3>Mapping des entités sur l&#8217;index</h3><p>Le mapping des entités dans un index Lucene se fait par annotation :</p><ul><li>L&#8217;annotation <strong><code>@Indexed</code></strong> déclare une classe comme indexable</li><li>L&#8217;annotation <strong><code>@Analyzer</code></strong> définit l&#8217;implémentation d&#8217;analyseur syntaxique à utiliser pour cette indexation :</li></ul><pre class="brush: java; title: ; notranslate">
@Entity
@Indexed
@Analyzer(impl = SimpleEnglishAnalyzer.class)
@Table(name = DOCUMENT_TABLE_NAME)
public class Document {
</pre><ul><li>L&#8217;annotation <strong><code>@DocumentId</code></strong> indique l&#8217;attribut à utiliser en tant qu&#8217;identifiant. Cet identifiant est utilisé par Hibernate Search afin d&#8217;assurer l&#8217;unicité des entités dans l&#8217;index :</li></ul><pre class="brush: java; title: ; notranslate">
@Id
@DocumentId
@Column(name = &quot;id&quot;, nullable = false)
public Long getId() {
	return this.id;
}
</pre><ul><li>L&#8217;annotation <strong><code>@Field</code></strong> marque un attribut comme devant être indexé. La propriété <code>index = Index.TOKENIZED</code> indique que le texte va être &laquo;&nbsp;tokenisé&nbsp;&raquo; via l&#8217;analyseur définit au niveau de la classe. La propriété <code>store = Store.NO</code> indique que le texte de l&#8217;attribut ne sera pas stocké dans l&#8217;index.</li></ul><pre class="brush: java; title: ; notranslate">
@Column(name = &quot;title&quot;)
@Field(index = Index.TOKENIZED, store = Store.NO)
public String getTitle() {
	return title;
}
@Column(name = &quot;summary&quot;)
@Field(index = Index.TOKENIZED, store = Store.NO)
public String getSummary() {
	return summary;
}
</pre><ul><li>Dans le cadre d&#8217;une association, l&#8217;annotation <strong><code>@IndexedEmbedded</code></strong> indique que l&#8217;objet associé doit être indexé dans l&#8217;index de l&#8217;entité racine. Dans notre exemple, cette méthode permet d&#8217;effectuer une recherche full-text sur les entités de type <code>Document</code> basée sur les propriétés de son attribut <code>author</code> :</li></ul><pre class="brush: java; title: ; notranslate">
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE,
		CascadeType.REFRESH }, fetch = FetchType.EAGER)
@JoinColumn(name = &quot;author_id&quot;)
@Fetch(FetchMode.JOIN)
@ForeignKey(name = &quot;fk_document_author_id&quot;)
@IndexedEmbedded
public Author getAuthor() {
	return author;
}
</pre><h4>Analyseur et tokeniseur</h4><p>La &laquo;&nbsp;tokenisation&nbsp;&raquo; consiste à découper un texte en mots. Ce découpage se fait en suivant les règles définies dans un analyseur. Hibernate Search propose par défaut l&#8217;utilisation de <code>org.apache.lucene.analysis.standard.StandardAnalyzer</code>.<br
/> Il est possible de définir son propre analyseur en étendant la classe abstraite <code>Analyzer</code> dont il faut implémenter la méthode</p><pre class="brush: java; title: ; notranslate">
public abstract TokenStream tokenStream(String fieldName, Reader reader);
</pre><p>C&#8217;est ce que propose la classe <code>SimpleEnglishAnalyzer</code> :</p><pre class="brush: java; title: ; notranslate">
@Override
public final TokenStream tokenStream(String fieldName, Reader reader) {
	if (fieldName == null)
		throw new IllegalArgumentException(&quot;fieldName must not be null&quot;);
	if (reader == null)
		throw new IllegalArgumentException(&quot;reader must not be null&quot;);
	TokenStream result = new StandardTokenizer(reader);
	result = new StandardFilter(result);
	result = new LowerCaseFilter(result);
	result = new StopFilter(result, stopTable);
	result = new PorterStemFilter(result);
	return result;
}
</pre><p>Cette implémentation passe les mots en minuscule, supprime les mots courants, et utilise le <code>PorterStemFilter</code> pour supprimer les terminaisons morphologiques communes.</p><h3>Configuration Hibernate Search</h3><p>La configuration d&#8217;Hibernate Search est câblée sur le système d&#8217;écoute d&#8217;événements. Elle est donc transparente avec Hibernate Annotations.<br
/> Dans le cadre de notre exemple, la configuration Hibernate est réalisée dans le set up de la classe <code>HibernateSearchDemoTestCase</code>. La seule propriété de configuration que nous spécifions pour Hibernate Search est le répertoire de stockage des index :</p><pre class="brush: java; title: ; notranslate">
@Override
protected void setUp() throws Exception {
	AnnotationConfiguration cfg = new AnnotationConfiguration();
	[...]
	cfg.setProperty(&quot;hibernate.search.default.indexBase&quot;, &quot;target&quot;);
	[...]
}
</pre><h3>Création et indexation des données de test</h3><p>La création des données de test se fait dans la méthode <code>populateDB</code> de la classe de test <i>(<code>HibernateSearchDemoTestCase</code>)</i>. L&#8217;indexation des données de test est faite de manière transparente au moment du commit de la transaction :</p><pre class="brush: java; title: ; notranslate">
Transaction tx = this.testSession.beginTransaction();
Author bTate = new Author(
		&quot;Bruce&quot;,
		&quot;Tate&quot;,
		&quot;Bruce A. Tate is a [...]&quot;);
[...]
Document beyondJava = new Document();
beyondJava.setTitle(&quot;Beyond Java&quot;);
beyondJava
		.setSummary(&quot;In Beyond Java, [...]&quot;);
beyondJava.setAuthor(bTate);
this.testSession.persist(beyondJava);
[...]
tx.commit();
</pre><h3>Recherches full-text sur les entités</h3><p>La recherche full-text de documents dans l&#8217;index se fait au travers de la méthode <code>searchDocuments</code> de la classe de test <i>(<code>HibernateSearchDemoTestCase</code>)</i> :</p><pre class="brush: java; title: ; notranslate">
private List&lt;Document&gt; searchDocuments(String queryString) {
	FullTextSession searchSession = Search
			.createFullTextSession(this.testSession);
	Transaction tx = searchSession.beginTransaction();
	MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {
			&quot;title&quot;, &quot;summary&quot;, &quot;author.lastName&quot;, &quot;author.firstName&quot;,
			&quot;author.resume&quot; }, new SimpleEnglishAnalyzer());
	FullTextQuery query;
	try {
		query = searchSession.createFullTextQuery(
				parser.parse(queryString), Document.class);
	} catch (ParseException pe) {
		logger.error(&quot;Error while parsing query.&quot;, pe);
		return new ArrayList&lt;Document&gt;(0);
	}
	List&lt;Document&gt; result = query.list();
	tx.commit();
	return result;
}
</pre><p>Pour effectuer une recherche full-text, il faut :</p><ul><li>Créer une <code>FullTextSession</code> à partir de la session Hibernate.</li><li>Créer un parseur (<code>MultiFieldQueryParser</code>) en lui indiquant les champs sur lesquels la recherche doit avoir lieu et l&#8217;analyseur à utiliser pour tokeniser les chaînes de recherche <i>(il est bien évidemment préférable d&#8217;utiliser le même que pour l&#8217;indexation des documents)</i>.</li><li>Créer une requête de recherche full-text à partir de la chaîne de recherche tokenisée.</li><li>Exécuter la méthode <code>list</code> sur la requête.</li></ul><p>Hibernate Search retourne alors une liste d’entités &laquo;&nbsp;managées&nbsp;&raquo;.</p><h4>Les cas de test</h4><p>La démonstration peut être lancée via maven avec la commande :</p><pre class="brush: bash; title: ; notranslate">
mvn test
</pre><p>Les cas de test proposés sont :</p><ul><li>Recherches simples (<code>"java"</code>, <code>"father"</code>, <code>"JAVA"</code>):</li></ul><pre class="brush: bash; title: ; notranslate">
=====================================================
Simple Test (query == &quot;java&quot;):
Results list:
Document 1: Beyond Java:
   &gt; Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   &gt; Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   &gt; Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   &gt; Author: Russell Miles (3)
             &gt; Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
=====================================================
Simple Test (query == &quot;father&quot;):
Results list:
Document 1: Beyond Java:
   &gt; Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   &gt; Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
=====================================================
=====================================================
Simple Case Test (query == &quot;JAVA&quot;):
Results list:
Document 1: Beyond Java:
   &gt; Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   &gt; Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   &gt; Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   &gt; Author: Russell Miles (3)
             &gt; Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
</pre><ul><li>Recherche par approximation (<code>"jav~"</code>):</li></ul><pre class="brush: bash; title: ; notranslate">
=====================================================
Approximation Test (query == &quot;jav~&quot;):
Results list:
Document 1: Beyond Java:
   &gt; Summary: In Beyond Java, Bruce Tate, author of the Jolt Award-winning Better, Faster, Lighter Java, chronicles the rise of the most  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 2: From Java to Ruby:
   &gt; Summary: If you're trying to adopt Ruby in your organization and need some help, this is the book for you. Based on a decision tree  ...
   &gt; Author: Bruce Tate (1)
             &gt; Resume: Bruce A. Tate is a kayaker, mountain biker, and father of two. In his spare time, he is an independent consultant in Austin ...
Document 4: AspectJ Cookbook:
   &gt; Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   &gt; Author: Russell Miles (3)
             &gt; Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
</pre><ul><li>Recherche par proximité (<code>""engineer UK"~5"</code>):</li></ul><pre class="brush: bash; title: ; notranslate">
=====================================================
Proximity Test (query == &quot;&quot;engineer UK&quot;~5&quot;):
Results list:
Document 4: AspectJ Cookbook:
   &gt; Summary: This hands-on book shows readers why and how common Java development problems can be solved by using new Aspect-oriented pr ...
   &gt; Author: Russell Miles (3)
             &gt; Resume: Russell Miles is a software engineer for General Dynamics UK where he works with Java and Distributed Systems, although his ...
=====================================================
</pre>]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/03/28/hands-on-hibernate-search-recherche-full-text/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>Hands on Wicket &#8211; Partie 3</title><link>http://blog.xebia.fr/2008/03/07/hands-on-wicket-partie-3/</link> <comments>http://blog.xebia.fr/2008/03/07/hands-on-wicket-partie-3/#comments</comments> <pubDate>Fri, 07 Mar 2008 10:35:15 +0000</pubDate> <dc:creator>Manuel Eveno</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Web]]></category> <category><![CDATA[Wicket]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/03/07/hands-on-wicket-partie-3/</guid> <description><![CDATA[Wicket est un framework plutôt en rupture avec les frameworks web actuels. Ici pas de fichier XML qui définit la navigation, pas de librairies de tag spécifiques, juste de la simplicité et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas mélanger les genres : les pages se font en [...]]]></description> <content:encoded><![CDATA[<p><a
href="http://wicket.apache.org/">Wicket</a> est un framework plutôt en rupture avec les frameworks web actuels. Ici pas de fichier XML qui définit la navigation, pas de librairies de tag spécifiques, juste de la simplicité et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas mélanger les genres : les pages se font en plain html et le développement, à proprement parler, se fait en pur Java.</p><p>Enfonçons-nous un peu dans les mécanismes du framework. Cette semaine, étudions la gestion de la session, la validation de formulaires et l&#8217;intégration (toujours aussi simple) avec SpringFramework.</p><h3>La gestion de la session</h3><p>Le framework s&#8217;occupe déjà de mettre en session la page courante et les composants Wicket inclus dans celle-ci. Ce mécanisme permet notamment d&#8217;écrire les traitements de réponse aux événements à la Swing (onClick, onSubmit). Il est cependant très important de faire attention aux objets que l&#8217;on met en attribut des pages et composants pour éviter de voir grossir la taille de la session.</p><p>Mais Wicket ne met en session que la page courante. Or, dans bien des cas, il est nécessaire de pouvoir accéder et manipuler des données de la session afin de rendre certaines données accessibles au travers d&#8217;un enchainement de pages. Wicket nous permet bien sûr d&#8217;accéder à la session mais nous oblige à le faire proprement.</p><p>A partir de n&#8217;importe quel composant, il est possible d&#8217;obtenir la session en utilisant la méthode <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/Component.html#getSession()">getSession()</a> qui renvoit un objet <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/Session.html">Session</a>, wrapper Wicket de la véritable session HTTP. On s&#8217;empresse donc de regarder les méthodes de cette session et on s&#8217;aperçoit que les méthodes get/setAttribute() sont &laquo;&nbsp;protected&nbsp;&raquo; et donc inaccessibles. Comment faire donc ?</p><p>Wicket nous invite en réalité à hériter de la classe WebSession pour proposer des méthodes explicites pour manipuler la session. L&#8217;avantage de cette méthode est la lisibilité et la limitation des erreurs dues à la manipulation de la session via les méthodes génériques habituelles et des clés de type String. Voici donc un exemple de session personnalisée :</p><pre class="brush: java; title: ; notranslate">
public class ShoppingWebSession extends WebSession {
	private static final long serialVersionUID = 1L;
	private static final String CART_KEY = &quot;cart&quot;;
	public XkeWebSession(Request request) {
		super(request);
	}
	public void setCart(Cart cart) {
		setAttribute(CART_KEY, cart);
	}
	public Cart getCart() {
		Cart cart = (Cart) getAttribute(CART_KEY);
		return cart;
	}
}
</pre><p>Ensuite, il faut dire à Wicket d&#8217;utiliser cette classe de session à la place de celle par défaut. Souvenez-vous de la classe principale dont je vous ai parlé dans un <a
href="http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/">précédent article</a>. C&#8217;est dans cette classe que nous allons redéfinir la classe qui représentera la session. Il suffit de redéfinir la méthode newSession(Request, Response) de la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/Application.html#newSession(org.apache.wicket.Request,%20org.apache.wicket.Response)">Application</a> pour proposer notre classe de session.</p><pre class="brush: java; title: ; notranslate">
@Override
public Session newSession(Request request, Response response) {
	return new ShoppingWebSession(request);
}
</pre><p>Et voila ! Wicket utilisera désormais votre classe pour gérer la session HTTP.</p><p>La bonne nouvelle, ce genre de ligne disparait de notre code :</p><pre class="brush: java; title: ; notranslate">
((Cart) request.getSession().getAttribute(&quot;cart&quot;));
</pre><p>La mauvaise, lors de l&#8217;utilisation de notre session, il faut effectuer un &laquo;&nbsp;cast&nbsp;&raquo; :</p><pre class="brush: java; title: ; notranslate">
((ShoppingSession) getSession()).getCart();
</pre><p>Malgré cela, l&#8217;accès à la session ne se fait plus via des String mais au travers de méthodes Java qui doivent compiler, ce qui évite bien des erreurs.</p><p>Il est aussi possible d&#8217;utiliser des Helpers ou de créer une base de composants fournissant des méthodes pour accéder sans &laquo;&nbsp;cast&nbsp;&raquo; à la session.</p><h3>La validation de formulaire</h3><p>En ce qui concerne les champs requis, la procédure est très simple : on les définit avec la méthode <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/form/FormComponent.html#setRequired(boolean)">setRequired(boolean)</a> disponible sur la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/form/FormComponent.html">FormComponent</a> (dont héritent quasiment tous les champs de formulaire).</p><pre class="brush: java; title: ; notranslate">
TextField textField = new TextField(&quot;myRequiredField&quot;, new Model());
textField.setRequired(true);
</pre><p>Pour la validation plus avancée de valeur, il faut utiliser les validators qui implémentent l&#8217;interface <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/validation/IValidator.html">IValidator</a>. On ajoute ensuite aux éléments du formulaire les validations nécessaires avec la méthode add(IValidator). Wicket fournit en standard quelques validators disponibles dans le package <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/validation/validator/package-frame.html">org.apache.wicket.validation.validator</a> (email, date, number, string, etc). Il est bien sûr possible de créer ses propres validateurs en implémentant soi-même l&#8217;interface IValidator.</p><p>Exemple :</p><pre class="brush: java; title: ; notranslate">
TextField textField = new TextField(&quot;myRequiredField&quot;, new Model());
textField.add(NumberValidator.range(10L, 99L)); // Valide un entier compris entre 10 et 99
</pre><p>Pour effectuer des validations entre les éléments du formulaire (par exemple, si tel champ vaut telle valeur alors tel autre champ doit &#8230;.), il faut se tourner cette fois-ci vers l&#8217;interface <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/form/validation/IFormValidator.html">IFormValidator</a> ou la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/form/validation/AbstractFormValidator.html">AbstractFormValidator</a>. Quelques implémentations de base sont disponibles mais ce genre de validation reste très spécifique aux besoins de l&#8217;application.</p><p>Encore une fois, pas de fichier XML fastidieux à définir, juste du bon vieux Java&nbsp;:-) .</p><h3>Intégration avec SpringFramework</h3><p>Pour utiliser Spring avec Wicket, il faut ajouter l&#8217;extension wicket-spring et wicket-spring-annot à votre projet en ajoutant la dépendance dans votre pom.xml.</p><p>Du point de vue de la configuration Spring, il suffit de déclarer la classe principale de l&#8217;application en tant que bean avec un scope singleton.</p><pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
	xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
	default-autowire=&quot;no&quot;
	xsi:schemaLocation=&quot;
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&quot;&gt;
	&lt;bean id=&quot;wicketApplication&quot; class=&quot;fr.xebia.view.WicketApplication&quot; scope=&quot;singleton&quot;/&gt;
	&lt;!-- Définition des autres beans --&gt;
	[.........]
&lt;/beans&gt;
</pre><p>On modifie le Servlet Filter dans le web.xml, en spécifiant cette fois-ci, la propriété &laquo;&nbsp;applicationFactoryClassName&nbsp;&raquo;. Cette propriété pointe vers la factory Spring qui permet l&nbsp;&raquo;intégration des deux frameworks :</p><pre class="brush: xml; title: ; notranslate">
&lt;filter&gt;
	&lt;filter-name&gt;Application&lt;/filter-name&gt;
	&lt;filter-class&gt;org.apache.wicket.protocol.http.WicketFilter&lt;/filter-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;applicationFactoryClassName&lt;/param-name&gt;
            &lt;param-value&gt;org.apache.wicket.spring.SpringWebApplicationFactory&lt;/param-value&gt;
        &lt;/init-param&gt;
&lt;/filter&gt;
</pre><p>Puis dans la méthode init() de la classe principale de l&#8217;application (rappelez-vous celle qui hérite de WebApplication), il faut ajouter une ligne pour que l&#8217;injection de dépendance fonctionne pour les pages Wicket :</p><pre class="brush: java; title: ; notranslate">
addComponentInstantiationListener(new SpringComponentInjector(this));
</pre><p>Ce <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/spring/injection/annot/SpringComponentInjector.html">SpringComponentInjector</a> va utiliser la classe <a
href="http://static.springframework.org/spring/docs/2.0.8/api/org/springframework/web/context/support/WebApplicationContextUtils.html">WebApplicationContextUtils</a> de Spring pour retrouver le contexte Spring (ceci sous-entend que vous ayez configuré un <a
href="http://static.springframework.org/spring/docs/2.0.8/api/org/springframework/web/context/ContextLoaderListener.html">ContextLoaderListener</a> Spring dans le web.xml par exemple pour charger la déclaration des beans gérés par Spring). Si vous voulez charger le contexte Spring vous-même, il est aussi possible de passer le contexte Spring en paramètre du constructeur de SpringComponentInjector.</p><p>Enfin dans le code Java, il nous suffit d&#8217;ajouter l&#8217;annotation <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/spring/injection/annot/SpringBean.html">@SpringBean</a> devant les attributs des composants (Page, Form, etc.) pour lesquels il faut injecter des beans gérés par Spring.</p><pre class="brush: java; title: ; notranslate">
@SpringBean(name=&quot;mySpringService&quot;)
private MyService myService;
</pre><p>Et voila, rien de plus !</p><p>La prochaine fois, nous verrons la mise en œuvre des mécanismes de templating, l&#8217;internationalisation ainsi que la façon d&#8217;écrire des tests unitaires (ce qui tient à cœur aux équipes de Xebia) !</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/03/07/hands-on-wicket-partie-3/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>Introduction à Hibernate Search (Googling your Persistent Domain Model)</title><link>http://blog.xebia.fr/2008/03/06/introduction-a-hibernate-search-googling-your-persistent-domain-model/</link> <comments>http://blog.xebia.fr/2008/03/06/introduction-a-hibernate-search-googling-your-persistent-domain-model/#comments</comments> <pubDate>Thu, 06 Mar 2008 07:00:42 +0000</pubDate> <dc:creator>Christophe Heubès</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Hibernate]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/03/06/introduction-a-hibernate-search-googling-your-persistent-domain-model/</guid> <description><![CDATA[J&#8217;ai eu l&#8217;occasion courant décembre de rencontrer Emmanuel Bernard [1] pour, entre autres, une présentation d&#8217;Hibernate Search. La vulgarisation et la généralisation de l&#8217;utilisation des moteurs de recherche ont définitivement changé les habitudes et les exigences des utilisateurs. Pourquoi la fonctionnalité de recherche d&#8217;une application de gestion ne serait-elle pas aussi simple et performante que [...]]]></description> <content:encoded><![CDATA[<p>J&#8217;ai eu l&#8217;occasion courant décembre de rencontrer <a
href="http://blog.emmanuelbernard.com/">Emmanuel Bernard</a> [1] pour, entre autres, une présentation d&#8217;Hibernate Search.</p><p>La vulgarisation et la généralisation de l&#8217;utilisation des moteurs de recherche ont définitivement changé les habitudes et les exigences des utilisateurs. Pourquoi la fonctionnalité de recherche d&#8217;une application de gestion ne serait-elle pas aussi simple et performante que Google ?<br
/> Difficile d&#8217;expliquer à son responsable que cela est dû à une divergence de paradigme entre l&#8217;indexation documentaire et SQL.<br
/> <a
href="http://search.hibernate.org/">Hibernate Search</a> vise à répondre à cette question, à moindre coût, en s&#8217;appuyant sur <a
href="http://lucene.apache.org">Apache Lucene</a> afin d&#8217;offrir au travers du modèle de persistance d&#8217;Hibernate des capacités de recherche full-text.</p><h3>Réconcilier la recherche full-text et les modèles de persistance relationnels</h3><p>Par nature, SQL nous limite à l&#8217;écriture de requête de recherche par mot <em>(par terme)</em> avec l&#8217;aide éventuelle de wildcard (&#8216;%&#8217;, &#8216;?&#8217;).<br
/> La recherche SQL sur un modèle relationnel n&#8217;offre pas <em>(ou en tout cas pas sans de très gros efforts)</em> un ensemble de capacités dignes d&#8217;un moteur de recherche moderne :</p><ul><li>L&#8217;<strong>approximation</strong> qui permet de s&#8217;affranchir des fautes de frappe (Ex : &laquo;&nbsp;xebi&nbsp;&raquo; -&gt; &laquo;&nbsp;xebia&nbsp;&raquo;).</li><li>La <strong>recherche par synonyme</strong> qui permet de remonter des résultats pertinents mais ne contenant pas le terme recherché (En SQL, &laquo;&nbsp;JEE&nbsp;&raquo; est différent de &laquo;&nbsp;J2EE&nbsp;&raquo;).</li><li>La <strong><a
href="http://en.wikipedia.org/wiki/Proximity_search_%28text%29">recherche par proximité</a></strong> (Ex : &laquo;&nbsp;persistance java&nbsp;&raquo; -&gt; &laquo;&nbsp;Hibernate&nbsp;&raquo;).</li><li>Le <strong>classement des résultats</strong> par pertinence <em>(result scoring)</em>.</li><li>La recherche <strong>multi-colonnes</strong>.</li></ul><p>Ces fonctionnalités sont traditionnellement offertes par les solutions de recherche full-text qui s&#8217;appuient sur l&#8217;utilisation <a
href="http://en.wikipedia.org/wiki/Inverted_index">d&#8217;index inversés</a>. Malheureusement, de telles solutions se marient souvent mal avec les systèmes de bases de données relationnelles en raison de décalages entre les deux paradigmes :</p><ul><li>Un <strong>déséquilibre structurel</strong> tout d&#8217;abord : Les index full-text ne sont que du texte. Ils ne reflètent pas une quelconque structuration des données. De plus, ils ne permettent pas de gérer des relations entre les documents.</li><li>De plus, le mariage des deux systèmes pose des problèmes de <strong>synchronisation</strong> : comment s&#8217;assurer que le contenu de la base de données et l&#8217;index documentaire sont à jour et cohérents l&#8217;un vis-à-vis de l&#8217;autre ?</li><li>Enfin, les <strong>résultats</strong> de recherches full-text sont des documents. En effet, les index ne stockent pas d&#8217;objets. Encore moins des objets <em>&laquo;&nbsp;managés&nbsp;&raquo;</em>.</li></ul><p>A cela s&#8217;ajoute des problèmes de portabilité et de flexibilité dus au fait que la plupart des implémentations ne sont que des surcouches propriétaires au dessus du SQL.</p><p>L&#8217;objet d&#8217;Hibernate Search est de résoudre ces décalages et de réconcilier la recherche full-text et les modèles de persistance relationnels. Pour ce faire, Hibernate Search se base sur <a
href="http://lucene.apache.org">Apache Lucene</a>, un moteur d&#8217;indexation et de recherche full-text standalone très puissant, mais plutôt bas niveau <em>(et donc facile à mal utiliser)</em>.<br
/> Hibernate Search permet ainsi d&#8217;utiliser les capacités d&#8217;Apache Lucene dans le cadre d&#8217;une couche de mapping Hibernate.</p><h3>Architecture</h3><p>Hibernate Search est donc un moteur d&#8217;indexation et de recherche full-text reposant sur Apache Lucene. Il prend ainsi en charge la gestion d&#8217;un index Lucene et sa synchronisation avec une structure objet persistée en base de données.</p><p>La mise à jour de l&#8217;index est déclenchée au travers de la capture d&#8217;événements JPA : quand une entité est insérée, mise à jour ou supprimée en base de données, Hibernate Search récupère l&#8217;événement et programme une mise à jour de l&#8217;index. Cette mise à jour est effectuée de façon transparente par Hibernate Search et ne nécessite pas l&#8217;utilisation directe des API Lucene.<br
/> Par souci d&#8217;efficacité, les opérations d&#8217;écritures dans l&#8217;index Lucene sont traitées par lots. Le traitement de ces lots s&#8217;inscrit dans la transaction courante. De cette façon, la mise à jour de l&#8217;index n&#8217;est déclenchée que si celle-ci est &laquo;&nbsp;commitée&nbsp;&raquo;, permettant ainsi de garantir l&#8217;<a
href="http://fr.wikipedia.org/wiki/Propri%C3%A9t%C3%A9s_ACID">ACID</a>-ité du système.</p><p>La recherche dans l&#8217;index se fait au travers des APIs <tt>}}<a
href="http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Query.html">org.hibernate.Query</a> ou <a
href="http://java.sun.com/javaee/5/docs/api/javax/persistence/Query.html">javax.persistence.Query</a>{{</tt> de la même façon que l&#8217;on écrirait des requêtes natives, HQL ou JPA-QL. Une requête de recherche Hibernate Search retourne une liste d&#8217;entités &laquo;&nbsp;managées&nbsp;&raquo; sans que le développeur ait à se soucier du fastidieux travail de mapping Objet &lt;-&gt; Document Lucene.</p><p>Le traitement des opérations de mise à jour de l&#8217;index peut être traité par différents backend en fonction de l&#8217;architecture et des scénarii mis en place. Hibernate Search propose deux &laquo;&nbsp;backend sur étagère&nbsp;&raquo; : Un backend Lucene <em>(synchrone)</em> et un backend JMS <em>(asynchrone)</em>.</p><h4>Backend synchrone</h4><p>Ce mode est le plus simple : toutes les opérations de mise à jour de l&#8217;index sont traitées par le nœud (la JVM) à l&#8217;origine de l&#8217;événement déclencheur. Le traitement des mises à jour est effectué de façon synchrone <em>(au commit de la transaction)</em>.<br
/> Ce backend adresse des environnements standalones <em>(non clusterisés)</em> ou en clusters symétriques <em>(tous les nœuds partagent la même instance d&#8217;index Lucene)</em>. C&#8217;est alors Lucene qui est en charge de la gestion de la stratégie de locking.</p><div
align="center"><img
src="http://blog.xebia.fr/wp-content/uploads/2008/03/hibernatesearchbackend.png" alt="Hibernate Search Back End" /></div><p>Les avantages de ce mode sont sa simplicité de mise en œuvre et la visibilité immédiate des mises à jour de l&#8217;index puisqu&#8217;elles sont traitées en synchrone.<br
/> Par contre, cette approche synchrone peut dégrader les performances de l&#8217;application front.</p><h4>Backend asynchrone</h4><p>Ce backend met en jeu le pattern maître / esclaves : Les opérations de mise à jour de l&#8217;index sont effectuées sur un index maître tandis que les opérations de lecture <em>(recherche)</em> sont effectuées sur des index esclaves. A intervalles réguliers, l&#8217;index maître est répliqué sur les esclaves <em>(copies du maître)</em>.</p><p>Ce backend adresse des environnements clusterisés répondant à des besoins de haute performance de l&#8217;application front. Dans ce mode, chaque nœud utilise un index esclave en local. Ces indexes sont exclusivement utilisés en lecture. Lorsqu&#8217;un événement nécessite une opération de mise à jour de l&#8217;index, l&#8217;ordre de mise à jour est envoyé sur une queue JMS <em>(tous les nœuds utilisant la même queue)</em>. Cette queue est dépilée par un consommateur unique en charge du traitement <em>(asynchrone)</em> des opérations de mise à jour qu&#8217;il effectue sur l&#8217;index maître.</p><div
align="center"><img
src="http://blog.xebia.fr/wp-content/uploads/2008/03/hibernatesearchbackendasync.png" alt="Hibernate Search Asynchronous Back End" /></div><p>Le principal avantage de ce mode est de ne pas surcharger l&#8217;application front avec les traitements de mise à jour de l&#8217;index.<br
/> Il entraîne par contre un délai de mise à jour des index de recherche <em>(Traitement asynchrone des mises à jour + délai de réplication)</em>.</p><h3>Pour bien commencer</h3><ul><li>Le site du projet <a
href="http://search.hibernate.org">Hibernate Search</a>.</li><li>La <a
href="http://www.hibernate.org/hib_docs/search/reference/en/html_single/">documentation Hibernate Search</a>.</li><li>La documentation de la <a
href="http://lucene.apache.org/java/2_3_1/queryparsersyntax.html">syntaxe de requêtes</a>.</li></ul><p>Et rendez-vous fin mars sur notre blog pour <a
href="http://blog.xebia.fr/2008/03/28/hands-on-hibernate-search-recherche-full-text/">un premier sample</a>.</p><hr
/> [1] <a
href="http://blog.emmanuelbernard.com/">Emmanuel Bernard</a> est membre de l&#8217;équipe Hibernate, Core developer à JBoss, lead developer sur les projets Hibernate Annotations, Hibernate EntityManager et Hibernate Search ainsi que membre des groupes d&#8217;expert EJB 3.0 et JSR-303.</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/03/06/introduction-a-hibernate-search-googling-your-persistent-domain-model/feed/</wfw:commentRss> <slash:comments>5</slash:comments> </item> <item><title>Hands on Wicket &#8211; Partie 2</title><link>http://blog.xebia.fr/2008/02/22/hands-on-wicket-partie-2/</link> <comments>http://blog.xebia.fr/2008/02/22/hands-on-wicket-partie-2/#comments</comments> <pubDate>Fri, 22 Feb 2008 09:43:04 +0000</pubDate> <dc:creator>Manuel Eveno</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Web]]></category> <category><![CDATA[Wicket]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/02/22/hands-on-wicket-partie-2/</guid> <description><![CDATA[Wicket est un framework plut&#244;t en rupture avec les frameworks web actuels. Ici pas de fichier XML qui d&#233;finit la navigation, pas de librairies de tag sp&#233;cifiques, juste de la simplicit&#233; et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas m&#233;langer les genres : les pages se font en [...]]]></description> <content:encoded><![CDATA[<p><a
href="http://wicket.apache.org">Wicket</a> est un framework plut&ocirc;t en rupture avec les frameworks web actuels. Ici pas de fichier XML qui d&eacute;finit la navigation, pas de librairies de tag sp&eacute;cifiques, juste de la simplicit&eacute; et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas m&eacute;langer les genres : les pages se font en plain html et le d&eacute;veloppement, à proprement parler, se fait en pur Java.</p><p>Dans le <a
href="http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/">pr&eacute;c&eacute;dent</a> billet, nous nous &eacute;tions arr&ecirc;t&eacute;s à la mise en place d&#8217;un projet Wicket. Aujourd&#8217;hui, regardons de plus pr&egrave;s la construction des pages et la façon d&#8217;impl&eacute;menter la logique de navigation avec les formulaires et les liens.</p><h3>Construction des pages</h3><p>La construction des pages se fait en deux &eacute;tapes :</p><ul><li>Cr&eacute;ation du fichier HTML</li><li>Cr&eacute;ation de la classe Java correspondante</li></ul><p>Par d&eacute;faut, Wicket suppose que le fichier HTML et la classe Java doivent &ecirc;tre dans le m&ecirc;me package et porter le m&ecirc;me nom (sauf l&#8217;extension bien sûr) . Il est cependant possible de <a
href="http://cwiki.apache.org/WICKET/control-where-html-files-are-loaded-from.html">modifier</a> ce comportement.</p><p>Le fichier HTML utilise exclusivement la syntaxe HTML sur laquelle on vient positionner des attributs &quot;wicket:id&quot; pour les &eacute;l&eacute;ments qui sont associ&eacute;s aux traitements m&eacute;tiers des pages. Les balises auxquelles on ajoute cet attribut repr&eacute;sentent le contenu dynamique (que l&#8217;on veut traiter en Java). C&#8217;est par cet attribut que Wicket fera la correspondance entre les tags HTML et les composants cr&eacute;&eacute;s en Java. Le code HTML ne contient rien d&#8217;autre que de la mise en page et mise en forme (classique HTML + CSS), la logique m&eacute;tier &eacute;tant compl&egrave;tement impl&eacute;ment&eacute;e en Java. Un bel exemple de &quot;<a
href="http://en.wikipedia.org/wiki/Separation_of_concerns">Separation of concerns</a>&quot;.</p><p>Etudions un exemple pour bien comprendre ce m&eacute;canisme.</p><p>Imaginons le fichier HTML suivant avec un message que l&#8217;on veut dynamique, un champ texte et un bouton :</p><pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
  &lt;body&gt;
    &lt;p&gt;Dynamic Message goes here&lt;/p&gt;
    &lt;form&gt;
    	&lt;input type=&quot;text&quot; /&gt;
    	&lt;input type=&quot;submit&quot; value=&quot;update&quot;/&gt;
    &lt;/form&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre><p>Le passage à Wicket donnera :</p><pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
  &lt;body&gt;
    &lt;p wicket:id=&quot;message&quot;&gt;Message goes here&lt;/p&gt;
    &lt;form wicket:id=&quot;messageInputForm&quot;&gt;
    	&lt;input type=&quot;text&quot; wicket:id=&quot;messageInput&quot;/&gt;
    	&lt;input type=&quot;submit&quot; value=&quot;update&quot;/&gt;
    &lt;/form&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre><p>On ajoute l&#8217;attribut wicket:id auquel on donne une valeur unique qui nous servira dans le code Java. Cet attribut &quot;wicket:id&quot; persiste par d&eacute;faut dans le HTML m&ecirc;me apr&egrave;s traitement par Wicket. Il est cependant possible de demander à Wicket de supprimer ces attributs (voir <a
href="http://cwiki.apache.org/WICKET/how-to-remove-wicket-markup-from-output.html">ici</a>) en production pour obtenir des pages HTML conformes au <a
href="http://validator.w3.org">standard du W3C</a>.</p><p>Il nous reste plus qu&#8217;à &eacute;crire la classe Java correspondante qui doit h&eacute;riter de la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/WebPage.html">WebPage</a> :</p><pre class="brush: java; title: ; notranslate">
public class HelloWorld extends WebPage
{
  public HelloWorld()
  {
    IModel messageModel = new Model(&quot;Hello World!&quot;);
    add(new Label(&quot;message&quot;, messageModel));
    add(new MessageForm(&quot;messageInputForm&quot;, messageModel));
  }
  private static final class MessageForm extends Form
  {
    public MessageForm(String id, IModel model)
    {
      super(id);
      add(new TextField(&quot;messageInput&quot;, model));
    }
    protected void onSubmit()
    {
      // Rien à faire ici, le modèle sera automatiquement mis à jour
    }
  }
}
</pre><p>On remarque que le code Java r&eacute;utilise les ids qui apparaissent dans le code HTML. C&#8217;est via ces ids que le pont entre le Java et le HTML est fait.</p><p>Ensuite, la seconde chose que l&#8217;on note est l&#8217;imbrication des composants. Cette imbrication doit respecter celle qui existe dans le HTML. Ainsi puisque le formulaire contient le champ texte, dans le code Java, on doit retrouver cette contenance.</p><p>L&#8217;autre particularit&eacute; à remarquer est l&#8217;utilisation de l&#8217;objet Model. Tous les composants Wicket ont un model qui permet de d&eacute;finir leur contenu : m&ecirc;me un simple label utilise un Model pour afficher son body. Chacun des mod&egrave;les doit impl&eacute;menter l&#8217;interface <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/IModel.html">IModel</a> (voir paragraphe suivant).</p><p>Dans notre cas, le champ texte et le label partagent le m&ecirc;me mod&egrave;le. Ainsi, le contenu du label aura le m&ecirc;me contenu que le champ texte. De plus, par d&eacute;faut, la soumission d&#8217;un formulaire entraîne la mise à jour automatique du mod&egrave;le avec les donn&eacute;es du formulaire. La classe Form appelle simplement la m&eacute;thode setObject(&#8230;) du mod&egrave;le avec la donn&eacute;e correspondante du formulaire. La mise à jour du champ causera donc la mise à jour du label apr&egrave;s soumission du formulaire. Il est bien sûr possible d&#8217;ajouter des traitements dans la m&eacute;thode onSubmit() du formulaire.</p><p>Je vous invite à faire un tour dans les <a
href="http://www.wicketstuff.org/wicket13/">exemples</a>, la <a
href="http://people.apache.org/~tobrien/wicket/apidocs/index.html">Javadoc</a> et le <a
href="http://cwiki.apache.org/WICKET/">wiki</a> pour &eacute;plucher les diff&eacute;rents composants et leur mod&egrave;le respectif.</p><h3>La notion de Model</h3><p>Les composants Wicket reposent tous sur un mod&egrave;le. Les composants acc&egrave;dent aux donn&eacute;es qu&#8217;ils doivent prendre en charge via le mod&egrave;le qui leur est attach&eacute;. Ces mod&egrave;les impl&eacute;mentent tous l&#8217;interface <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/IModel.html">IModel</a>. Ceci permet de garder le contr&ocirc;le sur l&#8217;acc&egrave;s aux donn&eacute;es par les composants. Il est à noter que les objets qui impl&eacute;mentent cette interface seront syst&eacute;matiquement stock&eacute;s dans la session. Il faut donc faire attention à la façon dont nous impl&eacute;mentons les mod&egrave;les pour &eacute;viter de surcharger la session.</p><p>Plusieurs mod&egrave;les sont disponibles et fournis par Wicket :</p><ul><li> <b>Basic Models</b> &#8211; Pour impl&eacute;menter un mod&egrave;le basique qui sera int&eacute;gralement stock&eacute; dans la session. Pour impl&eacute;menter ce mod&egrave;le, vous pouvez utiliser simplement la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/Model.html">Model</a>.</li><li> <b>Detachable Models</b> &#8211; Ces mod&egrave;les impl&eacute;mentent l&#8217;interface <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/IDetachable.html">IDetachable</a> et offrent la m&eacute;thode detach(), qui permet d&#8217;&ecirc;tre notifi&eacute; quand Wicket n&#8217;a plus besoin du mod&egrave;le, ainsi les donn&eacute;es peuvent &ecirc;tre lib&eacute;r&eacute;es de la m&eacute;moire au plus t&ocirc;t.</li><li> <b>Nested Models</b> &#8211; Les mod&egrave;les peuvent aussi &ecirc;tre imbriqu&eacute;s pour repr&eacute;senter une hi&eacute;rarchie d&#8217;objets. Pour ce faire, il suffit d&#8217;impl&eacute;menter la m&eacute;thode getNestedModel() de l&#8217;interface IModel qui renvoie le mod&egrave;le sous-jacent.</li><li> <b>Property Models</b> &#8211; La classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/AbstractPropertyModel.html">AbstractPropertyModel</a> fournit un comportement par d&eacute;faut pour les mod&egrave;les bas&eacute;s sur des propri&eacute;t&eacute;s. La classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/PropertyModel.html">PropertyModel</a> permet d&#8217;encapsuler un POJO et de donner acc&egrave;s aux propri&eacute;t&eacute;s de cet objet. C&#8217;est cette classe qu&#8217;on utilise pour associer l&#8217;attribut d&#8217;un objet à un champ de formulaire.</li><li> <b>Compound Property Models</b> &#8211; Ce type de mod&egrave;le permet de changer le comportement du mod&egrave;le en fonction du composant qui y acc&egrave;de. Les m&eacute;thodes get et setObject prennent le composant en param&egrave;tre pour cela.</li></ul><p>Le <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/PropertyModel.html">PropertyModel</a> et le <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/model/Model.html">Model</a> restent les deux mod&egrave;les les plus courants.</p><p>Exemple :&nbsp;</p><pre class="brush: java; title: ; notranslate">
Label myLabel = new Label(&quot;myLabel&quot;, new Model(&quot;Hello world !&quot;));
Person person = .... // Obtenir un objet Person
TextField myTextField = new TextField(&quot;myTextField&quot;,
	new PropertyModel(person, &quot;name&quot;));
</pre><h3>La navigation dans Wicket</h3><p>Avec Wicket, la navigation se code en Java. Ici, pas de fichier xml de configuration des mappings URL/Class. Une m&eacute;thode principale est utilis&eacute;e pour ceci, il s&#8217;agit de setResponsePage() disponible sur la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/Component.html">Component</a>. Cette m&eacute;thode prend en param&egrave;tre soit une classe, soit une classe avec des param&egrave;tres, soit une instance de page (dans les deux cas, la classe pass&eacute;e en param&egrave;tre doit h&eacute;riter de la classe Page).</p><ul><li>setResponsePage(Class) permet de rediriger vers une page ind&eacute;pendante des donn&eacute;es de la page courante. L&#8217;instanciation de la page et la gestion de son cycle de vie sont effectu&eacute;es par Wicket.</li><li>setResponsePage(Class, <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/PageParameters.html">PageParameters</a>) redirige vers une page en lui passant des donn&eacute;es. L&#8217;instanciation de la page et la gestion de son cycle de vie sont là aussi effectu&eacute;es par Wicket.</li><li>setResponsePage(Page) La troisi&egrave;me (avec une instance de page pass&eacute;e en param&egrave;tre) permet de pointer vers une page que l&#8217;on aura instanci&eacute;e nous-m&ecirc;me et à laquelle on a pass&eacute; des param&egrave;tres.</li></ul><pre class="brush: java; title: ; notranslate">
setResponsePage(MySecondPage.class);
setResponsePage(new MySecondPage(myTextField.getModel())); // On passe des données dynamiques à la page cible
</pre><p>Nous allons appeler cette m&eacute;thode en r&eacute;pondant aux &quot;&eacute;v&eacute;nements&quot; de l&#8217;application à la mani&egrave;re d&#8217;une application Swing (onClick, onSubmit, etc.) ou en sp&eacute;cifiant pour certains types de lien, la page à afficher en cas de click.</p><p>Il y a deux principaux moyens de naviguer dans Wicket :</p><ul><li><b>les formulaires</b> : Dans ce cas, on utilise (ou h&eacute;rite de) la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/form/Form.html">Form</a> à laquelle on ajoute les composants du formulaire. Le traitement associ&eacute; à la validation du formulaire est cod&eacute; dans la m&eacute;thode onSubmit(). C&#8217;est aussi dans cette m&eacute;thode que l&#8217;on peut d&eacute;finir la page qui sera affich&eacute;e apr&egrave;s soumission du formulaire via la m&eacute;thode setResponsePage().</li></ul><pre class="brush: java; title: ; notranslate">
/**
 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
 */
public final void onSubmit() {
	// Do the submit stuff (update the database, etc.)
	setResponsePage(MySecondPage.class);
}
</pre><ul><li><b>les liens</b> : Les liens h&eacute;ritent en g&eacute;n&eacute;ral de la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/link/Link.html">Link</a> qui fournit les m&eacute;canismes de base pour cr&eacute;er un lien. Pour d&eacute;finir l&#8217;action à effectuer lors d&#8217;un click, on red&eacute;finit simplement le m&eacute;thode onClick(). On peut effectuer dans cette m&eacute;thode un traitement pour finir par donner la page à afficher en r&eacute;sultat via la m&eacute;thode setResponsePage(). Il est aussi possible d&#8217;utiliser la classe <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/link/PageLink.html">PageLink</a> qui redirige simplement vers une page donn&eacute;e en param&egrave;tres.</li></ul><pre class="brush: java; title: ; notranslate">
Link addToCartLink = new Link(&quot;addToCartLink&quot;) {
	private static final long serialVersionUID = 1L;
	public void onClick() {
		AddToCartPage page = new AddToCartPage(book.getId());
		setResponsePage(page);
	}
};
 // ou
new PageLink(&quot;viewLink&quot;, ViewPage.class);
</pre><p>Par d&eacute;faut, les liens ne peuvent &ecirc;tre ajout&eacute;s aux favoris (si on retourne sur une page &quot;bookmark&eacute;e&quot;, on obtient un message &quot;Page expired&quot;). Les liens sont en fait maintenus en session, ce qui explique la possibilit&eacute; de cr&eacute;er ses liens en Java de cette façon. Pour cr&eacute;er un lien &quot;bookmarkable&quot;, il faut utiliser la classe du m&ecirc;me nom : <a
href="http://people.apache.org/~tobrien/wicket/apidocs/org/apache/wicket/markup/html/link/BookmarkablePageLink.html">BookmarkablePageLink</a>. Ce type de lien peut &ecirc;tre ajout&eacute; aux favoris.</p><p>C&#8217;est tout pour cette fois, il vous faudra un peu de patience avant d&#8217;en savoir plus sur les fonctionnalit&eacute;s de Wicket.</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/02/22/hands-on-wicket-partie-2/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Hands on Wicket &#8211; Partie 1</title><link>http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/</link> <comments>http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/#comments</comments> <pubDate>Thu, 14 Feb 2008 11:16:03 +0000</pubDate> <dc:creator>Manuel Eveno</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Web]]></category> <category><![CDATA[Wicket]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/</guid> <description><![CDATA[Wicket est un framework plutôt en rupture avec les frameworks web actuels. Ici pas de fichier XML qui définit la navigation, pas de librairies de tags spécifiques, juste de la simplicité et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas mélanger les genres : les pages se font en [...]]]></description> <content:encoded><![CDATA[<p><a
href="http://wicket.apache.org/">Wicket</a> est un framework plutôt en rupture avec les frameworks web actuels. Ici pas de fichier XML qui définit la navigation, pas de librairies de tags spécifiques, juste de la simplicité et du pragmatisme &#8230; tout pour nous plaire ! Wicket a choisi de ne pas mélanger les genres : les pages se font en pur html et le développement, à proprement parler, se fait en pur Java.</p><p>Dans cette première partie, nous verrons comment démarrer avec Wicket.</p><h3>Quickstart</h3><p>Démarrer avec <a
href="http://wicket.apache.org/">Wicket</a> est plutôt simple. Il suffit de lancer une commande <a
href="http://maven.apache.org/">Maven2</a>, ce qui a deux avantages :</p><ul><li>Générer automatiquement un pom avec les dépendances Wicket</li><li>Générer un squelette de projet Wicket</li></ul><p>Il existe donc un archetype Wicket pour Maven2 que l&#8217;on peut utiliser comme suit :</p><pre class="brush: python; title: ; notranslate">
mvn archetype:create -DarchetypeGroupId=org.apache.wicket
-DarchetypeArtifactId=wicket-archetype-quickstart
-DarchetypeVersion=1.3.0-rc1
-DgroupId=com.mycompany
-DartifactId=myproject
</pre><p>Qu&#8217;obtient-on d&#8217;intéressant avec cet archetype ?</p><ul><li>Un fichier pom.xml</li><li>Un fichier webapp\WEB-INF\web.xml préconfiguré pour Wicket</li><li>Quelques fichiers sources dans src\main\java\com\mycompany :<br
/> - Une page d&#8217;accueil constituée d&#8217;un fichier HTML (HomePage.html) et d&#8217;un fichier Java (HomePage.java)<br
/> - Une classe Java (WicketApplication.java)</li><li>Une classe Java src\test\java\com\mycompany\Start.java qui permet de lancer Jetty</li></ul><p>Vous avez déjà une application Wicket fonctionnelle à ce stade.</p><p>Ensuite, générer la configuration Eclipse avec la commande :</p><pre class="brush: python; title: ; notranslate">
mvn eclipse:eclipse
</pre><p><em>(Attention, le plugin maven eclipse n&#8217;est pas encore compatible WTP 2.0, c&#8217;est la version de WTP livrée dans Eclipse Europa, pour avoir ce support, il faut compiler le plugin maven eclipse 2.5-SNAPSHOT, en attendant sa sortie officielle.)</em></p><h3>Configuration de l&#8217;application web</h3><p>Revenons sur certains des fichiers générés par l&#8217;archetype Maven2. Pour utiliser Wicket dans une application web, il faut ajouter quelques lignes dans le web.xml. On y référence en fait un simple Servlet Filter qui lui-même pointe vers une classe de l&#8217;application.</p><pre class="brush: xml; title: ; notranslate">
&lt;filter&gt;
&lt;filter-name&gt;wicket.myproject&lt;/filter-name&gt;
&lt;filter-class&gt;org.apache.wicket.protocol.http.WicketFilter&lt;/filter-class&gt;
&lt;init-param&gt;&lt;/init-param&gt;&lt;/filter&gt;
&lt;param-name&gt;applicationClassName&lt;/param-name&gt;
&lt;param-value&gt;com.mycompany.WicketApplication&lt;/param-value&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;wicket.myproject&lt;/filter-name&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;
</pre><p>C&#8217;est tout pour la configuration en XML. Le reste des développements se fera en HTML et en Java !!</p><h3>La pièce maîtresse</h3><p>Le Servlet Filter pointe vers la classe principale de l&#8217;application qui doit hériter de <a
href="http://people.apache.org/%7Etobrien/wicket/apidocs/org/apache/wicket/protocol/http/WebApplication.html">WebApplication</a>. C&#8217;est le point d&#8217;entrée de l&#8217;application Wicket. Cette classe permet :</p><ul><li>de configurer l&#8217;application en utilisant les nombreuses méthodes de type getXXXXSettings() (où XXXX peut être remplacé par Session, Markup, Framework, Resources, Security, etc.).</li><li>de définir la page d&#8217;accueil en surchargeant la méthode getHomePage() :</li></ul><pre class="brush: java; title: ; notranslate">
import org.apache.wicket.protocol.http.WebApplication;
public class HelloWorldApplication extends WebApplication {
public void init() {
// getXXXSettings().set ......
}
/**
* @see org.apache.wicket.Application#getHomePage()
*/
public Class getHomePage() {
return HelloWorld.class;
}
}
</pre><p>Cette &laquo;&nbsp;HomePage&nbsp;&raquo; sera affichée, par défaut, lorsqu&#8217;un utilisateur accédera à l&#8217;application sans adresser directement une page visible de l&#8217;application <em>(nous reviendrons sur ce point dans les articles suivants)</em>.</p><p>Et voilà, la magie Wicket vient d&#8217;opérer : vous avez déjà une application exécutable. Pour la tester, c&#8217;est encore très simple.</p><p>Le pom Maven2, généré par l&#8217;archetype Wicket, est configuré pour pouvoir lancer jetty avec l&#8217;application web.</p><p>Démarrer le serveur Jetty (préconfiguré lui aussi dans le POM généré) via :</p><pre class="brush: python; title: ; notranslate">
mvn jetty:run
</pre><p>Ouvrez votre navigateur à l&#8217;adresse http://localhost:8080/myproject. Vous verrez apparaître votre première application Wicket <img
src='http://blog.xebia.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> .</p><p>A bientôt, pour de nouvelles aventures avec Wicket &#8230;</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/02/14/hands-on-wicket-partie-1/feed/</wfw:commentRss> <slash:comments>4</slash:comments> </item> <item><title>L&#8217;analyse de couverture de code en Java</title><link>http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/</link> <comments>http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/#comments</comments> <pubDate>Thu, 07 Feb 2008 07:40:34 +0000</pubDate> <dc:creator>Alexandre De Magalhaes Garcia</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Test-Driven-Development]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/</guid> <description><![CDATA[Il ne reste plus grand&#8217;monde pour soutenir que l&#8217;écriture de tests unitaires automatisés est une perte de temps sur un projet logiciel &#8211; la notion de dette technique entre dans les moeurs. Cette prise de conscience salutaire se heurte pourtant souvent à deux grandes catégories de difficultés : Le conservatisme &#8211; pour ne pas dire [...]]]></description> <content:encoded><![CDATA[<p>Il ne reste plus grand&#8217;monde pour soutenir que l&#8217;écriture de tests unitaires automatisés est une perte de temps sur un projet logiciel &#8211; la notion de dette technique entre dans les moeurs. Cette prise de conscience salutaire se heurte pourtant souvent à deux grandes catégories de difficultés :</p><ul><li>Le conservatisme &#8211; pour ne pas dire la stupidité &#8211; de certains chefs de projet, qui persistent à voir dans l&#8217;écriture des tests une activité contre-productive, qui servira de variable d&#8217;ajustement au moindre coup de grisou</li><li>L&#8217;existant : quand le projet n&#8217;a pas systématisé la pratique du test dès son origine et que la multiplication des anomalies tardives le pousse à la réintroduire. Par où commencer&nbsp;?</li></ul><p>Pour le premier point, la solution passe par un effort pédagogique, ou, pour les cas désespérés, par une reconversion imposée ou un congé sabbatique.<br
/> Pour le second, il faut un peu de bon sens, et un peu d&#8217;outillage.</p><p>C&#8217;est sur le deuxième aspect qu&#8217;interviennent les outils d&#8217;analyse de <strong>couverture de code</strong> (ou &laquo;&nbsp;<strong>code coverage</strong>&nbsp;&raquo; en anglais). Dans la suite, nous verrons que ces outils ne permettent pas d&#8217;évaluer les tests unitaires d&#8217;un point de vue qualitatif, mais qu&#8217;ils peuvent en revanche apporter au bon sens un précieux appui en répondant aux questions suivantes :</p><ul><li>Quels sont les tests unitaires déjà en place&nbsp;?</li><li>Les tests sont-ils en phase (à jour) avec le code qu&#8217;ils testent&nbsp;?</li><li>Les fonctions critiques de l&#8217;application sont-elles couvertes par les tests&nbsp;?</li></ul><h3>Quelques définitions</h3><p>Pour rappel, on distingue 2 grandes catégories complémentaires de tests pour un projet logiciel :</p><ul><li>Test par &laquo;&nbsp;<strong>boîte noire</strong>&nbsp;&raquo; (<strong>black box testing</strong>) : les tests sont effectués sans connaissance de la structure interne et des objets manipulés par le système. On ne s&#8217;intéresse qu&#8217;aux entrées et sorties de ce système. Les tests fonctionnels ou les tests manuels par exemple, appartiennent à cette catégorie.</li><li>Test par &laquo;&nbsp;<strong>boîte blanche</strong>&nbsp;&raquo; (<strong>white box testing</strong>) : les tests s&#8217;intéressent au fonctionnement interne du système. Ainsi, les tests unitaires manipulent les objets et éléments qui composent le système. L&#8217;analyse de couverture de code permet d&#8217;obtenir une mesure indirecte de la complétude des tests effectués à ce niveau.</li></ul><p>La couverture de code est une mesure qui décrit le degré auquel le code source d&#8217;un programme a été testé. Ainsi c&#8217;est l&#8217;exécution de tous les tests (unitaires, fonctionnels, etc&#8230;) qui va permettre de déterminer notre mesure de couverture.</p><p>Les outils fournissant cette mesure savent déterminer quels fragments de code ou quelles méthodes ont été exécutés. La mesure (ou &laquo;&nbsp;<strong>métrique</strong>&laquo;&nbsp;) principale est calculée en faisant le rapport du nombre de lignes de code exécutées sur le nombre de lignes total du code source : c&#8217;est la couverture de ligne (&laquo;&nbsp;<strong>statement coverage</strong>&laquo;&nbsp;).</p><p>Plusieurs autres métriques (ou &laquo;&nbsp;<strong>critères</strong>&laquo;&nbsp;) existent et permettent de calculer la couverture de code à différents niveau de granularité :</p><ul><li>Method coverage : Est-ce que toutes les méthodes ont été exécutées&nbsp;?</li><li>Branch coverage : Est-ce que les structures de contrôle (if ou while par exemple) ont été testées pour les 2 cas vrai et faux&nbsp;?</li><li>Path coverage : Est-ce que tous les chemins possibles d&#8217;exécution des fonctions d&#8217;un programme ont été exécutés&nbsp;?</li></ul><h3>Les stratégies d&#8217;instrumentation</h3><p>En Java, il existe plusieurs manières de déterminer quelles parties du code ont été exécutées.</p><div
align="center"><img
src="http://blog.xebia.fr/wp-content/uploads/2008/02/instrumentation-strategies.jpg" alt="stratégies d’instrumentation" /></div><p>On distingue 2 techniques :</p><ul><li>L&#8217;instrumentation des classes :<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;L&#8217;outil d&#8217;instrumentation ajoute des lignes dans le code source du programme lui permettant de détecter le code exécuté.<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;L&#8217;outil intervient directement sur le code compilé (byte-code) et ajoute ses modifications (&laquo;&nbsp;<strong>tracking hooks</strong>&laquo;&nbsp;).</li><li>L&#8217;utilisation des options de la JVM : il est possible d&#8217;utiliser une configuration particulière de la JVM pour utiliser des &laquo;&nbsp;listeners&nbsp;&raquo; (utilisant les interfaces JVMPI ou JVMDI, remplacés par JVMTI depuis Java 5). Ainsi&nbsp;certains outils de profiling utilisent un environnement d&#8217;exécution particulier permettant de détecter le code exécuté.</li></ul><p>Les outils les plus courants utilisent la technique d&#8217;instrumentation des classes, le plus souvent au niveau byte-code. Les raisons principales en sont les suivantes :</p><ul><li>La surcharge à l&#8217;exécution (&laquo;&nbsp;<strong>runtime overhead</strong>&laquo;&nbsp;) pour les classes instrumentées est comprise entre 5 et 20%. L&#8217;utilisation de profilers JVM est beaucoup plus pénalisante.</li><li>Pas besoin du code source pour instrumenter.</li><li>Instrumentation des jars tiers possible.</li><li>Indépendant de la plateforme.</li><li>Facilité de branchement de l&#8217;outil ad-hoc.</li></ul><p>A noter que certains outils utilisent une approche complémentaire : l&#8217;instrumentation dynamique (au runtime). Par exemple <a
href="http://emma.sourceforge.net/">Emma</a> permet d&#8217;instrumenter les classes à la volée (on the fly) et avec l&#8217;utilisation d&#8217;un classloader modifié, d&#8217;obtenir les informations de couverture d&#8217;un programme en cours d&#8217;exécution. <a
href="http://hansel.sourceforge.net/">Hansel</a> encapsule les tests unitaires (JUnit par exemple) dans des classes spécifiques permettant d&#8217;injecter à l&#8217;exécution (à l&#8217;aide d&#8217;un classloader modifié) le code permettant de tester la couverture.</p><h3>Exemple d&#8217;utilisation avec l&#8217;outil Cobertura</h3><h4>Caractéristiques</h4><ul><li>Le projet <a
href="http://cobertura.sourceforge.net/index.html">Cobertura</a> est basé sur JCoverage :<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Il est gratuit (licence Apache et GPL).<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Utilise l&#8217;instrumentation du byte-code en mode déconnecté (après compilation).</li><li>Génération de rapports en HTML ou XML :<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Possibilité de fusion de différents rapports de couverture (merge).<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Possibilité d&#8217;incorporer les rapports dans les outils d&#8217;intégration continue (Hudson, Cruise Control).</li><li>Metrics :<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Statement et branch coverage pour les classes, packages et l&#8217;ensemble d&#8217;un projet.<br
/> &nbsp;&nbsp;&nbsp;-&nbsp;Affichage de la complexité cyclomatique du code de McCabe pour chaque classes, moyenne pour chaque package et globale pour l&#8217;ensemble du projet.</li><li>Intégration possible en ligne de commande, dans une tâche Ant ou avec Maven.</li></ul><h4>Exemple de tâches Ant</h4><p><strong>Instrumentation des classes compilées</strong></p><p>Cette tâche permet d&#8217;instrumenter les classes :</p><pre class="brush: xml; title: ; notranslate">
&lt;cobertura-instrument todir=&quot;${instrumented.dir}&quot;&gt;
    &lt;ignore regex=&quot;org.apache.log4j.*&quot; /&gt;
    &lt;fileset dir=&quot;${classes.dir}&quot;&gt;
        &lt;include name=&quot;**/ui/**/*.class&quot; /&gt;
        &lt;exclude name=&quot;**/*Test.class&quot; /&gt;
    &lt;/fileset&gt;
    &lt;fileset dir=&quot;${jars.dir}&quot;&gt;
        &lt;include name=&quot;domain.jar&quot; /&gt;
    &lt;/fileset&gt;
&lt;/cobertura-instrument&gt;
</pre><div
style="border:dotted 1px #4F2F4F; background-color: #FEEEBA; text-align: left; margin-top: 20px; margin-right: 20px; margin-bottom: 20px; margin-left: 0px; padding-right: 5px; padding-left: 5px;"> <strong><i>Tips &amp; Tricks :</i></strong></p><ul><li><i><code>&lt;ignore regex="org.apache.log4j.*" /&gt;</code> : cette ligne permet d&#8217;ignorer tous les appels à toutes les méthodes des classes qui correspondent à l&#8217;expression régulière. Toutefois les classes correspondantes seront instrumentées.</i></li><li><i>On peut utiliser les filesets Ant standards pour inclure ou exclure certains packages ou classes du chemin d&#8217;instrumentation. De la même manière on peut instrumenter des jars.</i></li></ul></div><p>Cobertura peut également instrumenter des archives incluses dans d&#8217;autres archives. Par exemple, des jars contenu dans le répertoire WEB-INF/lib d&#8217;un war seront instrumenté et le war sera reconstruit avec les jars instrumentés inclus.</p><p><strong>Exécution des tests unitaires</strong></p><p>Les parties entre [ ] sont à ajouter/modifier dans une tâche Ant JUnit classique :</p><pre class="brush: xml; title: ; notranslate">
&lt;junit [fork=&quot;yes&quot;] dir=&quot;${basedir}&quot; failureProperty=&quot;test.failed&quot;&gt;
	[&lt;sysproperty key=&quot;net.sourceforge.cobertura.datafile&quot; file=&quot;${basedir}/cobertura.ser&quot; /&gt;]
	[&lt;classpath location=&quot;${instrumented.dir}&quot; /&gt;]
	&lt;classpath location=&quot;${classes.dir}&quot; /&gt;
	[&lt;classpath refid=&quot;cobertura_classpath&quot; /&gt;]
	...
&lt;/junit&gt;
</pre><div
style="border:dotted 1px #4F2F4F; background-color: #FEEEBA; text-align: left; margin-top: 20px; margin-right: 20px; margin-bottom: 20px; margin-left: 0px; padding-right: 5px; padding-left: 5px;"> <strong><i>Tips &amp; Tricks :</i></strong></p><ul><li>Il est nécessaire de spécifier la valeur &laquo;&nbsp;yes&nbsp;&raquo; pour le mode fork de la tâche JUnit pour qu&#8217;elle soit exécutée dans une JVM différente de celle de Ant. En effet, Cobertura n&#8217;écrit les données de couverture que lorsque la JVM a terminé son exécution. Or on a besoin que ces données soit écrite avant que Ant ne termine car il faut générer le rapport Cobertura.</li><li>Il faut déclarer le chemin vers le fichier dans lequel seront stockées les informations de couverture (ici cobertura.ser).</li><li>Ajouter le chemin vers le répertoire contenant les classes instrumentées. Il est important que la déclaration de ce chemin soit placée avant celui du classpath vers les classes non instrumentées.</li><li>Il faut ensuite ajouter dans le classpath les jars nécessaires à l&#8217;exécution de Cobertura (cobertura.jar + librairies asm + jakarta oro)</li></ul><p>Dans le cas d&#8217;utilisation de Cobertura avec un serveur d&#8217;application les données de couverture de code ne seront écrites dans le fichier que lorsque le serveur sera arrêté. Si cela pose un problème, il est possible d&#8217;ajouter dans le code (à la fin des tests unitaires par exemple) un appel à la méthode net.sourceforge.cobertura.coveragedata.ProjectData.saveGlobalProjectData() qui forcera l&#8217;écriture.</p></div><p><strong>Génération du rapport</strong></p><p>Pour générer le rapport au format HTML on utilisera le code suivant :</p><pre class="brush: xml; title: ; notranslate">
&lt;cobertura-report format=&quot;html&quot; destdir=&quot;${coveragereport.dir}&quot; srcdir=&quot;${src.dir}&quot; /&gt;
</pre><div
style="border:dotted 1px #4F2F4F; background-color: #FEEEBA; text-align: left; margin-top: 20px; margin-right: 20px; margin-bottom: 20px; margin-left: 0px; padding-right: 5px; padding-left: 5px;"> <strong><i>Tips &amp; Tricks :</i></strong><br
/> L&#8217;attribut &laquo;&nbsp;format&nbsp;&raquo; permet de spécifier 2 types de format en sortie : XML ou HTML. Le format XML est utile notamment pour l&#8217;intégration à Hudson.</td></tr></tbody></table></div><p>Un exemple de rapport HTML obtenu :</p><div
align="center"><a
href="http://blog.xebia.fr/wp-content/uploads/2008/02/report-overview.png" title="Exemple de rapport HTML"><img
src="http://blog.xebia.fr/wp-content/uploads/2008/02/report-overview.miniature.png" alt="Exemple de rapport HTML" /></a></div><p>Et le détail des lignes couvertes par les tests dans le code source :</p><div
align="center"><a
href="http://blog.xebia.fr/wp-content/uploads/2008/02/report-detail.png" title="Détail des lignes couvertes par les tests dans le code source"><img
src="http://blog.xebia.fr/wp-content/uploads/2008/02/report-detail.miniature.png" alt="Détail des lignes couvertes par les tests dans le code source" /></a></div><h4>Autres fonctionnalités</h4><p>Cobertura propose également une tâche, &laquo;&nbsp;cobertura-check&nbsp;&raquo;, permettant de définir des seuils et de vérifier comment le code est couvert par rapport à ces seuils.</p><p>La tâche &laquo;&nbsp;cobertura-merge&nbsp;&raquo;, permet de fusionner plusieurs fichiers de couverture. On peut ainsi générer des rapports globaux pour les parties client et serveur d&#8217;une application ou fusionner des données obtenues sur plusieurs sessions d&#8217;utilisation de Cobertura.</p><p>Pour chacune des opérations offertes Cobertura (instrument, report, merge, check), on peut effectuer l&#8217;appel via la ligne de commande. Par exemple :</p><pre class="brush: python; title: ; notranslate">
java -cp %COBERTURA_CLASSPATH% net.sourceforge.cobertura.reporting.Main --format html --destination C:\Reports\coverage
</pre><p>Enfin <a
href="http://cobertura.sourceforge.net/maven.html">2 projets annexes</a> permettent également d&#8217;intégrer Cobertura dans une configuration Maven (1 ou 2) en offrant les mêmes fonctionnalités.</p><p>Cobertura ne propose malheureusement pas de plugin d&#8217;intégration à Eclipse. Comme outil proposant les même fonctionnalités et l&#8217;intégration à Eclipse, on peut citer l&#8217;outil gratuit open source&nbsp;<a
href="http://www.eclemma.org/">EclEmma</a> qui est une extension de Emma cité un peu plus haut.</p><h3>Comment bien utiliser l&#8217;analyse de couverture de code</h3><p>Comme souvent dans l&#8217;ingénierie logicielle, l&#8217;utilisation d&#8217;une métrique de mesure de la qualité s&#8217;accompagne de son lot d&#8217;apôtres plus ou moins extrémistes. Le taux de couverture des tests n&#8217;échappe pas à cette règle, et l&#8217;on voit certains définir des objectifs quantitatifs rigides dans ce domaine. Certains affirment que le taux de couverture doit être de 100% ; d&#8217;autres, plus modestes, se contenteraient de 90%.</p><p>Dans la pratique il est difficile, voire impossible d&#8217;atteindre 100% et sur un projet conséquent ce n&#8217;est même pas pertinent de&nbsp;vouloir le faire. La valeur du taux de couverture n&#8217;est pas en soit une mesure pertinente de la qualité des tests.<br
/> En effet, Il est important que les équipes chargées du développement ne se focalisent pas sur un chiffre à atteindre mais plutôt sur la qualité des tests effectués.&nbsp;Ainsi, grâce aux informations de couverture de code, les équipes peuvent repérer et corriger rapidement&nbsp;les tests critiques et ne pas s&#8217;attarder à optimiser les tests concernant des parties du code moins importantes.</p><p>Brian Marick, dans <a
href="http://www.testing.com/writings/coverage.pdf">&laquo;&nbsp;How to Misuse Code Coverage&nbsp;&raquo;</a>&nbsp;explique de manière très claire comment bien utiliser la couverture de code dans un projet TDD (Test Driven Development). Je ne vais pas&nbsp;reprendre ici les points abordés, mais il est important de répéter que la mesure de couverture de code ne doit pas être une fin en soi mais un moyen d&#8217;améliorer la qualité globale du code et des tests effectués pour le valider.</p><h3>Liens</h3><ul><li><a
href="http://www.javaranch.com/journal/2004/01/IntroToCodeCoverage.html">JavaRanch : Introduction to code coverage</a></li><li><a
href="http://www.testing.com/">Brian&#8217;s Malick Testing website</a></li><li><a
href="http://www.ericsink.com/articles/Code_Coverage.html">Advocating the use of code coverage</a></li><li><a
href="http://www.topcoder.com/tc?module=Static&amp;d1=features&amp;d2=021307">TopCoder.com : An introduction to code coverage</a></li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2008/02/07/lanalyse-de-couverture-de-code-en-java/feed/</wfw:commentRss> <slash:comments>11</slash:comments> </item> <item><title>Laissez les coder !</title><link>http://blog.xebia.fr/2007/09/06/laissez-les-coder/</link> <comments>http://blog.xebia.fr/2007/09/06/laissez-les-coder/#comments</comments> <pubDate>Thu, 06 Sep 2007 07:30:55 +0000</pubDate> <dc:creator>Benoit Moussaud</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[projet]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2007/09/06/laissez-les-coder/</guid> <description><![CDATA[Les grands intégrateurs ont depuis longtemps institué un chemin d’évolution basé sur la gestion de projets. Les jeunes débutants, le plus souvent de formation ingénieurs, sont rapidement formés à encadrer des équipes de développeurs et à piloter les projets : la voie royale ! Ces dernières années, vue la complexité technique des nouvelles architectures, une [...]]]></description> <content:encoded><![CDATA[<p>Les grands intégrateurs ont depuis longtemps institué un chemin d’évolution basé sur la gestion de projets. Les jeunes débutants, le plus souvent de formation ingénieurs, sont rapidement formés à encadrer des équipes de développeurs et à piloter les projets : la voie royale ! Ces dernières années, vue la complexité technique des nouvelles architectures, une autre voie a émergé : la filière technique. Cette dernière destinée à promouvoir les compétences techniques et d’architectures applicatives, plus en adéquation avec une formation initiale d’ingénieur. Cependant la pression économique impose que ces personnes progressent rapidement vers une spécialité, une expertise (J2EE ; Les bases de données ; La sécurité …) et en les affectant de plus en plus à des missions en parallèle (2 jours chez Untel, 1 jour sur le projet Bidule). Cette rapidité de progression pose toujours le même souci : qui développe réellement l’application ? Très souvent des stagiaires ou, dans le meilleur des cas, de très jeunes embauchés après leur formation.</p><p>Les techniques de développement demandent de plus en plus d’être polyvalent : de l’écriture de requêtes SQL à la présentation des données dans une page JSP. Cette polyvalence ne s’acquière pas en quelques années et nécessite également de rester à jour dans ses connaissances. Alors pourquoi propulser ces personnes dans d’autres fonctions ?</p><p>Intégrer dans une équipe de développement un ou plusieurs développeurs expérimentés permet deux choses :</p><ul><li>Accélérer les phases de codage et de validation technique et fonctionnelle : l’expérience permet de réduire le nombre d’allers retours entre ces deux étapes.</li><li>Créer une continuité de savoir et d’intérêt entre de jeunes débutants et des profils tels que les chefs de projets voire même les architectes.</li></ul><p>Reste à définir la notion d’expérience. Deux composantes rentrent en jeux : les années passées à exercer cette compétence, 3 à 5 ans et surtout le nombre de projets, 5 à 10. Ce dernier point est très important : confronter sa compétence à différents clients, technologies ou architectures est essentiel pour aguerrir les meilleurs développeurs !</p><p>Alors laissez les coder !</p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2007/09/06/laissez-les-coder/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Développement logiciel : Doit on continuer à ignorer l&#8217;évidence ?</title><link>http://blog.xebia.fr/2007/07/26/developpement-logiciel-doit-on-continuer-a-ignorer-levidence/</link> <comments>http://blog.xebia.fr/2007/07/26/developpement-logiciel-doit-on-continuer-a-ignorer-levidence/#comments</comments> <pubDate>Thu, 26 Jul 2007 08:24:35 +0000</pubDate> <dc:creator>Luc Legardeur</dc:creator> <category><![CDATA[Méthodes agiles]]></category> <category><![CDATA[Mot du président]]></category> <category><![CDATA[développement]]></category> <category><![CDATA[Xebia]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2007/07/26/developpement-logiciel-doit-on-continuer-a-ignorer-levidence/</guid> <description><![CDATA[Nous l&#8217;avons constaté encore une fois, une fois de trop sans doute. L&#8217;un de nos clients s&#8217;est mis tout seul (car les responsables d&#8217;une telle situation travaillent bien dans la même entreprise que celle où officie notre interlocuteur) dans une situation perdant/perdant avec son intégrateur dans le cadre d&#8217;un projet développé au forfait. Quels sont [...]]]></description> <content:encoded><![CDATA[<p>Nous l&#8217;avons constaté encore une fois, une fois de trop sans doute. L&#8217;un de nos clients s&#8217;est mis tout seul (car les responsables d&#8217;une telle situation travaillent bien dans la même entreprise que celle où officie notre interlocuteur) dans une situation perdant/perdant avec son intégrateur dans le cadre d&#8217;un projet développé au forfait. Quels sont les faits ? Nous pourrions, sans même lui laisser la parole, nommer une à une toutes les difficultés qu&#8217;il rencontre, les yeux fermés, à la manière d&#8217;un chapelet qu&#8217;on égrène.</p><p>Un Appel d&#8217;Offre a été émis à destination des principaux intégrateurs de la place car selon la Direction des Achats c&#8217;est la seule manière de mener un projet informatique (Nous vous en parlions dans un précédent billet : <em>&laquo;&nbsp;<a
href="http://blog.xebia.fr/2006/12/04/eloge-de-la-qualite/">Eloge de la qualité</a>&laquo;&nbsp;</em>). C&#8217;est, selon les mêmes spécialistes reconnus pour leurs compétences informatiques, un moyen de maîtriser les coûts et les délais, de partager le risque avec l&#8217;entreprise choisie et d&#8217;avoir un moyen de pression (car bien entendu de superbes clauses de pénalités de retard ont été prévues au contrat). L&#8217;intégrateur X est choisi. Pour une grande part, parce qu&#8217;il avait une proposition alléchante et savait accepter, sans piper mot, tous les principes de ce « contrat de confiance ». Le décor est planté : une atmosphère de sérénité, une confiance réciproque, l&#8217;implication des meilleurs profils de la société X, des desiderata d&#8217;utilisateurs pris en compte &#8230;</p><p>Vous vous en doutez, cette dernière phase est ironique.</p><p>Le chef de projet est sous tension, son entreprise lui a affecté 3 stagiaires et demi, un quart d&#8217;Architecte car, après d&#8217;âpres négociations, l&#8217;affaire gagnée se révèle moins rentable que prévu. Et en interne dans la Société X, un projet mal négocié aboutit à l&#8217;affectation de développeurs inexpérimentés (&laquo;&nbsp;You pay peanuts, you get monkeys&nbsp;&raquo;, c&#8217;est bien connu). Les acheteurs du client se frottent les mains, ils ont bien mené leur barque. Le chef de projet est contraint de faire travailler tout le monde plus que de raison pour tenir des délais déraisonnables. Le mot &laquo;&nbsp;pénalité&nbsp;&raquo; se murmure discrètement puis fuse au grand jour dans toutes les réunions de projet, brandi comme la menace suprême. Les services juridiques des deux parties ressortent le contrat et s&#8217;y penchent avec beaucoup d&#8217;intérêt. Tout le monde passe d&#8217;ailleurs plus de temps à voir comment prendre l&#8217;autre partie à défaut que de véritablement essayer de résoudre les problèmes ensemble, en toute bonne foi. Et les utilisateurs dans tout ça ? Ils s&#8217;inquiètent. L&#8217;application qu&#8217;ils ont commandée est critique pour l&#8217;exercice de leur métier. La MOE leur explique qu&#8217;ils ont mal travaillé, que leurs spécifications sont trop floues, que leurs incessantes demandes de modifications sont inacceptables. Ils seraient même pointés du doigt comme deuxième responsable d&#8217;un tel chaos, après l&#8217;intégrateur, bien entendu.</p><p>Ce projet devient vite un puit sans fond. Le client est allé trop loin pour faire machine arrière. On ouvre la boîte de Pandore : les &laquo;&nbsp;avenants&nbsp;&raquo;, le mot est enfin lâché à la grande satisfaction de tous car, in fine, c&#8217;est la seule manière de s&#8217;en sortir la tête haute des deux cotés. Mais attention, l&#8217;usage de tels coups de canif au contrat liant les deux parties doit se faire avec beaucoup de parcimonie bien entendu, car il doit juste permettre à l&#8217;intégrateur de réduire &laquo;&nbsp;un peu&nbsp;&raquo; son déficit sur le projet, là où vraiment on ne peut pas le prendre à défaut. Ce subterfuge lui permet, tout juste, de se maintenir dans un état financier végétatif sur le projet (pertes réduites ou au mieux rentabilité nulle).</p><p>Le paradigme du développement logiciel au forfait repose sur des principes (pour ne pas dire des valeurs) falsifiés, malhonnêtes, utopiques, désuets.</p><p>Les entreprises qui réussissent comme Google, Amazon et Microsoft, pour ne citer que les plus connues, l&#8217;ont compris depuis longtemps. Les méthodes agiles sont une alternative sérieuse. Les principes de ces méthodes sont les suivants :</p><ul><li><strong>Compétence et motivation des équipes</strong><br
/> Le personnel mis à la disposition des projets est du niveau de séniorité nécessaire au développement d&#8217;applications modulaires, évolutives, performantes, documentées, intégrables et exploitables. Ce personnel s&#8217;engage sur le chiffrage et la réalisation de l&#8217;application.</li><li><strong>Développement itératif</strong><br
/> Le développement des projets est itératif. L&#8217;effet tunnel est supprimé. Les modules livrés sont immédiatement opérationnels.</li><li><strong>Un accueil favorable du changement</strong><br
/> La rédaction de spécifications détaillées complètes des mois avant sa livraison se révèle être une utopie. Dans ce contexte, l&#8217;acceptation du changement de priorités, de fonctionnalités fait partie intégrante du processus de développement agile de Xebia.</li><li><strong>Une collaboration étroite avec les utilisateurs</strong><br
/> L&#8217;implication forte du management et des utilisateurs tout au long du projet est organisée autour de feedbacks permanents orchestrés par des démonstrations systématiques qui permettent, à chaque itération, de valider le travail effectué et de définir les priorités de l&#8217;étape suivante.</li><li><strong>Une attention permanente à l&#8217;excellence technique</strong><br
/> L&#8217;hyperspécialisation Java/J2EE permet, dès le début du projet, de mettre en place les meilleures pratiques en vigueur dans l&#8217;industrie et cela à tous les niveaux du projet : Architecture conçue par des Architectes expérimentés, politique d&#8217;intégration continue, tests de performance, gestion des erreurs, exploitabilité des applications, choix technologiques pérennes, &#8230;</li></ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2007/07/26/developpement-logiciel-doit-on-continuer-a-ignorer-levidence/feed/</wfw:commentRss> <slash:comments>18</slash:comments> </item> <item><title>Des closures en Java</title><link>http://blog.xebia.fr/2007/05/10/des-closures-en-java/</link> <comments>http://blog.xebia.fr/2007/05/10/des-closures-en-java/#comments</comments> <pubDate>Thu, 10 May 2007 08:28:14 +0000</pubDate> <dc:creator>Guillaume Bodet</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[closure]]></category> <category><![CDATA[développement]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2007/05/10/des-closures-en-java/</guid> <description><![CDATA[Le débat autour de l&#8217;introduction des closures dans le langage Java fait rage &#8211; avec toute la mesure et l&#8217;absence de pédanterie dont sait faire preuve notre profession sur ce type de sujet. Selon toute vraisemblance, les closures seront l&#8217;une des fonctionnalités phare de Java 7. Reste à savoir sous quelle forme. Deux écoles ont [...]]]></description> <content:encoded><![CDATA[<p>Le débat autour de l&#8217;introduction des closures dans le langage Java fait rage &#8211; avec toute la mesure et l&#8217;absence de pédanterie dont sait faire preuve notre profession sur ce type de sujet. Selon toute vraisemblance, les closures seront l&#8217;une des fonctionnalités phare de Java 7. Reste à savoir sous quelle forme.<br
/> Deux écoles ont émergé sur le sujet : la première, désignée <em><a
href="http://www.javac.info/">BGGA</a></em> (du nom de ses auteurs et promoteurs, Gilad Bracha, Neal Gafter, James Gosling et Peter von der Ahe), propose une extension syntaxique relativement complexe mais permettant d&#8217;introduire dans le langage tous les idiomes nécessaires à un support des closures similaire à celui disponible dans Ruby ou Smalltalk : function types, free variables, blocks, etc. ; la seconde désignée par l&#8217;acronyme <em><a
href="http://docs.google.com/View?docid=k73_1ggr36h">CICE</a></em> (pour Concise Inner Class Expressions) et soutenue par Joshua Bloch, Doug Lea et &laquo;&nbsp;Crazy Bob&nbsp;&raquo; Lee, propose plus modestement une simplification de la syntaxe java dans le but de désinhiber l&#8217;usage des Inner Class en lieu et place des closures.<br
/> L&#8217;occasion de faire ici un point sur ces deux approches et d&#8217;apporter, sinon une pierre à l&#8217;ouvrage, du moins des éclaircissements sur les termes du débat.</p><p>La définition la plus répandue des closures est la suivante (Neal Gafter fournit un historique des closures <a
href="http://gafter.blogspot.com/2007/01/definition-of-closures.html">dans son blog [en]</a>) :</p><p><em>A <strong>closure</strong> is a <strong>function</strong> that captures the <strong>bindings</strong> of free variables in its <strong>lexical context</strong>.<br
/> </em><br
/> En substance, une closure est un bloc de code référençable, manipulant optionnellement des variables dites &laquo;&nbsp;libres&nbsp;&raquo; &#8211; libres en ce sens qu&#8217;elles sont définies non dans le bloc de code, ni de façon globale, mais par le contexte dans lequel le bloc est exécuté.</p><p>Un peu de code valant souvent mieux qu&#8217;un long discours, voici un exemple très simple d&#8217;utilisation des closures en Ruby (Ruby fait un usage intensif des closures, ce qui est probablement à l&#8217;origine de l&#8217;affection que lui porte ses utilisateurs). Cet exemple est tiré de<a
href="http://www.martinfowler.com/bliki/Closure.html"> l&#8217;article de Martin Fowler sur le sujet [en]</a>.</p><p>Imaginons que vous souhaitiez extraire d&#8217;une liste d&#8217;employés une sous-liste comprenant uniquement ceux qui sont cadres. Une implémentation possible en java est la suivante :</p><pre class="brush: java; title: ; notranslate">
public static List&lt;Employee&gt; managers(List&lt;Employee&gt; employees) {
  List&lt;Employee&gt; result = new ArrayList&lt;Employee&gt;();
  for (Employee e : employees)
    if (e.isManager) result.add(e);
  return result;
}
</pre><p>En Ruby, la même fonction serait codée de la sorte :</p><pre class="brush: ruby; title: ; notranslate">
def managers(employees)
  return employees.select {|e| e.isManager}
end
</pre><p>La méthode <code>select</code> est définie dans la classe Collection de Ruby. Elle prend en paramètre un bloc de code &#8211; la fameuse closure &#8211; , défini entre accolades. Si le bloc de code prend des arguments, ces derniers sont  déclarés entre deux barres verticales (ce sont les variables &laquo;&nbsp;libres&nbsp;&raquo;, dont la portée est définie par le contexte). La méthode <code>select</code> encapsule l&#8217;algorithmique : itération sur la collection, exécution du bloc de code pour chaque élément et construction d&#8217;une sous-liste contenant les éléments pour lesquels le bloc de code s&#8217;évalue à true.</p><p>Il existe bien sûr en Java un mécanisme assez similaire, autorisé par l&#8217;usage de classes anonymes imbriquées (Anonymous Inner Class, ou AIC).<br
/> Supposons que la classe <code>List</code> possède, symétriquement à sa contre-partie Ruby, une méthode <code>select</code>, prenant en paramètre une interface définie comme suit :</p><pre class="brush: java; title: ; notranslate">
interface Filter&lt;T&gt;(){
  public boolean accept(T e);
}
</pre><p>Alors le code java pourrait s&#8217;écrire comme suit :</p><pre class="brush: java; title: ; notranslate">
public static List&lt;Employee&gt; managers(List&lt;Employee&gt; employees) {
  return employees.select(
    new Filter&lt;Employee&gt; {
      public boolean accept(Employee e){
        return e.isManager();
      }
    }
  );
}
</pre><p>Les AIC sont l&#8217;avatar traditionnel des closures en java. Les API du JDK en font largement usage (pensez aux interfaces <code>Runnable</code>, <code>Comparable</code>, <code>Callable</code> ou <code>TimerTask</code>, qui fournissent des closures aux classes <code>Thread</code> ou <code>Executor</code>, ou encore permettent de configurer les collections). Les Design Patterns callbacks, factories, predicates ou stategies sont des candidats naturels à ce type de construction. Spring, en particulier dans son framework de templates, les exploite à très bon escient pour masquer le caractère fastidieux de certaines API, JDBC en particulier.</p><p>Pour autant, la programmation par closures, très répandue chez les rubyistes, est quasiment inexistante chez les programmeurs java. Le listing ci-dessus fournit une première explication : la syntaxe des AIC, verbeuse à souhait, est propre à décourager les meilleures intentions. Ensuite, le support des AIC dans Java comme mécanisme de closures souffre de sévères limitations : une fois instanciée, une AIC est un objet à part entière, doté d&#8217;une portée qui lui est propre, et partiellement aveugle au contexte qui l&#8217;a créé. En conséquence, les variables ou méthodes sont résolues dans le portée de ce nouvel objet.</p><p>Examinons, pour illustrer ces quelques lignes de Ruby :</p><pre class="brush: ruby; title: ; notranslate">
i = 1;
1.upto(100) { |num| i *= num; }
puts i;
</pre><p>Ce code permet d&#8217;afficher le factoriel de 100. La variable <code>i</code> est définie en dehors de la closure et modifiée dans son corps. Le code java équivalent ne compilerait pas (à supposer qu&#8217;une API similaire soit disponible). En effet, pour qu&#8217;une variable externe soit accédée dans le corps d&#8217;une AIC, elle doit être déclarée <code><em>final</em></code> (la raison sous-jacente est que la variable est copiée dans le contexte de l&#8217;AIC). Elle ne peut donc être modifiée. Cela ne pose pas de difficulté si la variable est d&#8217;un type <em>mutable</em> (comme une liste ou un wrapper), mais peut se révéler problématique s&#8217;il s&#8217;agit d&#8217;un type <em>immutable</em> ou d&#8217;un <em>value type</em> (en particulier les String et les types primaires).</p><p>Voici un exemple type des contorsions nécessaires en java pour contourner cette contrainte :</p><pre class="brush: java; title: ; notranslate">
final int[] numCompares = new int[1];
Arrays.sort(a, new Comparator&lt;Integer&gt;() {
  public int compare(Integer i1, Integer i2) {
    numCompares[0]++;
    return i1.compareTo(i2);
  }
});
System.out.println(numCompares[0]);
</pre><p>C&#8217;est sur ce constat primordial que la proposition <a
href="http://docs.google.com/View?docid=k73_1ggr36h">CICE</a> a vu le jour. Son objet n&#8217;est pas d&#8217;altérer le langage java mais de rendre plus concise la syntaxe de création des AIC (en fait, un sous-ensemble de ces dernières, appelées <em>single-abstract-method-types</em> ou SAM, qui sont en substance des interfaces ne comprenant qu&#8217;une unique méthode).</p><p>Avec la syntaxe proposée par CICE, notre exemple initial serait réécrit de la sorte :</p><pre class="brush: java; title: ; notranslate">
public static List&lt;Employee&gt; managers(List&lt;Employee&gt; employees) {
  return employees.select(
    Filter&lt;Employee&gt; { return element.isManager();}
  );
}
</pre><p>Cette syntaxe dite concise est complétée par une altération des règles d&#8217;accès aux variables locales du bloc appelant (en particulier, celles déclarées explicitement publiques peuvent être assignées dans le corps de l&#8217;AIC).</p><p>Cette approche a bien sûr le mérite de la simplicité puisqu&#8217;ellle n&#8217;introduit aucun nouveau concept et ne nécessite pas la réécriture des API existantes ; elle pourrait de surcroît probablement être implémentée à l&#8217;aide d&#8217;une modification mineure du compilateur.</p><p>Pour certains, cependant, l&#8217;approche CICE ne fait qu&#8217;effleurer le sujet et ne propose qu&#8217;une mise à disposition minimaliste des closures en Java.</p><p>En effet, d&#8217;une façon générale, les AIC rompent le contexte d&#8217;exécution du code appelant et ne permettent pas de conserver la sémantique d&#8217;un nombre important de structures syntaxiques (celles que Neal Gafter appelle &laquo;&nbsp;lexically scoped language constructs&nbsp;&raquo;) :</p><ul><li>noms des variables</li><li>noms des méthodes</li><li>noms des types</li><li>signification de <code>this</code></li><li>noms des labels</li><li>référent d&#8217;une instruction <code>break</code> sans label</li><li>référent d&#8217;une instruction <code>continue</code> sans label</li><li><code>checked exceptions</code> déclarées ou interceptées</li><li>référent d&#8217;une instruction <code>return</code></li><li>et quelques autres plus exotiques (état d&#8217;assignation des variables, <code>reachability</code>, …)</li></ul><p>La proposition BGGA est une extension du langage java permettant la mise en oeuvre des closures sans rupture de transparence. La syntaxe proposée par BGGA est loin d&#8217;être triviale, ce qui lui vaut de nombreuses critiques – ses contempteurs sont au demeurant souvent les partisans de CICE.</p><p>Sans entrer dans le détail (qui peut être consulté sur le site <a
href="http://www.javac.info/">http://www.javac.info/</a>), on retiendra les caractéristiques suivantes :</p><ul><li>BGGA définit une syntaxe permettant de déclarer des closures littérales ressemblant à ça :<pre class="brush: cpp; title: ; notranslate">
{int x, int y &gt; x+y}
</pre></li><li>BGGA définit également le concept des <em>function type</em>, dans lequel une fonction possède une liste d&#8217;arguments, un type de retour et une clause <em>throws</em> ; on peut instancier un <em>function type</em> à l&#8217;aide d&#8217;une closure littérale compatible</li><li>la transparence lexicale est intégralement garantie, tant pour l&#8217;association de variables que pour la résolution de noms ou les structures de contrôle (<code><em>break</em></code>, <code><em>continue</em></code>, <code><em>return</em></code>) – certaines closures peuvent cependant être marquées <em>restricted</em> et se comporter de façon identiques aux AIC</li><li>BGGA propose une grammaire simplifiée permettant d&#8217;exploiter les closures selon une syntaxe très proche de celle des structures de contrôle natives du langage – avec une telle syntaxe, la modification du langage pour introduire la seconde forme de boucle <code><em>for </em></code>aurait été superflue</li></ul><p>Avec BGGA, l&#8217;algorithme suivant</p><pre class="brush: java; title: ; notranslate">
lock.lock();
try {
  ++counter;
}
finally {
  lock.unlock();
}
</pre><p>deviendrait</p><pre class="brush: java; title: ; notranslate">
withLock(lock, {=&gt;
  ++counter;
});
</pre><p>dans la forme canonique et, dans la forme simplifiée :</p><pre class="brush: java; title: ; notranslate">
withLock(lock) {
  ++counter;
}
</pre><p>Notre exemple initial pourrait être codé de la sorte (pour peu que l&#8217;API de Collection java se dote d&#8217;une méthode <code>select </code>appropriée) :</p><pre class="brush: java; title: ; notranslate">
public static List&lt;Employee&gt; managers(List&lt;Employee&gt; employees) {
  return employees.select({Employee e =&gt; e.isManager()});
}
</pre><p>Comme évoqué plus haut, la médaille a un revers. Si côté client l&#8217;utilisation des closures BGGA semble relativement simple, la syntaxe risque de considérablement complexifier les API du JDK ou des frameworks désireux d&#8217;en tirer parti. Pour se donner une idée, voici le code de la fonction withLock utilisée plus haut :</p><pre class="brush: java; title: ; notranslate">
public static &lt;T,throws E extends Exception&gt;
T withLock(Lock lock, {=&gt;T throws E} block) throws E {
  lock.lock();
  try {
    return block.invoke();
  } finally {
    lock.unlock();
  }
}
</pre><p>A l&#8217;instar des génériques, qui ont rendu presqu&#8217;illisible au commun le code source de certaines classes du JDK, une telle évolution syntaxique risque de dresser une barrière supplémentaire à l&#8217;apprentissage du langage java, et creuser encore davantage le fossé entre l&#8217;utilisateur d&#8217;API (<em>Joe Java</em>, comme disent certains) et le concepteur d&#8217;API. Le jeu en vaut-il la chandelle ?</p><p>Références (toutes en anglais) :<br
/> [1] <a
href="http://www.javac.info/">La proposition BGGA</a>, présentée par Neal Gafter aux <a
href="http://video.google.com/videoplay?docid=4051253555018153503">Google TechTalks</a><br
/> [2] <a
href="http://docs.google.com/View?docid=k73_1ggr36h">L&#8217;approche CICE</a><br
/> [3] <a
href="http://crazybob.org/2006/10/java-closure-spectrum.html">Un très intéressant échange</a> sur le site de Crazy Bob, partisan de CICE<br
/> [4] Deux articles parus sur developerWorks, le <a
href="http://www-128.ibm.com/developerworks/java/library/j-cb01097.html">premier</a> sur les closures en général, et le <a
href="http://www-128.ibm.com/developerworks/java/library/j-jtp04247.html">second</a> plus spécifiquement sur le débat BGGA vs CICE.<br
/> [5] Un<a
href="http://fishbowl.pastiche.org/2003/05/16/closures_and_java_a_tutorial"> article plus ancien</a>, sur l&#8217;approche traditionnelle des closures en java</ul> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2007/05/10/des-closures-en-java/feed/</wfw:commentRss> <slash:comments>9</slash:comments> </item> <item><title>Codez comme des filles !</title><link>http://blog.xebia.fr/2007/04/11/codez-comme-des-filles/</link> <comments>http://blog.xebia.fr/2007/04/11/codez-comme-des-filles/#comments</comments> <pubDate>Wed, 11 Apr 2007 08:11:36 +0000</pubDate> <dc:creator>Guillaume Bodet</dc:creator> <category><![CDATA[Java / JEE]]></category> <category><![CDATA[développement]]></category> <guid
isPermaLink="false">http://blog.xebia.fr/2007/04/11/codez-comme-des-filles/</guid> <description><![CDATA[Vous avez sûrement déjà , au moins une fois, en parcourant les sources d&#8217;un logiciel open source été impressionné par la qualité formelle du code : nommage efficace et pertinent, choix judicieux des paquets et de leur hiérarchie, émergence des concepts au travers de la structure, javadoc riche et dénuée de fautes d&#8217;orthographe&#8230; même si [...]]]></description> <content:encoded><![CDATA[<p>Vous avez sûrement déjà , au moins une fois, en parcourant les sources d&#8217;un logiciel open source été impressionné par la qualité formelle du code : nommage efficace et pertinent, choix judicieux des paquets et de leur hiérarchie, émergence des concepts au travers de la structure, javadoc riche et dénuée de fautes d&#8217;orthographe&#8230; même si j&#8217;embellis un peu le tableau pour les besoins du propos, on oublierait pour un peu qu&#8217;il s&#8217;agit du même language que celui utilisé pour développer ce logiciel de gestion aux sources cryptiques dont vous avez hérité la maintenance. En fait, le code source de certains projets open source évoque fortement les fiches bristol que les filles confectionnaient au lycée pour simplifier leurs révisions, vous vous souvenez ? Ces précieuses fiches dont, pour tout dire, vous vous moquiez un peu, mais qui rendaient limpides les cours du semestre précédent quand vous même n&#8217;arriviez plus à  relire vos propres notes. Jamais vu un garçon faire des fiches bristol bariolées. C&#8217;est un truc génétique, je crois, une séquence codante dans la branche manquante du chromosome Y, qui interdit aux mâles l&#8217;usage de l&#8217;encre turquoise et celle de la règle pour souligner les titres.</p><p>La coloration syntaxique et le formattage automatique des sources proposés par les IDE modernes permettent bien sûr, à  peu de frais, de donner au code java l&#8217;apparence formelle des fiches bristol (ce qui, au passage, devrait les réhabiliter définitivement et couvrir de honte rétrospectives les anciens moqueurs). Mais ce travail d&#8217;enluminure textuelle n&#8217;est que rarement suffisant pour masquer la faiblesse des choix de nommage ou l&#8217;insuffisance des commentaires &#8211; quand ce n&#8217;est pas l&#8217;indigence des concepts architecturaux. Pire : il les met en exergue.</p><p>Laissé à lui-même, le développeur mâle standard tend à  se laisser aller à  une négligence peccamineuse, proche dans le principe de la façon dont il prenait ses notes au lycée, et qui transforme rapidement le code source le mieux intentionné en jachère sémantique &#8211; et en cauchemar esthétique. Etant moi même l&#8217;auteur de certaines des lignes de codes les plus vilaines jamais produites dans l&#8217;hémisphère nord (il m&#8217;arrive de faire du Perl), je m&#8217;interroge : pourquoi les développeurs open source (du moins certains) échappent-il à la malédiction ?</p><p>La raison en est probablement très simple : lorsque l&#8217;on développe du code open source, on s&#8217;expose à ce que les sources soient consultées par un nombre indéterminé de pairs à  l&#8217;oeil expert, dont le jugement critique fera ou défera le succès du projet. Alors on tente de produire le plus beau code possible. On renomme 6 fois cette variable. On élague par refactoring. On épure l&#8217;architecture. On formatte la javadoc, un espace ici, un saut de ligne là , et là  une liste à  puces&#8230; Bref, on code comme une fille. C&#8217;est une bête question d&#8217;orgueil, la soif ancestrale de reconnaissance, le besoin de séduire.</p><p>Il resterait, bien sûr, à  tenter de trouver une définition un tant soit peu opérationnelle de ce qu&#8217;est du <em>beau code</em> ; je ne m&#8217;y essaierai pas : les philosophes s&#8217;étripent (symboliquement, bien sûr, ce sont des gens d&#8217;une extrême mesure) depuis des siècles pour définir le <a
href="http://fr.wikipedia.org/wiki/Beau">Beau</a>, et les programmeurs leur ont, à  leur modeste échelle, emboité le pas [1]. Ce qui est sûr, c&#8217;est que la plupart des développeurs ont un jugement très sûr et savent reconnaître le beau code quand ils viennent à  le croiser (ils utilisent le plus souvent le qualificatif d&#8217;<em>élégant</em> pour le désigner). Il arrive même à  certains d&#8217;en produire&#8230; Et s&#8217;il est une vertu que chacun s&#8217;accorde à  trouver au <em>beau</em> code, c&#8217;est qu&#8217;il est facile à  comprendre, à  maintenir et à  utiliser. Des caractéristiques constitutives d&#8217;un logiciel de qualité.</p><p>Certaines pratiques clés de l&#8217;eXtreme Programming, en particulier le <em>pair programming</em> ou les revues de code systématiques ont pour vocation de stimuler en permanence l&#8217;orgueil esthétique des programmeurs en soumettant leur production au regard critique de leurs pairs. Ce n&#8217;est pas pour plomber la productivit des codeurs. C&#8217;est pour qu&#8217;ils produisent du beau code. C&#8217;est pour qu&#8217;ils codent comme des filles !</p><hr
/>[1] Pour d&#8217;autres considérations sur la féminité du code, consultez l&#8217;excellent post de Kathy Sierra <span
class="nobr"><a
rel="nofollow" href="http://headrush.typepad.com/creating_passionate_users/2006/03/code_like_a_gir.html">Coding like a Girl [en]</a></span>.</p><p></p> ]]></content:encoded> <wfw:commentRss>http://blog.xebia.fr/2007/04/11/codez-comme-des-filles/feed/</wfw:commentRss> <slash:comments>1</slash:comments> </item> </channel> </rss>
