<?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/"
	>

<channel>
	<title>Le Touilleur Express &#187; jsf</title>
	<atom:link href="http://www.touilleur-express.fr/tag/jsf/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.touilleur-express.fr</link>
	<description>Blog sur Java, le métier de développeur et la vie de freelance par Nicolas Martignole</description>
	<lastBuildDate>Wed, 08 Feb 2012 11:54:37 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Comparaison de l&#039;approche JSF 2.0 et Play! Framework pour du CRUD</title>
		<link>http://www.touilleur-express.fr/2010/07/27/comparaison-de-lapproche-jsf-2-0-et-play-framework-pour-du-crud/</link>
		<comments>http://www.touilleur-express.fr/2010/07/27/comparaison-de-lapproche-jsf-2-0-et-play-framework-pour-du-crud/#comments</comments>
		<pubDate>Tue, 27 Jul 2010 07:45:00 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[jsf]]></category>
		<category><![CDATA[play]]></category>

		<guid isPermaLink="false">http://www.touilleur-express.fr/?p=4062</guid>
		<description><![CDATA[Soyons décontracté pendant l&#8217;exercice qui va suivre. Dire du bien ou dire du mal, c&#8217;est facile. On peut passer des heures sur des blogs à s&#8217;envoyer des commentaires à la figure, ou sinon on peut prendre son temps pour regarder les choses. Moi vous voyez, je suis un gars avec un esprit ouvert. Il y a 3 ou 4 ans je me serai coupé un bras pour vous faire apprendre JSF à la place de Struts. Je me suis ensuite coupé un deuxième bras pour vous parler de Seam et ...]]></description>
			<content:encoded><![CDATA[<p>Soyons décontracté pendant l&#8217;exercice qui va suivre. Dire du bien ou dire du mal, c&#8217;est facile. On peut passer des heures sur des blogs à s&#8217;envoyer des commentaires à la figure, ou sinon on peut prendre son temps pour regarder les choses. Moi vous voyez, je suis un gars avec un esprit ouvert. Il y a 3 ou 4 ans je me serai coupé un bras pour vous faire apprendre JSF à la place de <a href="http://struts.apache.org/">Struts</a>. Je me suis ensuite coupé un deuxième bras pour vous parler de <a href="http://seamframework.org/">Seam </a>et de la gestion de la conversation, par rapport aux soucis des premiers frameworks Webs. Je vous ai bassiné les oreilles avec <a href="http://www.touilleur-express.fr/2009/12/19/grails-en-quelques-mots/">Grails</a> l&#8217;hiver dernier, et en ce moment je ne vous parle que de <a href="http://www.playframework.org">Play! Framework</a>. Donc dans la logique des choses, je devrai tomber amoureux d&#8217;un nouveau framework PouetPouet d&#8217;ici quelques semaines. En attendant, et là je me sens à l&#8217;aise dans mes baskets, je peux vous parler de ce que j&#8217;observe. Quand je dis &laquo;&nbsp;<strong>JE</strong>&nbsp;&raquo; cela veut dire que j&#8217;ai eu le temps et l&#8217;occasion de tester ce que je vois sur de vrais projets.</p>
<p>Il n&#8217;y a pas de mauvais frameworks Webs. Selon votre projet je vous encourage à tester différentes approches et à évoluer selon vos besoins fonctionnels. Essayez aussi de prototyper avec un outil comme <a href="http://www.balsamiq.com/">Balsamiq</a> ou <a href="http://www.irise.com/">iRise Studio</a> par exemple.</p>
<p>Pensez incrément lorsque vous faîtes du Web. Essayez de construire par petites étapes votre solution, et faites valider par l&#8217;utilisateur final les écrans. Le plus important : un logiciel qui marche et une interface qui répond à la demande du client. C&#8217;est cela qui doit vous guider pendant votre développement.</p>
<h3>And now&#8230; JSF 2 and Play! Framework</h3>
<p>Si vous souhaitez suivre le code de cet article, je vous encourage à suivre<a href="http://www.touilleur-express.fr/2010/07/25/generer-une-application-type-crud-en-jsf-2-0-avec-netbeans-6-9/"> le petit tutorial que j&#8217;ai écris sur NetBeans 6.9</a>. Cela vous permettra de générer rapidement une petite application en JSF 2 et Java EE 6 pour la partie Bean. Vous pouvez télécharger le code complet de la partie JSF <a href="http://www.touilleur-express.fr/divers/jsf/">ici</a> afin de pouvoir suivre simplement la discusssion.</p>
<p>Pour écrire cet article, j&#8217;ai écris la partie Play! Framework après la partie JSF 2.0. Je vous guiderai donc sur la partie Play! Framework.</p>
<p><strong>Création d&#8217;une application avec Play! Framework</strong><br />
- Télechargez Play! Framework sur <a href="http://www.playframework.org/">le site de Play!</a><br />
- Décompressez l&#8217;archive, ajoutez ensuite le répertoire de play à votre PATH.<br />
- Ouvrez un terminal, dans un répertoire de test, tapez &laquo;&nbsp;<em>play</em>&nbsp;&raquo; afin de vérifier qu&#8217;il est bien installé. J&#8217;ai utilisé la version 1.0.3<br />
<code><br />
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$ play<br />
~        _            _<br />
~  _ __ | | __ _ _  _| |<br />
~ | '_ \| |/ _' | || |_|<br />
~ |  __/|_|\____|\__ (_)<br />
~ |_|            |__/<br />
~<br />
~ play! 1.0.3, http://www.playframework.org<br />
~ framework ID is macbookpro<br />
~<br />
~ Usage: play cmd [app_path] [--options]<br />
~<br />
~ with,  new      Create a new application<br />
~        run      Run the application in the current shell<br />
~        help     Show play help<br />
~<br />
</code></p>
<p>- Tapez ensuite &laquo;&nbsp;<em>play new micromarket</em>&nbsp;&raquo; afin de créer une application de test</p>
<p><code><br />
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$ play new micromarket<br />
~        _            _<br />
~  _ __ | | __ _ _  _| |<br />
~ | '_ \| |/ _' | || |_|<br />
~ |  __/|_|\____|\__ (_)<br />
~ |_|            |__/<br />
~<br />
~ play! 1.0.3, http://www.playframework.org<br />
~ framework ID is macbookpro<br />
~<br />
~ The new application will be created in /Users/nicolas/NetBeansProjects/micromarket<br />
~ What is the application name? micromarket<br />
~<br />
~ OK, the application is created.<br />
~ Start it with : play run micromarket<br />
~ Have fun!<br />
~<br />
macbook-pro-de-nicolas-martignole:NetBeansProjects nicolas$</p>
<p></code></p>
<p>- Placez vous dans le répertoire micromarket puis tapez <em>play run</em> et vérifiez que la page de démarrage de Play! se lance sur <a href="http://localhost:9000/">http://localhost:9000/</a></p>
<p>Bravo, vous avez créé une application Play.</p>
<p><strong>Entité JPA</strong><br />
Vous pouvez prendre l&#8217;entité JPA MicroMarket du projet NetBeans et la placer dans le répertoire de Play!, cela fonctionne. En effet, il utilise JPA et Hibernate, donc pour lui, cela ne change pas grand chose. Voici le code initial de l&#8217;entité MicroMarket avant modification :</p>
<pre class="brush:java">

package org.letouilleur.demo;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
 *
 * @author nicolas
 */
@Entity
@Table(name = "MICRO_MARKET")
@NamedQueries({
    @NamedQuery(name = "MicroMarket.findAll", query = "SELECT m FROM MicroMarket m"),
    @NamedQuery(name = "MicroMarket.findByZipCode", query = "SELECT m FROM MicroMarket m WHERE m.zipCode = :zipCode"),
    @NamedQuery(name = "MicroMarket.findByRadius", query = "SELECT m FROM MicroMarket m WHERE m.radius = :radius"),
    @NamedQuery(name = "MicroMarket.findByAreaLength", query = "SELECT m FROM MicroMarket m WHERE m.areaLength = :areaLength"),
    @NamedQuery(name = "MicroMarket.findByAreaWidth", query = "SELECT m FROM MicroMarket m WHERE m.areaWidth = :areaWidth")})
public class MicroMarket implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "ZIP_CODE")
    private String zipCode;
    @Column(name = "RADIUS")
    private Double radius;
    @Column(name = "AREA_LENGTH")
    private Double areaLength;
    @Column(name = "AREA_WIDTH")
    private Double areaWidth;

    public MicroMarket() {
    }

    public MicroMarket(String zipCode) {
        this.zipCode = zipCode;
    }

    public String getZipCode() {
        return zipCode;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public Double getRadius() {
        return radius;
    }

    public void setRadius(Double radius) {
        this.radius = radius;
    }

    public Double getAreaLength() {
        return areaLength;
    }

    public void setAreaLength(Double areaLength) {
        this.areaLength = areaLength;
    }

    public Double getAreaWidth() {
        return areaWidth;
    }

    public void setAreaWidth(Double areaWidth) {
        this.areaWidth = areaWidth;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (zipCode != null ? zipCode.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof MicroMarket)) {
            return false;
        }
        MicroMarket other = (MicroMarket) object;
        if ((this.zipCode == null &#038;&#038; other.zipCode != null) || (this.zipCode != null &#038;&#038; !this.zipCode.equals(other.zipCode))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]";
    }

}
</pre>
<p>A cet instant, je vais vous proposer une approche un peu différente. J&#8217;insiste <strong>lourdement</strong> sur le fait que ce n&#8217;est pas obligatoire mais intéressant. Play! propose d&#8217;utiliser une approche orientée DDD où les objets du domaine ne sont pas anémiques, mais capables d&#8217;effectuer des services simples. Pour cela, le pattern que j&#8217;utilise est de donner une référence à ce que l&#8217;on appelle le &laquo;&nbsp;Repository&nbsp;&raquo; dans DDD, et de l&#8217;injecter dans mon Entité. Grâce à une classe générique, dans laquelle l&#8217;EntityManager sera injecté, et dans laquelle je vais déclarer mes méthodes standards type CRUD, toutes mes Entités pourront alors exécuter du code d&#8217;accès au Repository.</p>
<p>Ce que nous allons voir n&#8217;est pas mieux ou moins bien que l&#8217;approche services/par couche que vous connaissez tous. J&#8217;ai envie de vous dire <em>de laisser tomber les couches et de grandir</em> pour faire un bon mot.</p>
<p>Play! propose (n&#8217;impose pas) d&#8217;étendre soit la classe <a href="http://www.playframework.org/@api/play/db/jpa/JPASupport.html">JPASupport</a>, soit la classe Model, afin que l&#8217;accès à l&#8217;EntityManager soit injecté par le framework. C&#8217;est un couplage fort, mais sur la modélisation du domaine ce n&#8217;est pas un souci. Je n&#8217;ai pas vu de projets avec de l&#8217;héritage d&#8217;Entité, et j&#8217;ai souvent vu que les problèmes de composition ou d&#8217;agrégation étaient résolus sans faire appel à de l&#8217;héritage de classes.</p>
<p>J&#8217;effectue d&#8217;abord 3 modifications : le package, un import en plus et l&#8217;extends :</p>
<pre class="brush:java">
// Fichier micromarket/app/models/org/letouilleur/demo/MicroMarket.java
// dans le répertoire de l'appli Play!
// Changement 1
package models.org.letouilleur.demo;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

// Changement 2
import play.db.jpa.*;

@Entity
@Table(name = "MICRO_MARKET")
@NamedQueries({
    @NamedQuery(name = "MicroMarket.findAll", query = "SELECT m FROM MicroMarket m"),
    @NamedQuery(name = "MicroMarket.findByZipCode", query = "SELECT m FROM MicroMarket m WHERE m.zipCode = :zipCode"),
    @NamedQuery(name = "MicroMarket.findByRadius", query = "SELECT m FROM MicroMarket m WHERE m.radius = :radius"),
    @NamedQuery(name = "MicroMarket.findByAreaLength", query = "SELECT m FROM MicroMarket m WHERE m.areaLength = :areaLength"),
    @NamedQuery(name = "MicroMarket.findByAreaWidth", query = "SELECT m FROM MicroMarket m WHERE m.areaWidth = :areaWidth")})

// Changement 3
public class MicroMarket extends JPASupport implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "ZIP_CODE")
    private String zipCode;
    @Column(name = "RADIUS")
    private Double radius;
    @Column(name = "AREA_LENGTH")
    private Double areaLength;
    @Column(name = "AREA_WIDTH")
    private Double areaWidth;

    public MicroMarket() {
    }

    public MicroMarket(String zipCode) {
        this.zipCode = zipCode;
    }

    public String getZipCode() {
        return zipCode;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public Double getRadius() {
        return radius;
    }

    public void setRadius(Double radius) {
        this.radius = radius;
    }

    public Double getAreaLength() {
        return areaLength;
    }

    public void setAreaLength(Double areaLength) {
        this.areaLength = areaLength;
    }

    public Double getAreaWidth() {
        return areaWidth;
    }

    public void setAreaWidth(Double areaWidth) {
        this.areaWidth = areaWidth;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (zipCode != null ? zipCode.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof MicroMarket)) {
            return false;
        }
        MicroMarket other = (MicroMarket) object;
        if ((this.zipCode == null &#038;&#038; other.zipCode != null) || (this.zipCode != null &#038;&#038; !this.zipCode.equals(other.zipCode))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]";
    }

}
</pre>
<p>Ensuite, éditez le fichier micromarket/conf/application.conf de Play! afin d&#8217;activer une base de données. Nous allons utiliser une base en mémoire. Pas besoin de s&#8217;embêter avec le schéma, car Play! va créer une table MicroMarket identique à notre spécification, sans même avoir besoin de relancer le serveur.</p>
<p>A cet instant, si vous sauvez et que vous rechargez votre page, Play! compile le code, et vous serez notifié d&#8217;éventuelles erreurs. Pour l&#8217;instant nous n&#8217;avons pas d&#8217;entrées dans notre base, ni de page. Il faut continuer.</p>
<p>Allons encore plus loin. Les NamesQuery sont utilisables, mais je vais utiliser des fonctions de Play! afin d&#8217;avoir le support de la pagination intégré, sans devoir écrire cette logique dans une autre classe. Cela équivaut au code utilisant PaginationHelper de la classe MicroMarketController. Je n&#8217;invente rien. Avec ce que je vous montre nous n&#8217;avons pas besoin de déclarer ce système de PaginationHelper comme dans la version JSF, puisque c&#8217;est Play! qui vous le donne. Deuxième modification : je passe en public les attributs, puisque les getters/setters ne me servent à rien ici.</p>
<p>L&#8217;entité au final est donc encore plus simple :</p>
<pre class="brush:java">
package models.org.letouilleur.demo;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import play.db.jpa.*;
import java.util.*;

@Entity
@Table(name = "MICRO_MARKET")
public class MicroMarket extends JPASupport implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "ZIP_CODE")
    public String zipCode;
    @Column(name = "RADIUS")
    public Double radius;
    @Column(name = "AREA_LENGTH")
    public Double areaLength;
    @Column(name = "AREA_WIDTH")
    public Double areaWidth;

    public MicroMarket() {
    }

    public MicroMarket(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (zipCode != null ? zipCode.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof MicroMarket)) {
            return false;
        }
        MicroMarket other = (MicroMarket) object;
        if ((this.zipCode == null &#038;&#038; other.zipCode != null) || (this.zipCode != null &#038;&#038; !this.zipCode.equals(other.zipCode))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "org.letouilleur.demo.MicroMarket[zipCode=" + zipCode + "]";
    }

    /** Retourne la liste des MicroMarket en utilisant la méthode find hérité de JPASupport
      * zipCode : critere de recherche
      * page: numéro de la page à charger, si vous affichez un tableau paginé par exemple
      * length: taille de la page, par exemple 10 pour charger 10 entrées
      */
    public List&lt;Micromarket&gt; findByZipCode(String zipCode, int page, int length) {
        return  find("from MicroMarket m where m.zipcode like :p").bind("p",zipCode).fetch(page,length);
    }

   //.. autre finder
}
</pre>
<p>Voilà, comme vous pouvez le constater, ce n&#8217;est pas une révolution, simplement une approche différente pour le domaine.</p>
<h3>La partie Java EE 6</h3>
<p>Pour commencer, je vais regarder comment fonctionne la page qui donne la liste des Entités, avec la pagination. Dans la version JSF, nous avons un ManagedBean, la class MicroMarketController. Celui-ci utilise un EJB Stateless, MicroMarketFacade, qui donne accès à l&#8217;EntityManager, un DAO en quelque sorte.</p>
<pre class="brush:java">
public abstract class AbstractFacade&lt;T&gt; {
    private Class&lt;T&gt; entityClass;

    public AbstractFacade(Class&lt;T&gt; entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
    }

    public void edit(T entity) {
        getEntityManager().merge(entity);
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List&lt;T&gt; findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List&lt;T&gt; findRange(int[] range) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root&lt;T&gt; rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

}
</pre>
<p>Sa spécialisation en classe MicroMarketFacade est super simple et propre :</p>
<pre class="brush:java">
package org.letouilleur.demo;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class MicroMarketFacade extends AbstractFacade&lt;Micromarket&gt; {
    @PersistenceContext(unitName = "WebApplication1PU")
    private EntityManager em;

    protected EntityManager getEntityManager() {
        return em;
    }

    public MicroMarketFacade() {
        super(MicroMarket.class);
    }

}
</pre>
<p>Jusqu&#8217;ici, on voit qu&#8217;avec 2 classes et Java EE 6 vous pouvez faire l&#8217;équivalent de ce que vous avez l&#8217;habitude de faire avec Spring par exemple. Je trouve le code simple et efficace.</p>
<h3>JSF et la partie ManagedBean</h3>
<p>Attention là c&#8217;est un poil plus compliqué. La classe MicroMarketController est un ManagedBean JSF, capable de gérer la pagination. Le moins que l&#8217;on puisse dire, c&#8217;est que ce n&#8217;est pas simple.<br />
Pour être plus équitable, j&#8217;ai retiré la pagination, et l&#8217;on voit alors que le code est simple, pas plus compliqué que la version Play! Framework. La version originale est plus complexe, avec la gestion de la pagination et un Converter pour retenir l&#8217;élément sélectionné. S&#8217;il y a possibilité de critiquer JSF, c&#8217;est sur cette partie, que je vous laisse regarder dans le fichier original.</p>
<pre class="brush:java">
package org.letouilleur.demo;

import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

@ManagedBean (name="microMarketController")
@SessionScoped
public class MicroMarketController {
    private DataModel items = null;
    @EJB private org.letouilleur.demo.MicroMarketFacade ejbFacade;

    public MicroMarketController() {
    }

    private MicroMarketFacade getFacade() {
        return ejbFacade;
    }

    public String prepareList() {
        recreateModel();
        return "List";
    }

    public DataModel getItems() {
        if (items == null) {
            items = new ListDataModel(getFacade().findAll());
        }
        return items;
    }

    private void recreateModel() {
        items = null;
    }

}
</pre>
<p>Et si nous écrivions le controleur de la version Play ? Vous êtes prêt ?</p>
<p>Ouvrez la class app/controllers/Application.java et ajouter une nouvelle méthode static list comme ci-dessous:</p>
<pre class="brush:java">
// CODE PLAY dans app/controllers/Application.java
package controllers;

import play.mvc.*;
import models.org.letouilleur.demo.MicroMarket;
import java.util.List;

public class Application extends Controller {

    public static void index() {
        render();
    }

    public static void list() {
	    List<micromarket> listOfMicroMarkets=MicroMarket.findAll();
        render(listOfMicroMarkets);
    }
}
</micromarket></pre>
<p>Voilà, c&#8217;est tout.</p>
<h3>La vue</h3>
<p>Si vous le voulez bien, terminons par la vue. Pour Play c&#8217;est assez simple, voir un peu simpliste. Créez une page <strong>list.html</strong> dans le répertoire <em>views/Application</em>. La page portant le même nom que la méthode du contrôleur, elle sera chargée automatiquement par Play!</p>
<pre class="brush:html">
#{extends 'main.html' /}
#{set title:'List of MicroMarket' /}
<h2>List of MicroMarket</h2>

#{list items:listOfMicroMarkets, as:'currentMicroMarket'}
${currentMicroMarket.id} ${currentMicroMarket.zipCode} ${currentMicroMarket.radius} ${currentMicroMarket.areaLength} ${currentMicroMarket.areaWidth} 
#{/list}
</pre>
<p>Si vous sauvez cette page et que vous testez&#8230; petit déception. Il n&#8217;y a rien dans notre base de données. Comment tester ? Et bien nous allons créer un Bootstrap (comme avec Grails) afin de charger un fichier YAML. Ce fichier contiendra 3 entrées pour tester.</p>
<p>Créez un fichier BootStrap.java dans le répertoire <strong>app</strong> de Play:</p>
<pre class="brush:java">
import play.Play;
import play.jobs.*;
import play.test.*;

import models.*;
import models.org.letouilleur.demo.*;

@OnApplicationStart
public class BootStrap extends Job {

    public void doJob() {
        if (Play.mode == Play.Mode.DEV) {
            Fixtures.load("test-data.yml");
        }
    }

}
</pre>
<p>Ensuite créez un fichier test-data.yml dans le répertoire <strong>conf</strong>:</p>
<pre class="brush:xml">
models.org.letouilleur.demo.MicroMarket(m1):
    zipCode: 95051.0
    radius: 255.59
    areaLength: 689.856
    areaWidth: 478.479

models.org.letouilleur.demo.MicroMarket(m2):
    zipCode: 33740.0
    radius: 12.59
    areaLength: 389.52
    areaWidth: 528.41

models.org.letouilleur.demo.MicroMarket(m3):
    zipCode: 89211.0
    radius: 120.3
    areaLength: 39.856
    areaWidth: 452.52
</pre>
<p>Relancez le serveur, et chargez alors la page pour lister nos entrées (<a href="http://localhost:9000/application/list">http://localhost:9000/application/list</a>). Hop voilà la liste des entrées de la base. Notez que la page est moche par rapport à la version JSF.<br />
<a href="http://www.touilleur-express.fr/wp-content/play_framework_list.png"><img src="http://www.touilleur-express.fr/wp-content/play_framework_list-300x185.png" alt="" title="play_framework_list" width="300" height="185" class="alignnone size-medium wp-image-4079" /></a></p>
<p>La version JSF est plus complète, mais on va revenir là dessus à la fin:<br />
<a href="http://www.touilleur-express.fr/wp-content/jsf_list.png"><img src="http://www.touilleur-express.fr/wp-content/jsf_list-300x215.png" alt="" title="jsf_list" width="300" height="215" class="alignnone size-medium wp-image-4080" /></a></p>
<p>Et pour terminer, regardons le code la partie JSF, dans le fichier List.xhtml. J&#8217;ai retiré la pagination afin d&#8217;alléger la page :</p>
<pre class="brush:xml">
&lt;?xml version="1.0" encoding="ISO-8859-1" ?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"&gt;

    &lt;ui:composition template="/template.xhtml"&gt;
        &lt;ui:define name="title"&gt;
            &lt;h:outputText value="#{bundle.ListMicroMarketTitle}"&gt;&lt;/h:outputText&gt;
        &lt;/ui:define&gt;
        &lt;ui:define name="body"&gt;
        &lt;h:form styleClass="jsfcrud_list_form"&gt;
            &lt;h:panelGroup id="messagePanel" layout="block"&gt;
                &lt;h:messages errorStyle="color: red" infoStyle="color: green" layout="table"/&gt;
            &lt;/h:panelGroup&gt;
            &lt;h:outputText escape="false" value="#{bundle.ListMicroMarketEmpty}" rendered="#{microMarketController.items.rowCount == 0}"/&gt;
            &lt;h:panelGroup rendered="#{microMarketController.items.rowCount &gt; 0}"&gt;
                &lt;h:dataTable value="#{microMarketController.items}" var="item" border="0" cellpadding="2" cellspacing="0" rowClasses="jsfcrud_odd_row,jsfcrud_even_row" rules="all" style="border:solid 1px"&gt;
                    &lt;h:column&gt;
                        &lt;f:facet name="header"&gt;
                            &lt;h:outputText value="#{bundle.ListMicroMarketTitle_zipCode}"/&gt;
                        &lt;/f:facet&gt;
                        &lt;h:outputText value="#{item.zipCode}"/&gt;
                    &lt;/h:column&gt;
                    &lt;h:column&gt;
                        &lt;f:facet name="header"&gt;
                            &lt;h:outputText value="#{bundle.ListMicroMarketTitle_radius}"/&gt;
                        &lt;/f:facet&gt;
                        &lt;h:outputText value="#{item.radius}"/&gt;
                    &lt;/h:column&gt;
                    &lt;h:column&gt;
                        &lt;f:facet name="header"&gt;
                            &lt;h:outputText value="#{bundle.ListMicroMarketTitle_areaLength}"/&gt;
                        &lt;/f:facet&gt;
                        &lt;h:outputText value="#{item.areaLength}"/&gt;
                    &lt;/h:column&gt;
                    &lt;h:column&gt;
                        &lt;f:facet name="header"&gt;
                            &lt;h:outputText value="#{bundle.ListMicroMarketTitle_areaWidth}"/&gt;
                        &lt;/f:facet&gt;
                        &lt;h:outputText value="#{item.areaWidth}"/&gt;
                    &lt;/h:column&gt;
                    &lt;h:column&gt;
                        &lt;f:facet name="header"&gt;
                            &lt;h:outputText value="&nbsp;"/&gt;
                        &lt;/f:facet&gt;

                    &lt;/h:column&gt;
                &lt;/h:dataTable&gt;
            &lt;/h:panelGroup&gt;
            &lt;br /&gt;
        &lt;/h:form&gt;
        &lt;/ui:define&gt;
    &lt;/ui:composition&gt;

&lt;/html&gt;
</pre>
<p>Alors pour enfoncer le clou, je propose de piquer la CSS de JSF, de mettre un tableau HTML simple, et de voir ce que cela donne. Play! Framework utilise le langage Groovy comme moteur de template dans la vue. Cela rend plus simple l&#8217;écriture des pages, mais ne permet pas de se faire aider lors de la création des pages, comme avec JSF dans NetBeans. Il y a donc un risque d&#8217;erreur. La bonne nouvelle, c&#8217;est que vous n&#8217;avez pas à arrêter Play! pour voir vos modifications. Il suffit de recharger la page dans votre navigateur.</p>
<p><a href="http://www.touilleur-express.fr/wp-content/play_list_V2.png"><img src="http://www.touilleur-express.fr/wp-content/play_list_V2-300x198.png" alt="" title="play_list_V2" width="300" height="198" class="alignnone size-medium wp-image-4083" /></a></p>
<p>Et le code HTML associé :</p>
<pre class="brush:html">
#{extends 'main.html' /}
#{set title:'List of MicroMarket' /}

&lt;h2&gt;List of MicroMarket&lt;/h2&gt;
&lt;P&gt;Play! Framework&lt;/P&gt;

&lt;table border="0" cellpadding="2" cellspacing="0" rules="all" style="border:solid 1px"&gt;
&lt;thead&gt;

&lt;tr class=""&gt;
   &lt;th scope="col"&gt;ZipCode&lt;/th&gt;
   &lt;th scope="col"&gt;Radius&lt;/th&gt;
   &lt;th scope="col"&gt;AreaLength&lt;/th&gt;
   &lt;th scope="col"&gt;AreaWidth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
#{list items:listOfMicroMarkets, as:'currentMicroMarket'}
&lt;tr class="jsfcrud_${currentMicroMarket_parity}_row"&gt;
	&lt;td&gt;${currentMicroMarket.zipCode}&lt;/td&gt;
    &lt;td&gt;${currentMicroMarket.radius}&lt;/td&gt;
    &lt;td&gt;${currentMicroMarket.areaLength}&lt;/td&gt;
    &lt;td&gt;${currentMicroMarket.areaWidth}&lt;/td&gt;
&lt;/tr&gt;
#{/list}
&lt;/tbody&gt;
&lt;/table&gt;
</pre>
<h3>Analyse des 2 approches</h3>
<p>Que pensez-vous de la partie vue de la version JSF ? Objectivement, j&#8217;ai l&#8217;impression d&#8217;écrire en tag JSF du code HTML. Je sais la puissance de JSF et que je peux gagner du temps, mais au prix d&#8217;une complexité dont je n&#8217;ai pas besoin dès le départ. Côté Play! Framework, les tableaux paginés et triés existent aussi, grâce aux librairies de jQuery. D&#8217;ailleurs, le code généré par JSF utilise aussi la librairie jQuery. Au final cela ne change pas grand chose dans le navigateur. Donc je préfère la simplicité de Play! Framework à l&#8217;approche complète de JSF, mais il faut reconnaître que JSF sera en mesure de construire des interfaces plus riches que Play!, qui reste très basique.</p>
<p>Côté serveur, la partie Entité et DAO ne change pas beaucoup. La philosophie est différente, mais cela correspond à une manière de coder. Le ManagedBean et son Converter dans la version complète est lui aussi un peu compliqué. Pourquoi avoir un DataModel (regardez le code de la méthode List) alors qu&#8217;une List d&#8217;entité ferait l&#8217;affaire ?</p>
<h3>Conclusion</h3>
<p>Après avoir vu tout ce code, on voit bien la différence d&#8217;approche. Il n&#8217;y a pas de vainqueur, il y a deux approches différentes. Côté Model et Controleur, c&#8217;est du pareil au même. A part l&#8217;approche orientée DDD de Play! nous n&#8217;avons pas vu de grosses différences. Côté vue, j&#8217;ai le choix entre JSF qui me permet d&#8217;écrire plutôt une application type client riche, ou Play! qui me laisse me débrouiller avec ma page HTML.</p>
<p>La conclusion ?</p>
<p>C&#8217;est parce que <a href="http://www.playframework.org">Play! Framework</a> est proche de ce que nous avons déjà l&#8217;habitude de faire que je vous recommande de regarder. Il vous faudra quelques heures à peine pour vous en servir, et vous serez surpris. Productivité, plaisir et impression d&#8217;avancer.</p>
<p><strong>Références:</strong><br />
Le code du projet Play! zippé et du projet JSF sont disponible ici: <a href="http://touilleur-express.fr/divers/jsf/">http://touilleur-express.fr/divers/jsf/</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2010/07/27/comparaison-de-lapproche-jsf-2-0-et-play-framework-pour-du-crud/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Spring Faces+WebFlow+Java classique comparé à Play! Framework</title>
		<link>http://www.touilleur-express.fr/2010/05/13/spring-faceswebflowjava-classique-compare-a-play-framework/</link>
		<comments>http://www.touilleur-express.fr/2010/05/13/spring-faceswebflowjava-classique-compare-a-play-framework/#comments</comments>
		<pubDate>Thu, 13 May 2010 13:23:31 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Web 2.0]]></category>
		<category><![CDATA[jsf]]></category>
		<category><![CDATA[play]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://www.touilleur-express.fr/?p=3716</guid>
		<description><![CDATA[
Dans cet article, je vous propose de regarder 2 approches différentes pour résoudre un même problème : construire une application web sécurisée, moderne et Ajaxisé. Je vais vous présenter les différences de conceptions sur la partie Java essentiellement. Cet article ne sera pas une introduction à l&#8217;une ou l&#8217;autre des technologies. Je souhaite vous éclairer sur quelques concepts différents dans Play! par rapport à l&#8217;approche classique.
Pour comparer 2 choses, il faut un référentiel comparable. JBoss Seam propose sur cette page plusieurs versions d&#8217;une application de réservation de chambres d&#8217;hôtels. La ...]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.touilleur-express.fr/wp-content/uploads/2010/05/springwebflow-versus-Play1.jpg"><img src="http://www.touilleur-express.fr/wp-content/uploads/2010/05/springwebflow-versus-Play1.jpg" alt="" title="springwebflow-versus-Play" width="500" height="500" class="alignnone size-full wp-image-3719" /></a><br />
Dans cet article, je vous propose de regarder 2 approches différentes pour résoudre un même problème : construire une application web sécurisée, moderne et Ajaxisé. Je vais vous présenter les différences de conceptions sur la partie Java essentiellement. Cet article ne sera pas une introduction à l&#8217;une ou l&#8217;autre des technologies. Je souhaite vous éclairer sur quelques concepts différents dans Play! par rapport à l&#8217;approche classique.</p>
<p>Pour comparer 2 choses, il faut un référentiel comparable. JBoss Seam propose <a href="http://demo.flamingo.exadel.com/booking/">sur cette page</a> plusieurs versions d&#8217;une application de réservation de chambres d&#8217;hôtels. La version originale basée sur JSF 1.2 n&#8217;est plus toute jeune mais fonctionne très bien. <a href="http://www.touilleur-express.fr/2006/12/12/jboss-seam-linteret-de-la-conversation/">J&#8217;en avais parlé sur le Touilleur Express il y a 3 ans et demie&#8230;</a> Quand je me dis que cela fait plus de 3 ans que j&#8217;ai vu cela, et qu&#8217;à l&#8217;époque je vous avais convaincu de regarder JSF par rapport à Struts, je me dis qu&#8217;il faut que je réussisse dans cet article à vous montrer la nouvelle approche, celle de 2010.</p>
<p>Nous allons prendre Spring Faces et Spring Webflow, une approche que je trouve très intéressante et qui permet de réaliser des applications Webs avec les technologies de Spring.</p>
<h2>I. Le match : télécharger, installer et lancer la démo</h2>
<p>Voyons tout d&#8217;abord comment télécharger, compiler et lancer la démonstration avec chacun des environnements. Je souhaite évaluer la rapidité de prise en main. Je souhaite que le projet soit configuré dans IDEA IntelliJ ou Eclipse pour travailler, et je vais évaluer la facilité à monter chacun des environnements. Je regarderai aussi le temps de démarrage des serveurs Webs testés.</p>
<h3>I.1) Préparation de l&#8217;espace de démo pour Spring</h3>
<p>SpringSource propose &laquo;&nbsp;<a href="http://richweb.springframework.org/swf-booking-faces/spring/intro">Spring Travel</a>&laquo;&nbsp;, une version basée sur <a href="http://www.springsource.org/faces">Spring Faces</a>, Spring WebFlow 2.0.9 et une librairie JSF avancée pour le rendu. J&#8217;ai regardé précisément le code &laquo;&nbsp;booking-faces&nbsp;&raquo; dans le répertoire projects/spring-webflow-samples pour écrire cet article. Si vous souhaitez tester par vous même :<br />
- téléchargez <a href="http://www.springsource.org/download#webflow">la version Spring WebFlow 2.0.9</a> sur le site de SpringSource<br />
- décompressez l&#8217;archive<br />
- ouvrez le répertoire projects/spring-webflow-samples/booking-faces<br />
- tapez <em>mvn eclipse:eclipse</em> pour créer un nouveau projet<br />
- ou sinon tapez <em>mvn idea:idea</em> pour créer un projet IDEA IntelliJ<br />
- ou enfin, si vous avez IDEA IntelliJ, celui-ci importe sans problème le pom.xml et vous prépare un projet configuré&#8230; Pas besoins de plugins.</p>
<p><strong>Faire tourner la démo en local</strong><br />
Pour faire fonctionner la version Spring en local, éditez le pom.xml et ajoutez Jetty dans la balise build/plugins comme ici:</p>
<pre class="brush:xml">   ...
   &lt;build&gt;
        &lt;finalName&gt;swf-booking-faces&lt;/finalName&gt;
        &lt;plugins&gt;
        	...

            &lt;plugin&gt;
                &lt;groupId&gt;org.mortbay.jetty&lt;/groupId&gt;
                &lt;artifactId&gt;maven-jetty-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;

        &lt;/plugins&gt;
    &lt;/build&gt;
    &lt;properties&gt;
        &lt;spring.version&gt;2.5.6.SEC01&lt;/spring.version&gt;
        &lt;jsf.version&gt;1.2.0.09&lt;/jsf.version&gt;
    &lt;/properties&gt;
&lt;/project&gt;</pre>
<p>Vous pourrez alors lancer la démo avec mvn jetty:run dans le répertoire de la démo de Spring.</p>
<h3>I.2) Configuration de la version Play! Framework</h3>
<p>Play! Framework se télécharge <a href="http://www.playframework.org/">sur le site de Play!</a>. Voici comment suivre mes explications pour la deuxième partie :<br />
- décompressez le zip dans un répertoire C:\Soft\play par exemple<br />
- ajoutez C:\Soft\play à votre PATH</p>
<p>Pour lancer l&#8217;application :<br />
- allez dans le répertoire C:\Soft\play\samples-and-tests\booking<br />
- tapez &laquo;&nbsp;<em>play run</em>&nbsp;&raquo; et connectez-vous sur http://localhost:9000/</p>
<p>Pour travailler avec Eclipse ou IDEA IntelliJ<br />
- allez dans le répertoire C:\Soft\play\samples-and-tests\booking<br />
- tapez <em>play eclipsify</em> pour créer un projet Eclipse<br />
- OU tapez <em>play idealize</em> pour créer un module IDEA IntelliJ<br />
c&#8217;est tout !</p>
<p><strong>I.3) Résultat du match :</strong></p>
<p>Facilité d&#8217;installation : match nul<br />
Qualité de la doc sur le site : match nul<br />
Création du projet dans IDEA IntelliJ : match nul<br />
Serveur : la démo Spring n&#8217;embarque pas de serveur, il a fallut ajouter Jetty pour tester alors que Play! Framework dispose de son propre serveur<br />
Temps de démarrage : avantage Play! qui démarre très rapidement en 2 secondes.</p>
<p><strong>Vainqueur : Play! Framework d&#8217;une courte longueur </strong></p>
<h2>II. Le modèle</h2>
<h3>II.1) Spécification fonctionnelle</h3>
<p>L&#8217;objectif de l&#8217;application est de réaliser un site de réservation d&#8217;hôtel. Pour cela, l&#8217;utilisateur peut chercher un hôtel, puis ensuite créer une réservation (Booking en Anglais).</p>
<p>L&#8217;utilisateur doit s&#8217;authentifier avant de pouvoir terminer sa réservation. Une entité User permet donc de gérer la sécurité facilement avec un moteur de persistence.</p>
<p>Nous utiliserons JPA et une base en mémoire pour les 2 versions. L&#8217;essentiel est que Play! Framework comme la version Spring, utilisent JPA. Donc les entités sont presque identiques.</p>
<h3>II.2) Une entité simple : l&#8217;Hotel</h3>
<p>Prenons tout d&#8217;abord l&#8217;entité Hotel qui représente un hôtel. Voici la version de la démo Spring Webflow/JSF :</p>
<pre class="brush:java">
package org.springframework.webflow.samples.booking;

import java.io.Serializable;
import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * Version Spring Web Flow. La sérialisation est nécessaire pour JSF.
 */
@Entity
public class Hotel implements Serializable {
    private static final long serialVersionUID = 4011346719502656269L;
    private Long id;
    private String name;
    private String address;
    private String city;
    private String state;
    private String zip;
    private String country;
    private BigDecimal price;

    @Id
    @GeneratedValue
    public Long getId() {
	return id;
    }

    public void setId(Long id) {
	this.id = id;
    }

    public String getName() {
	return name;
    }

    public void setName(String name) {
	this.name = name;
    }

    public String getAddress() {
	return address;
    }

    public void setAddress(String address) {
	this.address = address;
    }

    public String getCity() {
	return city;
    }

    public void setCity(String city) {
	this.city = city;
    }

    public String getZip() {
	return zip;
    }

    public void setZip(String zip) {
	this.zip = zip;
    }

    public String getState() {
	return state;
    }

    public void setState(String state) {
	this.state = state;
    }

    public String getCountry() {
	return country;
    }

    public void setCountry(String country) {
	this.country = country;
    }

    @Column(precision = 6, scale = 2)
    public BigDecimal getPrice() {
	return price;
    }

    public void setPrice(BigDecimal price) {
	this.price = price;
    }

    public Booking createBooking(User user) {
	return new Booking(this, user);
    }

    @Override
    public String toString() {
	return "Hotel(" + name + "," + address + "," + city + "," + zip + ")";
    }
}</pre>
<p>Voici la version Play! Framework :</p>
<pre class="brush:java">package models;

import play.db.jpa.*;
import play.data.validation.*;
import javax.persistence.*;
import java.math.*;

/**
 * Version Play! Framework
 */
@Entity
public class Hotel extends play.db.jpa.Model {

    @Required
    @MaxSize(50)
    public String name;

    @MaxSize(100)
    public String address;

    @Required
    @MaxSize(40)
    public String city;

    @Required
    @MaxSize(6)
    @MinSize(2)
    public String state;

    @Required
    @MaxSize(6)
    @MinSize(5)
    public String zip;

    @Required
    @MaxSize(40)
    @MinSize(2)
    public String country;

    @Column(precision=6, scale=2)
    public BigDecimal price;

    public String toString() {
        return "Hotel(" + name + "," + address + "," + city + "," + zip + ")";
    }

}</pre>
<p>Les différences entre les 2 versions :<br />
- Play! Framework étend la classe &laquo;&nbsp;play.db.jpa.Model&nbsp;&raquo; qui fournit le support JPA.<br />
- Les attributs sont publics dans la version Play!, ce qui logiquement évite des getters/setters comme dans la version Spring classique. Moi depuis que l&#8217;encapsulation n&#8217;est plus à la mode en soirée, j&#8217;ai arrêté.<br />
- La syntaxe JPA est un peu plus poussée dans la version Play! Nous verrons si cela permet d&#8217;améliorer l&#8217;expérience utilisateur dans l&#8217;interface graphique ou non.</p>
<p><del datetime="2010-05-14T05:57:30+00:00">Le bean de Spring n&#8217;étend pas une classe de base contrairement à Play!, ce qui permet d&#8217;étendre éventuellement Hotel. Mais est-ce que c&#8217;est quelque chose que nous faisons pour des Entities ? Je me pose la question. Perso je décourage l&#8217;héritage avec JPA. FBI = Fausse bonne idée.</del></p>
<p>[Update] Avec Play! Framework vous pouvez étendre la classe Model afin de simplifier la gestion JPA de l&#8217;entité. Cela fait partie des bonnes pratiques comme expliqué par Nicolas Leroux dans les commentaires. Pour en savoir plus, regardez <a href="http://www.playframework.org/documentation/1.0.2.1/jpa">la doc de la partie JPA de Play!<br />
</a></p>
<p>Play! gère l&#8217;identité dans la class Model avec un attribut Id de type Long par défaut. Si vous souhaitez gérer votre propre id, vous <del datetime="2010-05-14T05:57:30+00:00">devez</del> <em>pouvez</em> par exemple étendre la class <strong>JPASupport</strong> de Play!, puis simplement définir un attribut @Id comme d&#8217;habitude avec JPA. C&#8217;est tout à fait possible.</p>
<p>On voit que dans la version Spring, il y a une méthode &laquo;&nbsp;createBooking&nbsp;&raquo; qui permet de créer au niveau de l&#8217;Hotel une réservation.</p>
<p>Il n&#8217;y a donc pas de vainqueur dans cette partie pour moi. Les 2 approches se valent je pense. Je préfère la simplicité de Play! Framework qui remet en question quelques habitudes du monde Java, comme l&#8217;encapsulation systématique.</p>
<p>Par contre je n&#8217;aime pas trop l&#8217;approche de placer les annotations JPA sur les getters/setters plutôt que sur les attributs de la classe dans la version Spring. On perd beaucoup en lisibilité.</p>
<h3>II.3) Une entité plus compliquée : Booking</h3>
<p>Une réservation est l&#8217;association d&#8217;un User, d&#8217;un Hotel, d&#8217;une date d&#8217;arrivée et de départ et de différents autres attributs. Je vais faire exprès de vous donner la version complète des 2 frameworks, afin de vous montrer les différences flagrantes entre les 2 approches.</p>
<p>La version de la démo Spring WebFlow telle qu&#8217;elle est livrée, sans trucages, sans effets spéciaux :</p>
<pre class="brush:java">package org.springframework.webflow.samples.booking;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;

/**
 * A Hotel Booking made by a User.
 */
@Entity
public class Booking implements Serializable {

    private static final long serialVersionUID = 1171567558348174963L;

    private Long id;
    private User user;
    private Hotel hotel;
    private Date checkinDate;
    private Date checkoutDate;
    private String creditCard;
    private String creditCardName;
    private int creditCardExpiryMonth;
    private int creditCardExpiryYear;
    private boolean smoking;
    private int beds;
    private Amenity amenities;   // Absent de la version Play!

    public Booking() {
    }

    public Booking(Hotel hotel, User user) {
        this.hotel = hotel;
        this.user = user;
        Calendar calendar = Calendar.getInstance();
        setCheckinDate(calendar.getTime());
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        setCheckoutDate(calendar.getTime());
    }

    @Transient
    public BigDecimal getTotal() {
        return hotel.getPrice().multiply(new BigDecimal(getNights()));
    }

    @Transient
    public int getNights() {
        if (checkinDate == null || checkoutDate == null) {
            return 0;
        } else {
            return (int) (checkoutDate.getTime() - checkinDate.getTime()) / 1000 / 60 / 60 / 24;
        }
    }

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Basic
    @Temporal(TemporalType.DATE)
    public Date getCheckinDate() {
        return checkinDate;
    }

    public void setCheckinDate(Date datetime) {
        this.checkinDate = datetime;
    }

    @ManyToOne
    public Hotel getHotel() {
        return hotel;
    }

    public void setHotel(Hotel hotel) {
        this.hotel = hotel;
    }

    @ManyToOne
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Basic
    @Temporal(TemporalType.DATE)
    public Date getCheckoutDate() {
        return checkoutDate;
    }

    public void setCheckoutDate(Date checkoutDate) {
        this.checkoutDate = checkoutDate;
    }

    public String getCreditCard() {
        return creditCard;
    }

    public void setCreditCard(String creditCard) {
        this.creditCard = creditCard;
    }

    @Transient
    public String getDescription() {
        DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);
        return hotel == null ? null : hotel.getName() + ", " + df.format(getCheckinDate()) + " to "
                + df.format(getCheckoutDate());
    }

    public boolean isSmoking() {
        return smoking;
    }

    public void setSmoking(boolean smoking) {
        this.smoking = smoking;
    }

    public int getBeds() {
        return beds;
    }

    public void setBeds(int beds) {
        this.beds = beds;
    }

    public String getCreditCardName() {
        return creditCardName;
    }

    public void setCreditCardName(String creditCardName) {
        this.creditCardName = creditCardName;
    }

    public int getCreditCardExpiryMonth() {
        return creditCardExpiryMonth;
    }

    public void setCreditCardExpiryMonth(int creditCardExpiryMonth) {
        this.creditCardExpiryMonth = creditCardExpiryMonth;
    }

    public int getCreditCardExpiryYear() {
        return creditCardExpiryYear;
    }

    public void setCreditCardExpiryYear(int creditCardExpiryYear) {
        this.creditCardExpiryYear = creditCardExpiryYear;
    }

    @Transient
    public Amenity getAmenities() {
        return amenities;
    }

    public void setAmenities(Amenity amenities) {
        this.amenities = amenities;
    }

    public void validateEnterBookingDetails(ValidationContext context) {
        MessageContext messages = context.getMessageContext();
        if (checkinDate.before(today())) {
            messages.addMessage(new MessageBuilder().error().source("checkinDate").code(
                    "booking.checkinDate.beforeToday").build());
        } else if (checkoutDate.before(checkinDate)) {
            messages.addMessage(new MessageBuilder().error().source("checkoutDate").code(
                    "booking.checkoutDate.beforeCheckinDate").build());
        }
    }

    private Date today() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, -1);
        return calendar.getTime();
    }

    @Override
    public String toString() {
        return "Booking(" + user + "," + hotel + ")";
    }

}</pre>
<p>Voyons la version Play! Framework de l&#8217;entité Booking maintenant :</p>
<pre class="brush:java">package models;

import play.db.jpa.*;
import play.data.validation.*;
import javax.persistence.*;
import java.util.*;
import java.text.*;
import java.math.*;

@Entity
public class Booking extends Model {

    @Required
    @ManyToOne
    public User user;

    @Required
    @ManyToOne
    public Hotel hotel;

    @Required
    @Temporal(TemporalType.DATE)
    public Date checkinDate;

    @Required
    @Temporal(TemporalType.DATE)
    public Date checkoutDate;

    @Required(message="Credit card number is required")
    @Match(value="^\\d{16}$", message="Credit card number must be numeric and 16 digits long")
    public String creditCard;

    @Required(message="Credit card name is required")
    @MinSize(value=3, message="Credit card name is required")
    @MaxSize(value=70, message="Credit card name is required")
    public String creditCardName;
    public int creditCardExpiryMonth;
    public int creditCardExpiryYear;
    public boolean smoking;
    public int beds;

    public Booking(Hotel hotel, User user) {
        this.hotel = hotel;
        this.user = user;
    }

    public BigDecimal getTotal() {
        return hotel.price.multiply( new BigDecimal( getNights() ) );
    }

    public int getNights() {
        return (int) ( checkoutDate.getTime() - checkinDate.getTime() ) / 1000 / 60 / 60 / 24;
    }

    public String getDescription() {
        DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);
        return hotel==null ? null : hotel.name +
            ", " + df.format( checkinDate ) +
            " to " + df.format( checkoutDate );
    }

    public String toString() {
        return "Booking(" + user + ","+ hotel + ")";
    }

}</pre>
<p>Tout d&#8217;abord il manque des choses dans la version Play! Framework, et on ne compare donc plus exactement la même chose. Notez l&#8217;absence de l&#8217;entité Amenity. Ensuite, la version Spring utilise une approche différente pour l&#8217;attribut @Id et spécifie le générateur alors que la version Play! hérite de ce que la super-classe Model propose.</p>
<p>Ce qui me dérange dans la version Spring c&#8217;est la présence de la méthode <em>validateEnterBookingDetails</em>. Celle-ci est intéressante fonctionnellement, mais dépose une dépendance vers MessageContext un peu trop forte à mon goût.</p>
<p>Regardez aussi la différence sur la validation du numéro de carte de paiement. La version Play! utilise un moteur simple similaire à Hibernate Validation, mais propriétaire à Play! Au contraire la version Spring ne précise rien, et on imagine donc que la validation des 16 digits se fera dans la vue&#8230; et pas dans le modèle.</p>
<p>Conclusion : j&#8217;ai de plus en plus de mal avec la sauce des getters/setters, surtout lorsque les entités dépasse les &laquo;&nbsp;HelloWorld&nbsp;&raquo; et que l&#8217;on commence à sortir des cas d&#8217;usages compliqués. Souvenez-vous de ce que je vous disais : pour résoudre des problèmes compliqués il faut des solutions simples&#8230;</p>
<h2>III. Le contrôleur</h2>
<p>Après avoir regardé 2 entités différentes pour le modèle, voyons maintenant la différence d&#8217;approche entre Spring WebFlow et Play! Framework. Je vais étudier le cas de l&#8217;affichage de la liste des Hôtels, lorsque l&#8217;utilisateur clique sur le bouton Search par exemple.</p>
<h3>III.1) Spring WebFlow</h3>
<p>L&#8217;approche de Spring WebFlow est la suivante : afin d&#8217;éviter d&#8217;écrire un controller classique, Spring propose de définir les chemins logiques de navigation en XML, et d&#8217;associer directement des méthodes sur des services pour récupérer la partie Model. Cette approche a l&#8217;avantage de simplifier le développement de workflow webs complexes. Je vais vous présenter le concept.</p>
<p>Pour commencer, nous définissions un flow en XML pour l&#8217;action qui affiche la liste des Hotel correspondant à un critère de recherche. Je ne vous montre que la view-state &laquo;&nbsp;reviewHotels&nbsp;&raquo; afin de ne se concentrer que sur l&#8217;exemple :</p>
<p>&laquo;&nbsp;<em>main-flow.xml</em>&nbsp;&raquo;</p>
<pre class="brush:xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"&gt;

	&lt;var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" /&gt;

	&lt;view-state id="enterSearchCriteria"&gt;
		// Effacé pour simplifier l'exemple...
	&lt;/view-state&gt;

	&lt;view-state id="reviewHotels"&gt;
		&lt;on-render&gt;
			&lt;evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /&gt;
		&lt;/on-render&gt;
        &lt;transition on="sort"&gt;
            &lt;set name="searchCriteria.sortBy" value="requestParameters.sortBy" /&gt;
            &lt;render fragments="hotels:searchResultsFragment" /&gt;
        &lt;/transition&gt;
		&lt;transition on="previous"&gt;
			&lt;evaluate expression="searchCriteria.previousPage()" /&gt;
			&lt;render fragments="hotels:searchResultsFragment" /&gt;
		&lt;/transition&gt;
		&lt;transition on="next"&gt;
			&lt;evaluate expression="searchCriteria.nextPage()" /&gt;
			&lt;render fragments="hotels:searchResultsFragment" /&gt;
		&lt;/transition&gt;
		&lt;transition on="select" to="reviewHotel"&gt;
			&lt;set name="flowScope.hotel" value="hotels.selectedRow" /&gt;
		&lt;/transition&gt;
		&lt;transition on="changeSearch" to="changeSearchCriteria" /&gt;
	&lt;/view-state&gt;</pre>
<p><strong>Le point fort de Spring WebFlow/JSF ici :</strong> il sera possible de ne redessiner qu&#8217;une partie de l&#8217;interface. En effet, vous avez noté le tag &lt;render fragments=&nbsp;&raquo;"/&gt; ? Grâce à ce système très pratique avec Spring WebFlow et JSF, si vous avez une partie cliente en Ajax il devient alors possible de ne redéfinir qu&#8217;une partie de l&#8217;interface.</p>
<p>Dans la démo Play! Booking, ce mécanisme est implémenté &laquo;&nbsp;à la main&nbsp;&raquo; dans la page index.html. Tout est fait avec du jQuery et le tag jsAction de Play!, qui appelle des actions du controller Play. Celui-ci retourne alors des fragments de HTML, ce qui fait que la version Play! est aussi ajaxisé. Je reconnais que c&#8217;est moins évident et facile, mais les 2 se discutent.</p>
<p>Il y a donc un BookingService avec une méthode <strong>findHotels</strong> pour rechercher la liste des hôtels, d&#8217;après le Flow XML. Il s&#8217;agit d&#8217;une interface, ce qui permet de changer l&#8217;implémentation. Dans notre cas nous n&#8217;aurons que la version JPA. Si vous avez une forte envie de faire une version <em>PasJPA</em> notez que c&#8217;est super utile&#8230;</p>
<pre class="brush:java">public interface BookingService {
   // j'ai effacé les autres méthodes ici pour ne garder que l'essentiel

    public List&lt;hotel&gt; findHotels(SearchCriteria criteria);

}</pre>
<p>Le service implémente l&#8217;interface, j&#8217;ai supprimé une bonne partie du code afin de ne vous laissez que ce qui nous intéresse. Mais croyez-moi, la version complète est assez verbeuse. Ce qui nous intéresse ici c&#8217;est uniquement la méthode <em>findHotels</em></p>
<pre class="brush:java">@Service("bookingService")
@Repository
public class JpaBookingService implements BookingService {

    private EntityManager em;

    @PersistenceContext
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

   // Note : j'ai effacé du code ici pour ne garder que quelques méthodes

    @Transactional(readOnly = true)
    @SuppressWarnings("unchecked")
    public List findHotels(SearchCriteria criteria) {
        String pattern = getSearchPattern(criteria);
        return em.createQuery(
                "select h from Hotel h where lower(h.name) like " + pattern + " or lower(h.city) like " + pattern
                        + " or lower(h.zip) like " + pattern + " or lower(h.address) like " + pattern + " order by h."
                        + criteria.getSortBy()).setMaxResults(criteria.getPageSize()).setFirstResult(
                criteria.getPage() * criteria.getPageSize()).getResultList();
    }

   // Note 2 : j'ai effacé du code ici pour ne garder que quelques méthodes

   // helpers
   private String getSearchPattern(SearchCriteria criteria) {
        if (StringUtils.hasText(criteria.getSearchString())) {
            return "'%" + criteria.getSearchString().toLowerCase().replace('*', '%') + "%'";
        } else {
            return "'%'";
        }
    }

}</pre>
<p>Le plus intéressant est donc la méthode findHotels. Elle permet d&#8217;effectuer la recherche, de retourner une List d&#8217;Hotel à la vue, que nous verrons tout à l&#8217;heure si vous êtes toujours motivé.</p>
<h3>III.2) Le contrôleur version Play! Framework</h3>
<p>L&#8217;approche de Play! est un peu plus simple mais demande un peu plus de code Java.<br />
Je précise tout de suite : j&#8217;ai modifié l&#8217;exemple afin de retirer la partie Authentification et pour ne me concentrer que sur le Use-Case de récupérer la liste des Hotel correspondant à un critère entré dans l&#8217;interface utilisateur.</p>
<pre class="brush:java">public class Hotels extends Controller {

    public static void list(String search, Integer size, Integer page) {
        List hotels = null;
        page = page != null ? page : 1;
        if(search.trim().length() == 0) {
            hotels = Hotel.all().fetch(page, size);
        } else {
            search = search.toLowerCase();
            hotels = Hotel.find("lower(name) like ? OR lower(city) like ?", "%"+search+"%", "%"+search+"%").fetch(page, size);
        }
        render(hotels, search, size, page);
    }
   ...
    // Note : code effacé pour ne conserver que la méthode list
   ...
}</pre>
<p>L&#8217;approche de Play! Framework est donc la suivante :<br />
- écrire une classe dans le répertoire &laquo;&nbsp;controllers&nbsp;&raquo; qui étend la class Controller de Play!<br />
- écrire une méthode public static avec en argument les paramètres de la vue. Play! se charge de vous récupérer ce que l&#8217;utilisateur a entré. Notez ensuite l&#8217;appel pour récupérer la liste des Hotels :</p>
<pre class="brush:java">            search = search.toLowerCase();
            hotels = Hotel.find("lower(name) like ? OR lower(city) like ?", "%"+search+"%", "%"+search+"%").fetch(page, size);</pre>
<p>La méthode find est une méthode statique définie dans la classe JPASupport. Elle permet d&#8217;exécuter des requêtes JPA. Vous pouvez aussi écrire une méthode dans l&#8217;entité Hotel pour éviter de placer ici du code technique par exemple. Les 2 approches se valent.</p>
<h3>III.3) Différence d&#8217;approche</h3>
<p>La version Spring WebFlow demande plus de XML, ce qui personnellement me dérange. Je trouve que la définition des flows n&#8217;est pas simple. C&#8217;est même une fausse bonne idée. Je préfère une approche fortement typée en Java, où par contre pour que la magie opère, je dois étendre des classes spécialisées de Play! Framework.</p>
<p>Côté test, il est assez simple de <a href="http://static.springsource.org/spring-webflow/docs/2.0.x/reference/htmlsingle/spring-webflow-reference.html#testing">tester Spring WebFlow</a>, mais je n&#8217;ai pas testé personnellement.</p>
<p>Je vais être méchant avec Spring WebFlow : je trouve cela bien compliqué pour résoudre un problème simple. Ici quel est l&#8217;apport du XML ? Des annotations ? Est-ce que notre vie est plus simple grâce à ce système ? Permettez-moi d&#8217;en douter.</p>
<p>C&#8217;est peut-être une révolution par rapport à l&#8217;approche Struts, mais tant qu&#8217;à faire la révolution, il ne faut pas s&#8217;arrêter à un concept basé sur un fichier XML pour définir la navigation d&#8217;une application Web.</p>
<h3>III. 4) Synthèse</h3>
<h4>Avantages de Spring Web Flow</h4>
<p>- possibilité de déclarer en quelques lignes un bout de code pour que la vue récupère le Model sans devoir définir un controller.<br />
- gestion de la navigation de l&#8217;utilisateur fine dans le site<br />
- se repose sur des Services facile à tester pour s&#8217;assurer que le Model est correct<br />
- bien meilleur que l&#8217;approche classique où les DAO dans les Services ne servent à rien<br />
- Spring MVC récupère les paramètres de la vue et les dénormalise, ce qui est très pratique.<br />
- bonne intégration de la sécurité</p>
<h4>Inconvénients de Spring Web Flow</h4>
<p>- clairement plus compliqué que Play! sur ce point<br />
- XML tu aimes ou tu n&#8217;aimes pas, mais c&#8217;est <strong>ton</strong> environnement de développement Web<br />
- gestion pas facile pour les débutants entre les view-states, les action-states, les transitions etc.<br />
- l&#8217;EntityManager à la main dans le service : pas top. Mais c&#8217;est quelque chose qui est souvent factorisé dans des classes de GenericService par exemple, avec un DAO&#8230; Bref encore 3 ou 4 classes Java en plus.</p>
<h4>Avantages de Play! Framework :</h4>
<p>- le controlleur déclare des méthodes static et public qui deviennent autant d&#8217;URL dans la vue<br />
- le framework se charge de récupérer et dénormaliser les paramètres de la vue<br />
- approche full java<br />
- les entités ont un moteur comme GORM en Grails mais plus simple. Cela permet d&#8217;avoir des finders comme méthode statique sur l&#8217;entité Hotel. C&#8217;est cependant statique et plus limité que Grails/GORM.</p>
<h4>Inconvénients de Play! Framework :</h4>
<p>- le controller de base étend une class de Play! (si la spécialisation vous dérange plutôt que la composition)<br />
- la gestion des workflows compliqué demande un effort de développement par rapport à Spring WebFlow, qui sera capable de retomber sur ses pieds.</p>
<h2>IV. La Vue</h2>
<p>Je vais maintenant évaluer plusieurs critères :<br />
- la création d&#8217;une page pour chacune des technos<br />
- la qualité de l&#8217;expérience utilisateur avec l&#8217;utilisation d&#8217;Ajax<br />
- la taille des pages webs et les temps de chargement</p>
<h3>IV.1) Créer une page avec Spring Faces</h3>
<p>Basé sur JSF, cette partie sera donc essentiellement une discussion sur JSF plutôt que sur Spring. Je parlerai un peu de la taglibs de Spring pour la partie validation du côté client, que je trouve très bien fait. Mais l&#8217;essentiel du débat porte plus sur JSF ici.</p>
<p>Dans le projet Spring Webflow, les pages sont construites à partir d&#8217;un template, avec la technologie Facelets. Très pratique, cela vous permet de construire le canevas de votre site, puis de spécialiser vos vues en écrivant peu de code.</p>
<p>Le template standard.xhtml :</p>
<pre class="brush:html">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;f:view xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
	  xmlns:c="http://java.sun.com/jstl/core"
	  xmlns:sf="http://www.springframework.org/tags/faces"
	  contentType="text/html" encoding="UTF-8"&gt;
&lt;html&gt;
&lt;head&gt;
	&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
	&lt;title&gt;Spring Faces: Hotel Booking Sample Application&lt;/title&gt;

	&lt;sf:includeStyles/&gt;
	&lt;sf:resourceGroup&gt;
		&lt;sf:resource path="/css-framework/css/tools.css"/&gt;
		&lt;sf:resource path="/css-framework/css/typo.css"/&gt;
		&lt;sf:resource path="/css-framework/css/forms.css"/&gt;
		&lt;sf:resource path="/css-framework/css/layout-navtop-localleft.css"/&gt;
		&lt;sf:resource path="/css-framework/css/layout.css"/&gt;
	&lt;/sf:resourceGroup&gt;
	&lt;sf:resource path="/styles/booking.css"/&gt;
	&lt;ui:insert name="headIncludes"/&gt;
&lt;/head&gt;
&lt;body class="tundra spring"&gt;
&lt;div id="page"&gt;
	&lt;div id="header" class="clearfix spring"&gt;
		&lt;div id="welcome"&gt;
			&lt;div class="left"&gt;Spring Travel: A Spring Faces Reference Application&lt;/div&gt;
			&lt;div class="right"&gt;
				&lt;c:if test="${not empty currentUser.name}"&gt;
	            	Welcome, ${currentUser.name} | &lt;a href="${request.contextPath}/spring/logout"&gt;Logout&lt;/a&gt;
				&lt;/c:if&gt;
				&lt;c:if test="${empty currentUser.name}"&gt;
	            	&lt;a href="${request.contextPath}/spring/login"&gt;Login&lt;/a&gt;
				&lt;/c:if&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id="branding" class="spring"&gt;
			&lt;a href="#{request.contextPath}"&gt;&lt;img src="${request.contextPath}/resources/images/header.jpg" alt="Spring Travel"/&gt;&lt;/a&gt;
		&lt;/div&gt;
	&lt;/div&gt;
	&lt;div id="content" class="clearfix spring"&gt;
		&lt;div id="local" class="spring"&gt;
			&lt;a href="http://www.thespringexperience.com"&gt;
				&lt;img src="${request.contextPath}/resources/images/diplomat.jpg" alt="generic hotel" /&gt;
			&lt;/a&gt;
			&lt;a href="http://www.thespringexperience.com"&gt;
				&lt;img src="${request.contextPath}/resources/images/tse.gif" alt="The Spring Experience" /&gt;
			&lt;/a&gt;
			&lt;p&gt;
			&lt;/p&gt;
		&lt;/div&gt;
		&lt;div id="main"&gt;
			&lt;ui:insert name="content"/&gt;
		&lt;/div&gt;
	&lt;/div&gt;
	&lt;div id="footer" class="clearfix spring"&gt;
		&lt;a href="http://www.springframework.org"&gt;&lt;img src="${request.contextPath}/resources/images/powered-by-spring.png" alt="Powered by Spring" /&gt;&lt;/a&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;/body&gt;
&lt;/html&gt;
&lt;/f:view&gt;</pre>
<p>La page &laquo;&nbsp;reviewHotels.xhtml&nbsp;&raquo; avec 52 balises HTML+JSF permet d&#8217;afficher la liste des Hôtels :</p>
<pre class="brush:html">&lt;!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;ui:composition xmlns="http://www.w3.org/1999/xhtml"
	    		xmlns:ui="http://java.sun.com/jsf/facelets"
	  			xmlns:h="http://java.sun.com/jsf/html"
	  			xmlns:f="http://java.sun.com/jsf/core"
	  			xmlns:sf="http://www.springframework.org/tags/faces"
				template="/WEB-INF/layouts/standard.xhtml"&gt;

&lt;ui:define name="content"&gt;

&lt;h:form id="hotels"&gt;
	&lt;div class="section"&gt;
		&lt;h2&gt;Hotel Results&lt;/h2&gt;
		&lt;p&gt;
			&lt;sf:commandLink value="Change Search" action="changeSearch"/&gt;
		&lt;/p&gt;
		&lt;ui:fragment id="searchResultsFragment"&gt;
			&lt;div id="searchResults"&gt;
				&lt;h:outputText id="noHotelsText" value="No Hotels Found" rendered="#{hotels.rowCount == 0}"/&gt;
				&lt;h:dataTable id="hotels" styleClass="summary" value="#{hotels}" var="h" rendered="#{hotels.rowCount &gt; 0}"&gt;
					&lt;h:column&gt;
						&lt;f:facet name="header"&gt;
							&lt;sf:commandLink id="sortByNameLink" action="sort" value="Name" processIds="*"&gt;
								&lt;f:param name="sortBy" value="name" /&gt;
							&lt;/sf:commandLink&gt;
						&lt;/f:facet&gt;
						#{h.name}
					&lt;/h:column&gt;
					&lt;h:column&gt;
						&lt;f:facet name="header"&gt;Address&lt;/f:facet&gt;
						#{h.address}
					&lt;/h:column&gt;
					&lt;h:column&gt;
						&lt;f:facet name="header"&gt;
							&lt;sf:commandLink id="sortByCity" action="sort" value="City, State" processIds="*" &gt;
								&lt;f:param name="sortBy" value="city" /&gt;
							&lt;/sf:commandLink&gt;
						&lt;/f:facet&gt;
						#{h.city}, #{h.state}, #{h.country}
					&lt;/h:column&gt;
					&lt;h:column&gt;
						&lt;f:facet name="header"&gt;Zip&lt;/f:facet&gt;
						#{h.zip}
					&lt;/h:column&gt;
					&lt;h:column&gt;
						&lt;f:facet name="header"&gt;Action&lt;/f:facet&gt;
						&lt;sf:commandLink id="viewHotelLink" value="View Hotel" action="select"/&gt;
					&lt;/h:column&gt;
				&lt;/h:dataTable&gt;
				&lt;div class="buttonGroup"&gt;
					&lt;sf:commandLink id="previousPageLink" value="Previous results" action="previous" rendered="#{searchCriteria.page &gt; 0}"/&gt;
					&lt;sf:commandLink id="nextPageLink" value="More Results" action="next" rendered="#{not empty hotels and hotels.rowCount == searchCriteria.pageSize}"/&gt;
				&lt;/div&gt;
			&lt;/div&gt;
		&lt;/ui:fragment&gt;
	&lt;/div&gt;
&lt;/h:form&gt;
&lt;/ui:define&gt;
&lt;/ui:composition&gt;</pre>
<p>Ce que j&#8217;apprécie à titre personnel avec JSF :<br />
- possibilité d&#8217;écrire du code de mise en forme sans avoir besoin de faire trop de CSS<br />
- moteur de construction de page assez proche d&#8217;une vue client lourd<br />
- possibilité d&#8217;écrire avec la syntaxe JSF et d&#8217;avoir un rendu riche sans se prendre la tête avec du Javascript ou des feuilles de style.<br />
&lt;HUMOUR&gt;De toutes les façons, je suis pas développeur Web. Donc mort aux CSS&#8230;&lt;/HUMOUR&gt;</p>
<p>Ce qui me fait un peu tiquer, c&#8217;est que l&#8217;on dit au développeur Java : tu n&#8217;auras pas besoin d&#8217;apprendre à faire une mise en page compliquée en HTML et CSS&#8230;</p>
<p>Mais bon, vous vous foutez pas un peu de nous là ?</p>
<p>La page demande 52 balises. Et ce dataTable pourrait très bien être un simple TABLE en HTML avec une CSS non ? Ce qui d&#8217;ailleurs est le cas au final non ?</p>
<h3>IV.2) Créer une page avec Play! Framework</h3>
<p>Attention cela pique les yeux. Comme JSF ou Wicket, vous pouvez utiliser un éditeur HTML pour construire votre page. En effet, le format des pages de Play! Framework est très original <strong>puisque c&#8217;est du HTML</strong> ni plus, ni moins. Il y a cependant l&#8217;utilisation de Groovy qui risque de faire peur à quelques uns. Je note cela comme un inconvénient par rapport à JSF.</p>
<p>La version livrée avec Play! utilise jQuery et Ajax à fond. Il y a en fait une page principale, puis un ensemble de fonction Javascript pour ne charger rapidement que ce qui change dans un DIV au format HTML. C&#8217;est radicalement différent, et peut-être moins facile d&#8217;abord pour des gens n&#8217;ayant jamais fait de Web. Je vous montre donc plutôt ici ma version &laquo;&nbsp;<em>gars qui a fait du Java et du Web et qui découvre Play</em>&nbsp;&raquo;</p>
<p>Play! Framework dispose aussi d&#8217;un moteur de template. Je vais donc vous montrer 2 fichiers. Le fichier main.html est un template de base que j&#8217;utilise pour le site :</p>
<pre class="brush:html">&lt;!DOCTYPE html&gt;

&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;#{get 'title' /}&lt;/title&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
        &lt;link rel="stylesheet" type="text/css" media="screen" href="@{'/public/stylesheets/main.css'}"&gt;
        #{get 'moreStyles' /}
        &lt;link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}"&gt;
        &lt;script src="@{'/public/javascripts/jquery-1.3.2.min.js'}" type="text/javascript" charset="utf-8"&gt;&lt;/script&gt;
        &lt;script src="@{'/public/javascripts/sessvars.js'}" type="text/javascript" charset="utf-8"&gt;&lt;/script&gt;
        #{get 'moreScripts' /}
    &lt;/head&gt;
    &lt;body&gt;

        &lt;div id="header"&gt;
            &lt;h1&gt;play framework booking demo&lt;/h1&gt;
            #{if user?.id}
                &lt;div id="options"&gt;
                    Connected as ${user.username}
                    |
                    &lt;a href="@{Hotels.index()}"&gt;Search&lt;/a&gt;
                    |
                    &lt;a href="@{Hotels.settings()}"&gt;Settings&lt;/a&gt;
                    |
                    &lt;a href="@{Application.logout()}"&gt;Logout&lt;/a&gt;
                &lt;/div&gt;
            #{/if}
        &lt;/div&gt;

        &lt;div id="content"&gt;
            #{if flash.error}
                &lt;p class="fError"&gt;
                    &lt;strong&gt;${flash.error}&lt;/strong&gt;
                &lt;/p&gt;
            #{/if}
            #{if flash.success}
                &lt;p class="fSuccess"&gt;
                    &lt;strong&gt;${flash.success}&lt;/strong&gt;
                &lt;/p&gt;
            #{/if}

            #{doLayout /}

        &lt;/div&gt;

        &lt;div id="footer"&gt;
            Created with &lt;a href="http://www.playframework.org"&gt;play framework&lt;/a&gt; and really inspirated from the booking sample application provided by &lt;a href="http://seamframework.org/"&gt;seam framework&lt;/a&gt;
        &lt;/div&gt;

    &lt;/body&gt;
&lt;/html&gt;</pre>
<p>Voici la page qui affiche la liste des Hotels. Le fichier s&#8217;appelle &laquo;&nbsp;list.html&nbsp;&raquo; comme le nom de la méthode <em>list</em> du Controller Hotels que nous avons vu plus haut. Il est bien entendu possible de déclarer un nom différent et de configurer une autre route avec Play! Framework.</p>
<p>Le fichier est assez simple : il reçoit un objet List&lt;Hotel&gt;, il itère en Groovy avec un tag spécifique à Play et il affiche le résultat dans un tableau HTML :</p>
<pre class="brush:html">
#{extends 'main.html' /}
#{set title:'Search result'/}

#{if hotels}
&lt;table&gt;
    &lt;thead&gt;
    &lt;tr&gt;
        &lt;th&gt;Name&lt;/th&gt;
        &lt;th&gt;Address&lt;/th&gt;
        &lt;th&gt;City, State&lt;/th&gt;
        &lt;th width="8%"&gt;Zip&lt;/th&gt;
        &lt;th width="13%"&gt;Action&lt;/th&gt;
    &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
    #{list hotels, as:'hotel'}
    &lt;tr&gt;
        &lt;td&gt;${hotel.name}&lt;/td&gt;
        &lt;td&gt;${hotel.address}&lt;/td&gt;
        &lt;td&gt;${hotel.city}, ${hotel.state}, ${hotel.country}&lt;/td&gt;
        &lt;td&gt;${hotel.zip}&lt;/td&gt;
        &lt;td&gt;
            &lt;a href="@{show(hotel.id)}"&gt;View Hotel&lt;/a&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
    #{/list}
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
    &lt;a id="nextPage" href="${page+1}"&gt;More results&lt;/a&gt;
&lt;/p&gt;
#{/if}

#{else}
&lt;p&gt;
    No more results
&lt;/p&gt;
#{/else}</pre>
<p>La version Play! est très dépouillée. L&#8217;inconvénient : il sera nécessaire dans les 2 cas (JSF comme Play!) d&#8217;apprendre la technologie de rendu et les tags pour travailler efficacement. Mais Play! n&#8217;est pas orienté composant comme JSF. Il n&#8217;y a pas d&#8217;utilisation de l&#8217;Expression Language, ici c&#8217;est Groovy. Ce moteur Groovy est un poil plus lent que <a href="http://www.playframework.org/modules/japid">Japid</a>, un autre moteur Java qui est disponible pour Play!</p>
<p>Regardez aussi <a href="http://www.playframework.org/modules">la liste des modules de Play! Framework</a>. Il y a du lourd comme le support de Scala, le moteur NoSQL MongoDB, Guice, GWT ou le module Search avec Lucene (comme Hibernate Search en plus simple).</p>
<p>Un inconvénient de Play! pour les personnes qui n&#8217;ont jamais fait de Web : la mise en page et la partie CSS. Lorsque je monte un projet avec Play! Framework, j&#8217;utilise par exemple <a href="http://960.gs/">CSS 960 grid system</a>. Je connais ces systèmes car j&#8217;ai un gros background Web. Pour le CSS, pour sélectionner des couleurs qui ne font pas vomir l&#8217;utilisateur, j&#8217;ai pleins d&#8217;outils comme <a href="http://kuler.adobe.com/">Kuler</a> d&#8217;Adobe pour préparer des styles&#8230; Donc pour moi qui suit très Web : je peux faire un truc joli avec Play! Framework. Je pourrai styliser mes tableaux sans soucis. Je pense que ce qui est bien avec JSF, c&#8217;est que cela vous aide à monter vos pages facilement, à construire vos formulaires ou vos tableaux sans devoir devenir une bête en HTML/CSS. Mais c&#8217;est un peu dommage, lorsque l&#8217;on voit le potentiel d&#8217;HTML 5 pour les années qui viennent.</p>
<h3>IV.3) Expérience utilisateur</h3>
<p>J&#8217;ai lancé les 2 applications et j&#8217;ai effectué une comparaison en tant qu&#8217;utilisateur. Je donne l&#8217;avantage à la version Spring Faces. Je trouve que la librairie de Spring de validation du côté client est très sympa.</p>
<p>En terme de rapidité, la version Play! Framework est vraiment plus réactive. Si je clique comme un dingue avec un script Selenium, la version Spring WebFlow est à la peine alors que la version Play! se tourne les pouces sans s&#8217;essoufler.</p>
<h3>IV.5) Taille des pages Webs</h3>
<p>La page d&#8217;affichage de la liste des Hôtels sur Spring WebFlow (cliquez sur l&#8217;image pour l&#8217;afficher en grand) :<br />
<a href="http://www.touilleur-express.fr/wp-content/page_spring_webflow_result.jpg"><img class="alignnone size-medium wp-image-3739" title="page_spring_webflow_result" src="http://www.touilleur-express.fr/wp-content/page_spring_webflow_result-300x236.jpg" alt="" width="300" height="236" /></a></p>
<p>La version Play! Framework :<br />
<a href="http://www.touilleur-express.fr/wp-content/page_hotels_play.jpg"><img class="alignnone size-medium wp-image-3740" title="page_hotels_play" src="http://www.touilleur-express.fr/wp-content/page_hotels_play-300x261.jpg" alt="" width="300" height="261" /></a></p>
<p>Mes tests avec YSlow en effectuant 10 chargements de la page puis en prenant la moyenne :</p>
<p><strong>Moyenne des temps de chargement de la page liste des Hotels pour 10 hotels:</strong><br />
- Play! Framework en mode Dev ou Prod : 0,234 secondes<br />
- Spring WebFlow avec Jetty : 1.286 secondes<br />
- Spring WebFlow avec Tomcat 6.0.20 : 1.092 secondes</p>
<p>Le mode Prod de Play! ne prend qu&#8217;un peu plus de temps à démarrer, mais je n&#8217;ai pas constaté de différence une fois le serveur lancé.</p>
<p><strong>Volume de la partie HTML sans CSS/Images et temps de chargement :</strong><br />
Si je ne regarde que la partie HTML sans prendre en compte le temps de chargement des images, je teste réellement que la partie serveur.</p>
<p>Play! Framework : chargement de 3.1 kb en un temps moyen de 14ms<br />
Tomcat+Spring : chargement de 14kb en un temps moyen de 52 ms</p>
<p>Donc pour un affichage similaire, vous voyez le souci ? <strong>la version Spring Faces de cette démonstration est 5 fois plus lourde et demande 4 à 5 fois plus de temps</strong>. Je ne dis pas qu&#8217;en général c&#8217;est le cas, mais pour notre exemple oui c&#8217;est le cas. Et nous sommes sur le même cahier des charges : afficher dans un tableau une liste d&#8217;Hôtel.</p>
<h2>V. Conclusion</h2>
<p>Si vous avez pris le temps de lire cet énorme article jusqu&#8217;au bout, merci. Revenez voir le code des deux version en détail. Téléchargez chacune des versions et donnez vos propres arguments techniques en faveur de l&#8217;un ou de l&#8217;autre.</p>
<p>J&#8217;ai envie de taper du poing sur la table : <strong>il est temps de revoir le développement Web en Java</strong>.<br />
Le développement Web demande des compétences. Si vous ne les avez pas, ne prenez pas une techno pour couvrir vos lacunes. Apprenez à faire du Web. Désolé, le XML ne m&#8217;excite pas. Play! est plus facile à apprendre que toute la stack Spring que je vous ai présenté. Car elle est plus simple et tout aussi puissante.</p>
<p>Pour construire une application Web compliquée ou d&#8217;entreprise, est-ce que dans 5 ans nous ferrons toujours du JSF ? ou est-ce que nous ferrons du HTML+CSS+jQuery ? Je pose la question. Evidemment, Play! Framework doit encore faire ses preuves et on peut me retourner l&#8217;argument. Mais c&#8217;est plus simple, plus réaliste et plus facile à maintenir.</p>
<p>Le point faible de Play! par rapport à un framework comme Spring Faces+Spring WebFlow+JSF c&#8217;est qu&#8217;il faut plus d&#8217;efforts pour construire ses composants dans la partie vue. D&#8217;un autre côté, lorsque vous avez un framework aussi puissant que JSF, vous avez envie de prendre des tableaux avancés, des richTables, ou des composants plus puissants. Cela entraîne parfois le risque de prendre des composants qui font trop par rapport à un tableau simple. D&#8217;ailleurs, si vous ne preniez pas ces composants, quel intérêt à utiliser une librairie JSF ? Je pose la question.</p>
<p>Un risque est donc d&#8217;utiliser des composants riches trop facilement dans une application Web avec JSF. Vous vous retrouvez avec un tableau avec des colonnes triables, alors que vous n&#8217;en n&#8217;avez pas besoin. Et votre page pèse 15-20kb au lieu de peser 2-5kb, et demande de la mémoire et du CPU du côté serveur.</p>
<p>Le site &laquo;&nbsp;<a href="http://www.express-board.fr/">eXpress-Board</a>&nbsp;&raquo; tourne sur la même machine que le blog &laquo;&nbsp;Le Touilleur Express&nbsp;&raquo; avec 3 parts chez Gandi, soit 768Mo. Le process Java tourne avec 128 Mo de mémoire, et prend entre 5 et 16% de l&#8217;utilisation de la machine, bien loin de MySQL qui est très sollicité par WordPress pour le Blog.</p>
<p>Je vais me faire taper par les maîtres Javas qui ne vont pas être d&#8217;accord. Mais c&#8217;est la première fois où j&#8217;en ai rien à faire, car je suis sûr de mes arguments.</p>
<p>Allez SuperTroll, sort du bois, je t&#8217;attends.</p>
<p>On va bien s&#8217;amuser&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2010/05/13/spring-faceswebflowjava-classique-compare-a-play-framework/feed/</wfw:commentRss>
		<slash:comments>44</slash:comments>
		</item>
		<item>
		<title>A propos d&#039;ICEFaces 1.6, JBoss Seam et mon projet de dans deux ans</title>
		<link>http://www.touilleur-express.fr/2007/07/12/a-propos-dicefaces-16-jboss-seam-et-mon-projet-de-dans-deux-ans/</link>
		<comments>http://www.touilleur-express.fr/2007/07/12/a-propos-dicefaces-16-jboss-seam-et-mon-projet-de-dans-deux-ans/#comments</comments>
		<pubDate>Thu, 12 Jul 2007 07:27:18 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[icefaces]]></category>
		<category><![CDATA[jboss]]></category>
		<category><![CDATA[jsf]]></category>
		<category><![CDATA[ria]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=40</guid>
		<description><![CDATA[Je suis sur un nouveau projet personnel. Basé sur JBoss Seam, j&#8217;ai besoin de librairies JSF assez puissante pour gagner du temps. Ce matin j&#8217;ai vu qu&#8217;ICEsoft Technologies, Inc a annoncé la sortie officielle de la librairie JSF ICEFaces en version 1.6. Cette nouvelle version s&#8217;intégre avec JBoss Seam 1.2.1 GA. Des tutoriaux permettent d&#8217;apprendre à coder des pages rapidement. Le mieux cependant est peut-être de regarder la page de démonstration pour vous faire une idée.
Par rapport à d&#8217;autres librairies comme tomahawk qui a du mal à s&#8217;intégrer avec Seam, ...]]></description>
			<content:encoded><![CDATA[<p>Je suis sur un nouveau projet personnel. Basé sur JBoss Seam, j&#8217;ai besoin de librairies JSF assez puissante pour gagner du temps. Ce matin j&#8217;ai vu qu&#8217;ICEsoft Technologies, Inc a annoncé la sortie officielle de la librairie JSF ICEFaces en version 1.6. Cette nouvelle version s&#8217;intégre avec JBoss Seam 1.2.1 GA. Des <a href="http://www.icefaces.org/main/resources/tutorials.iface">tutoriaux</a> permettent d&#8217;apprendre à coder des pages rapidement. Le mieux cependant est peut-être de regarder <a href="http://www.icefaces.org/main/demos/">la page de démonstration</a> pour vous faire une idée.</p>
<p>Par rapport à d&#8217;autres librairies comme tomahawk qui a du mal à s&#8217;intégrer avec Seam, ICEFaces a été testé et validé avec JBoss Seam. Pour ma part <b>je cherche un composant jsf d&#8217;agenda</b> qui tienne la route. J&#8217;aimerai bien tester <a href="http://myfaces.apache.org/tomahawk/schedule.html">MyFaces Schedule</a> mais pour mettre en place tomahawk dans JBoss il faut désactiver l&#8217;implémentation jsf par défaut. Je n&#8217;ai pas encore testé, car je regarde une autre alternative utilisant Google Agenda. L&#8217;idée est de récuperer l&#8217;agenda via du HTML, ce que j&#8217;ai réussi à faire hier soir sans trop d&#8217;effort. Google met aussi à disposition une API Java pour poster des évenements vers un agenda Google. Bref j&#8217;ai tout, sauf qu&#8217;au niveau de l&#8217;ergonomie l&#8217;agenda qui s&#8217;affiche en mode HTML est moins sympa à utiliser que la vraie version de Google Agenda&#8230; Donc me voilà revenu au point de départ.</p>
<p>Sinon JBoss Seam est vraiment puissant&#8230; mais chut, tant pis pour ceux qui se réveilleront trop tard&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2007/07/12/a-propos-dicefaces-16-jboss-seam-et-mon-projet-de-dans-deux-ans/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ajax4jsf 1.0.2 fonctionne avec JBoss Seam</title>
		<link>http://www.touilleur-express.fr/2006/10/12/ajax4jsf-102-fonctionne-avec-jboss-seam/</link>
		<comments>http://www.touilleur-express.fr/2006/10/12/ajax4jsf-102-fonctionne-avec-jboss-seam/#comments</comments>
		<pubDate>Thu, 12 Oct 2006 07:04:33 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Dev Web]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=62</guid>
		<description><![CDATA[Bonne nouvelle, Ajax4jsf fonctionne sans aucuns problèmes avec JBoss Seam. Ajax4jsf est une api open source qui offre à JSF (Java Server Faces) des possibilités AJAX sans devoir écrire une ligne de Javascript. Ajax4jsf utilise cependant toutes les fonctionalités de l&#8217;architecture JSF (validation, conversion, gestion du cycle de vie) tout en facilitant la mise en place de composants dynamiques.


Le site Ajax4jsf: https://ajax4jsf.dev.java.net/nonav/ajax/ajax-jsf/
]]></description>
			<content:encoded><![CDATA[<p>Bonne nouvelle, <a href="https://ajax4jsf.dev.java.net/nonav/ajax/ajax-jsf/" target="test">Ajax4jsf</a> fonctionne sans aucuns problèmes avec JBoss Seam. Ajax4jsf est une api open source qui offre à JSF (Java Server Faces) des possibilités AJAX sans devoir écrire une ligne de Javascript. Ajax4jsf utilise cependant toutes les fonctionalités de l&#8217;architecture JSF (validation, conversion, gestion du cycle de vie) tout en facilitant la mise en place de composants dynamiques.<br />
<img src="https://ajax4jsf.dev.java.net/nonav/images/a4jSimple.gif"/><br />
<br/><br />
Le site Ajax4jsf: <a href="https://ajax4jsf.dev.java.net/nonav/ajax/ajax-jsf/">https://ajax4jsf.dev.java.net/nonav/ajax/ajax-jsf/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2006/10/12/ajax4jsf-102-fonctionne-avec-jboss-seam/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Comment AJAX-iser ses composants JSF</title>
		<link>http://www.touilleur-express.fr/2006/06/18/comment-ajax-iser-ses-composants-jsf/</link>
		<comments>http://www.touilleur-express.fr/2006/06/18/comment-ajax-iser-ses-composants-jsf/#comments</comments>
		<pubDate>Sun, 18 Jun 2006 13:05:10 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=66</guid>
		<description><![CDATA[Ajax Anywhere est une librairie open source qui permet de transformer des composants JSF existant en composant compatible AJAX. Ainsi au lieu de rafraichir l&#8217;ensemble d&#8217;une page lorsque l&#8217;utilisateur soumet un formulaire, vous pouvez d&#233;limiter des zones dans votre page et enchainer un rafraichissement uniquement de cette zone. L&#8217;avantage d&#8217;AJAX Anywhere contrairement &#224; d&#8217;autres JSF existant, c&#8217;est que cette API n&#8217;est pas orient&#233;e composant mais plut&#244;t &#171;&#160;zone&#160;&#187;. D&#8217;autre part il est possible d&#8217;ajouter ou retirer AJAX anywhere facilement car les composants JSF eux-m&#234;me ne sont pas impact&#233;s. Vous pouvez donc ...]]></description>
			<content:encoded><![CDATA[<p><a href="http://ajaxanywhere.sourceforge.net/">Ajax Anywhere</a> est une librairie open source qui permet de transformer des composants JSF existant en composant compatible AJAX. Ainsi au lieu de rafraichir l&#8217;ensemble d&#8217;une page lorsque l&#8217;utilisateur soumet un formulaire, vous pouvez d&eacute;limiter des zones dans votre page et enchainer un rafraichissement uniquement de cette zone. L&#8217;avantage d&#8217;AJAX Anywhere contrairement &agrave; d&#8217;autres JSF existant, c&#8217;est que cette API n&#8217;est pas orient&eacute;e composant mais plut&ocirc;t &laquo;&nbsp;zone&nbsp;&raquo;. D&#8217;autre part il est possible d&#8217;ajouter ou retirer AJAX anywhere facilement car les composants JSF eux-m&ecirc;me ne sont pas impact&eacute;s. Vous pouvez donc ajouter une couche &laquo;&nbsp;AJAX&nbsp;&raquo; sur votre application JSF existante, et ce, tr&egrave;s facilement</p>
<p><b>Mise en oeuvre:</b><br/></p>
<ul>
<li>T&eacute;l&eacute;chargez l&#8217;api <a href="http://sourceforge.net/projects/ajaxanywhere">ici</a></li>
<li>Placer le fichier aa.js &agrave; la racine de votre r&eacute;pertoire web, les fichiers .class dans le r&eacute;pertoire WEB-INF/lib, le fichier ajaxanywhere.tld dans le r&eacute;pertoire META-INF\tlds.</li>
<li>Configurez un nouveau filter:<br />
<code xml>
<pre>
      &lt;filter&gt;
        &lt;filter-name&gt;AjaxAnywhere&lt;/filter-name&gt;
        &lt;filter-class&gt;org.ajaxanywhere.AAFilter&lt;/filter-class&gt;
    &lt;/filter&gt;

    &lt;filter-mapping&gt;
        &lt;filter-name&gt;AjaxAnywhere&lt;/filter-name&gt;
        &lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;
    &lt;/filter-mapping&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;AjaxAnywhere&lt;/filter-name&gt;
        &lt;url-pattern&gt;*.do&lt;/url-pattern&gt; &lt;!-- default Struts mapping --&gt;
    &lt;/filter-mapping&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;AjaxAnywhere&lt;/filter-name&gt;
        &lt;url-pattern&gt;*.htm&lt;/url-pattern&gt; &lt;!-- other framewords mapping--&gt;
    &lt;/filter-mapping&gt;
</pre>
<p></code></li>
</p>
<p>Il faut maintenant brancher AjaxAnywhere sur vos composants JSF dans une page JSP. Vous pouvez voir <a href="http://ajaxanywhere.sweetdev-labs.org:8080/ajaxAnywhereDemo-1.0.1/facesFrame.jsp" target="est">ici</a> une d&eacute;mo avec les composants MyFaces et l&#8217;int&eacute;r&ecirc;t de ne pas recharger toute la page. Pour ajouter AjaxAnywhere &agrave; une page JSP existante il faut:</p>
<ul>
<li>Importer la librairie de tag</li>
<li>Appeler la methode AAUtil.addZones sur l&#8217;&eacute;lement JSF &agrave; ajaxiser</li>
<li>Importer les librairies JS et d&eacute;finir la FORM qui contient le composant</li>
<li>Changer la target action de la balise FORM pour appeler du code JS d&#8217;AjaxAnywhere</li>
<li>Pre et post fixer votre composant avec le tag JSF &lt;aa:zone&gt;</li>
</ul>
<p>Si vous avez d&eacute;j&agrave; une application avec JSF et que vous d&eacute;sirez tester le principe d&#8217;AJAX rapidement, alors AjaxAnywhere est pour vous</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2006/06/18/comment-ajax-iser-ses-composants-jsf/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JBoss Seam 1.0 GA est disponible depuis le 13 juin</title>
		<link>http://www.touilleur-express.fr/2006/06/15/jboss-seam-10-ga-est-disponible-depuis-le-13-juin/</link>
		<comments>http://www.touilleur-express.fr/2006/06/15/jboss-seam-10-ga-est-disponible-depuis-le-13-juin/#comments</comments>
		<pubDate>Thu, 15 Jun 2006 08:10:44 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=67</guid>
		<description><![CDATA[A peine une semaine après le séminaire JBoss, la version 1.0 GA de JBoss Seam est disponible. En fait les archives étaient disponibles dès samedi 11 juin. Suite à cela nous avons vu cette semaine une pluie d&#8217;article sur les sites anglophones. Parmis ceux-ci, je vous conseille la lecture d&#8217;une interview de Gavin King le  créateur d&#8217;Hibernate et de JBoss Seam sur InfoQ complète et intéressante. En deux pages il présente les idées phares de JBoss Seam qui en font un Framework de 3ème génération.
Pour ma part, depuis la ...]]></description>
			<content:encoded><![CDATA[<p>A peine une semaine après le séminaire JBoss, la version 1.0 GA de JBoss Seam est disponible. En fait les archives étaient disponibles dès samedi 11 juin. Suite à cela nous avons vu cette semaine une pluie d&#8217;article sur les sites anglophones. Parmis ceux-ci, je vous conseille la lecture d&#8217;une <a href="http://www.infoq.com/news/JBoss-SEAM-1.0-Gavin-interview">interview de Gavin King</a> le  créateur d&#8217;Hibernate et de JBoss Seam sur InfoQ complète et intéressante. En deux pages il présente les idées phares de JBoss Seam qui en font un Framework de 3ème génération.</p>
<p>Pour ma part, depuis la semaine passée j&#8217;ai avancé sur ma maquette. J&#8217;ai intégré les tags JSF de l&#8217;api <a href="http://www.jenia.org/">Jenia.org</a> pour permettre d&#8217;avoir accès facilement dans JBoss Seam à JFreeChart. J&#8217;en aurai besoin pour la partie charting de notre application financière. Promis dès que j&#8217;ai un peu de temps je poste ici les modifications effectuées dans la build pour tester tout cela. Il a fallut notamment changer le build.xml pour pouvoir packager des JAR à la racine de l&#8217;EAR et changer le Manifest.mf pour le jar packagé dans le WAR&#8230; mais bon c&#8217;est du charabia et je prendrai le temps d&#8217;expliquer les modifications pour intégrer ces différents tags.</p>
<p>Pour tester JBoss Seam Remoting, en fait il ne m&#8217;a fallut qu&#8217;une petite heure pour reprendre &laquo;&nbsp;helloWorld&nbsp;&raquo; et créer un tag inputSearch à la AJAX. C&#8217;est vraiment simple. Seam = Simplicité.</p>
<p>La prochaine étape sera de voir comment appeler un web service à partir de la partie Seam. Il y a des specifications en cours dans EJB3 mais pour l&#8217;instant je n&#8217;en sais pas trop&#8230; A suivre donc</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2006/06/15/jboss-seam-10-ga-est-disponible-depuis-le-13-juin/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Migrer de JSF 1.2 vers JBoss Seam</title>
		<link>http://www.touilleur-express.fr/2006/06/11/migrer-de-jsf-12-vers-jboss-seam/</link>
		<comments>http://www.touilleur-express.fr/2006/06/11/migrer-de-jsf-12-vers-jboss-seam/#comments</comments>
		<pubDate>Sun, 11 Jun 2006 08:15:24 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=68</guid>
		<description><![CDATA[&#60;P&#62;Sur cet &#60;a href=&#160;&#187;http://weblogs.java.net/blog/bleonard/archive/2006/05/trying_out_jbos.html&#160;&#187; target=&#160;&#187;new2&#8243;&#62;article&#60;/a&#62; vous trouverez des explications claires et complètes pour intégrer JBoss Seam sur une application utilisant JSF 1.2. L&#8217;article comporte une deuxieme partie &#60;a href=&#160;&#187;http://weblogs.java.net/blog/bleonard/archive/2006/05/trying_out_jbos_1.html&#160;&#187; target=&#160;&#187;new22&#8243;&#62;ici&#60;/a&#62;. Seam permet de remplacer les backed-bean de JSF par des ejb3 rapidement et facilement.&#60;/P&#62; &#60;P&#62;J&#8217;ai commencé hier une maquette en reprenant une application financière qui utilise notre propre framework pour évaluer le coût, la complexité et l&#8217;intérêt de passer à Seam. A suivre&#8230;&#60;/P&#62; &#60;P&#62;Sinon la Suisse joue contre la France mardi soir&#8230; clin d&#8217;oeil à Luc et Thomas de Jboss ...]]></description>
			<content:encoded><![CDATA[<p><span style="font-family: 'Lucida Grande'; font-size: 11px; line-height: normal; white-space: pre-wrap" class="Apple-style-span">&lt;P&gt;Sur cet &lt;a href=&nbsp;&raquo;http://weblogs.java.net/blog/bleonard/archive/2006/05/trying_out_jbos.html&nbsp;&raquo; target=&nbsp;&raquo;new2&#8243;&gt;article&lt;/a&gt; vous trouverez des explications claires et complètes pour intégrer JBoss Seam sur une application utilisant JSF 1.2. L&#8217;article comporte une deuxieme partie &lt;a href=&nbsp;&raquo;http://weblogs.java.net/blog/bleonard/archive/2006/05/trying_out_jbos_1.html&nbsp;&raquo; target=&nbsp;&raquo;new22&#8243;&gt;ici&lt;/a&gt;. Seam permet de remplacer les backed-bean de JSF par des ejb3 rapidement et facilement.&lt;/P&gt; &lt;P&gt;J&#8217;ai commencé hier une maquette en reprenant une application financière qui utilise notre propre framework pour évaluer le coût, la complexité et l&#8217;intérêt de passer à Seam. A suivre&#8230;&lt;/P&gt; &lt;P&gt;Sinon la Suisse joue contre la France mardi soir&#8230; clin d&#8217;oeil à Luc et Thomas de Jboss inc.&lt;/P&gt;</span></p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2006/06/11/migrer-de-jsf-12-vers-jboss-seam/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installation de JBoss Seam</title>
		<link>http://www.touilleur-express.fr/2006/06/10/installation-de-jboss-seam/</link>
		<comments>http://www.touilleur-express.fr/2006/06/10/installation-de-jboss-seam/#comments</comments>
		<pubDate>Sat, 10 Jun 2006 09:14:54 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=69</guid>
		<description><![CDATA[Pour tester JBoss Seam 1.0.0.RC3 vous devez installer JBoss AS 4.0.4 GA. Sur la page http://labs.jboss.com/portal/jbossas/download
sélectionnez l&#8217;installation via l&#8217;installeur en cliquant sur Run Installer. Ne téléchargez pas l&#8217;archive (via le bouton Download). En effet les EJB3 n&#8217;étant pas encore certifiés JEE5, l&#8217;ejb3 deployer n&#8217;est pas livré par défaut. 
Une fois le téléchargement terminé, et après avoir accepté la licence, pensez à choisir l&#8217;installation de type EJB3 lorsque le programme d&#8217;installation démarre. Il vous faudra aussi Java 5 si vous ne l&#8217;avez pas déjà. A noter que si vous téléchargez jboss-4.0.4.GA-installer.jar, ...]]></description>
			<content:encoded><![CDATA[<p>Pour tester JBoss Seam 1.0.0.RC3 vous devez installer JBoss AS 4.0.4 GA. Sur la page <a href="http://labs.jboss.com/portal/jbossas/download">http://labs.jboss.com/portal/jbossas/download</a><br />
sélectionnez l&#8217;installation via l&#8217;installeur en cliquant sur <b>Run Installer</b>. Ne téléchargez pas l&#8217;archive (via le bouton Download). En effet les EJB3 n&#8217;étant pas encore certifiés JEE5, l&#8217;ejb3 deployer n&#8217;est pas livré par défaut. <br/><br />
Une fois le téléchargement terminé, et après avoir accepté la licence, pensez à choisir l&#8217;installation de type EJB3 lorsque le programme d&#8217;installation démarre. Il vous faudra aussi Java 5 si vous ne l&#8217;avez pas déjà. A noter que si vous téléchargez <i>jboss-4.0.4.GA-installer.jar</i>, vous pouvez ensuite patcher votre installation de JBoss 4.0.4 de la manière suivante pour y ajouter le support des EJB3:<br />
<br/><br />
<code><br />
java -jar jboss-4.0.4.GA-installer.jar -installGroup ejb3 installpath=C:\\Software\\jboss-4.0.4-ejb3<br />
</code><br />
<br/><br />
C&#8217;est tout pour aujourd&#8217;hui.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2006/06/10/installation-de-jboss-seam/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Struts ou JSF ? ou les deux ?</title>
		<link>http://www.touilleur-express.fr/2005/06/17/struts-ou-jsf-ou-les-deux/</link>
		<comments>http://www.touilleur-express.fr/2005/06/17/struts-ou-jsf-ou-les-deux/#comments</comments>
		<pubDate>Fri, 17 Jun 2005 18:11:13 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[jsf]]></category>

		<guid isPermaLink="false">http://www.touillleur-express.fr/?p=106</guid>
		<description><![CDATA[Entre Struts et JSF mon coeur balance&#8230; En fait pas du tout. Après avoir mis le nez dans le code, je pense que les 2 ne sont pas antagonistes. Il est d&#8217;ailleurs possible d&#8217;intégrer Struts et JSF au sein d&#8217;une même application. Cependant ces 2 Frameworks se recoupent et il n&#8217;est pas évident de choisir l&#8217;un ou l&#8217;autre lorsque l&#8217;on commence à développer une application Web J2EE. Avant de vous donner mon avis, j&#8217;ai traduit et adapté un article de Roland Barcia dispo en anglais ici

Quelles sont les différences entre ...]]></description>
			<content:encoded><![CDATA[<p>Entre Struts et JSF mon coeur balance&#8230; En fait pas du tout. Après avoir mis le nez dans le code, je pense que les 2 ne sont pas antagonistes. Il est d&#8217;ailleurs possible d&#8217;intégrer Struts et JSF au sein d&#8217;une même application. Cependant ces 2 Frameworks se recoupent et il n&#8217;est pas évident de choisir l&#8217;un ou l&#8217;autre lorsque l&#8217;on commence à développer une application Web J2EE. Avant de vous donner mon avis, j&#8217;ai traduit et adapté un article de Roland Barcia <a href="http://homepage1.nifty.com/algafield/barcia.html">dispo en anglais ici</a><br />
<br/><br />
<b>Quelles sont les différences entre JSF et Struts ?</b></p>
<p>Tout d&#8217;abord JSF est jeune. Par rapport à Struts, la première version de JSF est sortie en main 2004. Struts au contraire est maintenant un framework mature. En cours de dévelopement, il est plus facile de trouver de l&#8217;aide pour Struts que pour JSF.</p>
<p>Au niveau du design, je pense que JSF est plus puissant que Struts, surtout pour la partie <i>View</i>.<br />
Struts se base sur un <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/FrontController.html">FrontController</a> et le pattern <i>Command</i>. JSF est basé sur le pattern <i>Page Controller</i>. Pour Struts, une servlet recupere la requête HTTP et transforme en ActionForm les paramètres avant de déléguer le tout à une Action class. Cela permet de coder simplement et rapidement des actions. L&#8217;inconvénient est qu&#8217;une seule Action est executée par appel. Le <i>Model</i> et les Actions (<i>Controller</i>) sont parfois mélangés.<br />
<br/>JSF est basé sur le pattern Page Controller. Chaque requête passe par une seule servlet comme Struts à la différence que cette servlet recupère une Page complète avec ses composants (Input type, label, table&#8230;) et ensuite va déclencher des évenements pour chacun d&#8217;eux. En plus du contexte de la page il existe donc un contexte de vue (FacesContext). La partie rendu est ensuite déléguée à une couche de présentation qui se charge de rendre du HTML, du WAP ou autre. Les objets de votre modèle peuvent être cablé avec des componsants JSF (label, text, table) directement dans la page JSP en utilisant la syntaxe JSF EL (Expression Language). Lorsque l&#8217;utilisateur modifie et retourne la page vers le serveur, JSF peut mettre à jour le modèle de données automatiquement. C&#8217;est le principe du Backed Beans. JSF d&#8217;un point de vue évenement peut gérer le changement d&#8217;état de plusieurs composants sur une seule page et donc, par la gestion d&#8217;event inspirée de <b>Swing</b>, effectuer plusieurs tâches par requêtes. Côté Struts on ne gère qu&#8217;um seul évenement par requête. Enfin les ActionForms doivent étendre les class de Struts, ce qui force votre model à dépendre de Struts alors que JSF gére des POJO (Plain Old Java Object) ou Bean sans aucunes dépendances. La partie modèle de votre application avec JSF n&#8217;a aucunes dépendances sur JSF. D&#8217;ailleurs dans notre application de monitoring, nous nous contentons d&#8217;envoyer des simples POJO pour décrire une machine, une application et un processus.<br/><br />
Côté navigation les deux frameworks sont similaires. Cependant JSF ajoute en plus la possibilité de déclarer sur un élément de la page le nom de la vue suivante à afficher et à déléguer la partie &laquo;&nbsp;action&nbsp;&raquo; à une méthode.<br />
Pour la mise en page et l&#8217;intégration, JSF permet d&#8217;écrire ses propres composants. <a href="http://myfaces.apache.org/">MyFaces</a> propose des composants vraiment puissant comme Tree2 que nous utilisons dans notre application.</p>
<p><b>Est-ce que JSF est mieux que Struts ?</b> Il est bien trop tôt pour y répondre. Tout d&#8217;abord historiquement, <a href="http://blogs.sun.com/roller/page/craigmcc">Craig McClanahan</a> l&#8217;un des auteurs de Struts a participé aux spécifications de JSF. Il est donc clair que l&#8217;un n&#8217;est pas le concurent de l&#8217;autre. Lisez <a href="http://blogs.sun.com/roller/page/craigmcc/20040927">ce post de Craig</a> où il donne son point de vue sur JSF et Struts. Sa conclusion est que JSF est plus puissant que Struts sur la partie View, et que Struts dans le future devra se concentrer sur la partie Model-Controller. Il y a d&#8217;ailleurs une librairie <a href="http://cvs.apache.org/builds/jakarta-struts/nightly/struts-faces/">Struts-Faces</a> qui permet de faciliter l&#8217;intégration de JSF sur une appli utilisant Struts.</p>
<p><b>Le problème:</b> Struts est un framework 100% MVC, alors que JSF est un framework basé sur la page que voit l&#8217;utilisateur. Avec Struts vous devez coder l&#8217;action <i>doLogin</i> et la câbler pour qu&#8217;elle soit déclencher lorsque l&#8217;utilisateur effectue une action précise.<br/><br />
Avec JSF vous pouvez:</p>
<ul>
<li>- déclarer que <i>doLogin</i> est appelé si l&#8217;utilisateur clique sur le bouton <i>Submit</i></li>
<li>- appeler la fonction <i>doLogin</i> parce que le champ Login et le champ Password ont été rempli</li>
<li>- valider que le champ &laquo;&nbsp;Login&nbsp;&raquo; n&#8217;est pas vide et fait au moins 8 caractères grâce à un Validator</li>
<li>- afficher en anglais, en japonais ou autre les labels &laquo;&nbsp;Login:&nbsp;&raquo; et &laquo;&nbsp;Password:&nbsp;&raquo; </li>
<li>- ne pas afficher par exemple le bouton <i>Submit</i> si l&#8217;utilisateur est déjà authentifié</li>
<li>- etc&#8230;</li>
</ul>
<p>La différence est assez énorme: Struts est basé sur des Actions définies, JSF est basé sur un GUI représenté dans un navigateur sur lequel l&#8217;utilisateur a la possibilité de tout faire. Vous voyez pourquoi JSF gére plusieurs événement à la fois ? JSF est donc adapté lorsque ce que vous représentez à l&#8217;utilisateur est un GUI complexe composé de plusieurs actionneurs. Sinon pour la petite histoire, JSF est fortement inspiré des WebForms du framework Microsoft ASP.NET.</p>
<p>Voici donc les liens que j&#8217;ai le plus visité sur mon Bookmark JSF avec Firefox:</p>
<ul>
<li><a href="http://www.jsftutorials.net/">JSF Tutorial</a> très complet, avec des tutoriaux.</li>
<li><a href="http://www.exadel.com/tutorial/jsf/jsftutorial-kickstart.html">JSF QuickStart</a> est un tutorial qui explique les grands principes de JSF.</li>
<li><a href="http://myfaces.apache.org/">MyFaces</a> est une implémentation open-source de JavaServer(tm) Faces</li>
<li><a href="http://java.sun.com/j2ee/javaserverfaces/">La page de SUN sur JSF</a> donne accès à des articles, des FAQ et la possibilité de télécharger l&#8217;implémentation de référence.</li>
<li><a href="https://javaserverfaces.dev.java.net/">JavaServerFaces sur Java.Net</a> est le site de la communauté open-source Java.Net sur JSF. C&#8217;est ici que l&#8217;on peut suivre le boulot des développeurs de l&#8217;implémentation de référence.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2005/06/17/struts-ou-jsf-ou-les-deux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

