jeudi 15 juillet 2010

Jouons avec CDI et Wicket

Après une série d'articles consacrés à Play framework, je vous propose aujourd'hui d'étudier l'intégration de CDI (context and dependency injection) avec Wicket. Voyons comment ces 2 frameworks peuvent être mariés et quelle souplesse CDI va nous apporter lors du développement d'une application web avec Wicket.


CDI est une des spécifications de Java EE6. Cette spec apporte des solutions pour l'injection de dépendance et la gestion des scopes (requêtes, session, application et le petit nouveau, le scope conversation).
Nous allons utiliser Weld, l'implémentation de JBoss dérivée de Seam, qui est également l'implémentation de référence de CDI.

Voici les dépendances maven à utiliser :


            javax.enterprise
            cdi-api
            provided
            1.0
        

        
            javax.annotation
            jsr250-api
            provided
            1.0
        

        
            org.jboss.weld
            weld-wicket
            1.0.1-Final
        
      
        
            org.apache.wicket
            wicket
            1.4.9
        


Si vous utilisez JBoss ou Glassfish, pas besoin de configuration supplémentaire, Weld sera pris en compte automatiquement par le serveur.

Sinon, si vous utilisez par exemple Tomcat ou Jetty, ajoutez ce listener à votre fichier web.xml :


      org.jboss.weld.environment.servlet.Listener


Avec ces serveurs, ajoutez aussi ceci à vos dépendances maven :


            org.jboss.weld.servlet
            weld-servlet
            1.0.1-Final
        

et supprimez la mention "provided" pour les librairies de Weld et de la JSR 250.


Voyons maintenant un peu de code Java. Comme vous le savez peut être, Wicket est un framework stateful. Il garde dans la session http un certain nombre d'informations, comme la valeurs des attributs définis dans une page web. C'est le framework qui gère la durée de rétention de ces informations. Il le fait de manière plutôt intelligente, par exemple pour assurer le bon fonctionnement du bouton retour du navigateur. Cependant on aimerait parfois pouvoir gérer plus finement ce mécanisme. Nous allons pouvoir le faire grâce à CDI et aux conversations.

Définition d'un bean CDI :

@ConversationScoped
public class ConversationBean implements Serializable
{
   private int value;
   
   @Inject Conversation conversation;
   

   @PostConstruct
   public void init(){
      this.value = -1;
   }

    void x2() {
        value = value * 2;
    }


    void begin() {
        conversation.begin();
        this.value = 1;
    }

    void end() {
        conversation.end();
    }
    

    public int getValue() {
        return value;
    }
}

Ce bean a une portée "ConversationScoped". Cela signifie que son état sera conservé côté serveur durant la durée de la conversation. La méthode begin() permet de démarrer la conversation , end() permet de la fermer. Il contient uniquement un attribut value, initialisé à -1 à la création de l'objet (grâce à l'annotation @PostConstruct), et initialisé à 1 au démarrage d'une conversation. Lé méthode x2() multiplie par 2 l'entier value.

Créons maintenant une page côté Wicket pour accéder à notre bean CDI :

Partie Java:
@Inject ConversationBean cbean;

   public TestPage(){
       final Label number = new Label("number", new Model());
       number.setOutputMarkupId(true);
       add(number);
       AjaxLink begin = new AjaxLink("begin") {

            @Override
            public void onClick(AjaxRequestTarget target) {
              cbean.begin();
              number.setDefaultModelObject(cbean.getValue());
              target.addComponent(number);
            }
        };
        add(begin);
         AjaxLink x2 = new AjaxLink("x2") {

            @Override
            public void onClick(AjaxRequestTarget target) {
               cbean.x2();
               number.setDefaultModelObject(cbean.getValue());
               target.addComponent(number);
            }
        };
        add(x2);
         AjaxLink end = new AjaxLink("end") {

            @Override
            public void onClick(AjaxRequestTarget target) {
              cbean.end();
              setResponsePage(TestPage.class);
            }
        };
        add(end);
}
On injecte notre ConversationBean avec l'annotation @Inject. Notez que l'on n'a plus besoin de Spring pour injecter nos dépendance lorsque l'on utilise Weld avec Wicket.


Partie html :
<html>
   <head></head>
   <body>
      <h1>Test</h1>
         <span wicket:id="number"/><br/>
         <a wicket:id="begin">Start</a>-<a wicket:id="x2">x2</a>-<a wicket:id="end">End</a>
   </body>
</html>


On obtient la page suivante :

Au démarrage on retrouve la valeur d'initialisation de l'attribut value : -1.
En cliquant sur x2 plusieurs fois de suite, on voit qu'on ne peut pas faire plusieurs multiplications de suite. On reste bloquer à la valeur -2. C'est normal, le bean CDI ne conserve pas d'état côté serveur tant que la conversation n'est pas démarrée. On recommence donc à chaque fois la même opération avec un attribut value initialisé à -1.
Par contre si on clique sur Start, la conversation démarre, value est positionné à 1 et chaque clic sur x2 va doubler la valeur affichée : 1, 2, 4, 8, 16 etc.
En cliquant sur End, on peut mettre fin à cette conversation, libérer le bean CDI en mémoire sur le serveur et recharger la page.

Grâce à CDI, on a pu prendre la main sur la rétention côté serveur de l'état du label number de notre page Wicket.
Dans un  prochain exemple nous verrons comment mettre ça en pratique dans un cas d'utilisation réel, avec un wizard, des objets persistants, des transactions...