Exemple plus complexe EJB3 + GWT + annotations + entities dans le client GWT

De $1

Version de 20:24, 25 Avr 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

Introduction

Ce TP est la suite de celui-ci : EJB3 et GWT, petit exemple avec Netbeans.

Dans ce TP, nous allons continuer le travail du TP précédent en faisant passer des entity beans dans la partie GWT, y compris la partie "client" qui va être traduite en javascript.

Par exemple, on a envie de rajouter au service GWT une méthode qui renvoie la liste de tous les Students.

NOTE : ces informations ont été synthétisées à partir de la documentation en ligne de GWT et quelques messages dans le google group GWT. Je vous conseille de lire la partie "configuration d'un module" GWT.

On commence par modifier rajouter  l'interface GWTService que doit implémenter le service côté serveur (qui est une servlet)


package org.yournamehere.client;
import com.google.gwt.user.client.rpc.RemoteService;
import fr.unice.howto.gwtejb.entities.Student;
import java.util.Collection;

/**
 *
 * @author buffa
 */
public interface GWTService extends RemoteService {

    public String addStudent(String firstName, String lastName);

    public Collection<Student> getAllStudents();
}

Puis on implémente la méthode dans la classe du service

Exactement comme on avait fait pour qu'une servlet appelle un EJB session, on va faire click droit/entreprise/call enterprise bean et selectionner le bean StudentManager :



package org.yournamehere.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import fr.unice.howto.gwtejb.entities.Student;
import fr.unice.howto.gwtejb.managers.StudentManagerLocal;
import java.util.Collection;
import javax.ejb.EJB;
import org.yournamehere.client.GWTService;

/**
 *
 * @author buffa
 */
public class GWTServiceImpl extends RemoteServiceServlet implements
        GWTService {

    @EJB
    private StudentManagerLocal studentManagerBean;

    public String addStudent(String firstName, String lastName) {

        Student s = studentManagerBean.addStudent(firstName, lastName);

        return "A new student has been added: #" + s.getId() + " - " + s.getFirstName() + " " + s.getLastName();
    }

    public Collection<Student> getAllStudents() {
        return studentManagerBean.getAllStudents();
    }
}

Côté client, on modifie aussi l'interface que va implémenter le proxy asynchrone (généré par le compilateur)


Rappel : ce proxy est en javascript, c'est lui qui va s'occuper de sérialiser les objets en XML et qui va appeler l'URL de la servlet/service que vous avez implémenté côté serveur. C'est lui aussi qui va désérialiser les valeurs de retour et appeler le callback asynchrone (qui sera du javascript au final)



package org.yournamehere.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
 *
 * @author buffa
 */
public interface GWTServiceAsync {
    public void addStudent(String firstName, String lastName, AsyncCallback callback);
    public void getAllStudents(AsyncCallback callback);
}

Toujours côté client, on modifie la classe qui construit l'interface graphique

On ajoute un bouton "list all students", on créée une FlexTable (un tableau dont on peut faire varier le nombre de lignes et de colonnes) qui servira à afficher les résultats, on implémente le callback asynchrone qui va recevoir la liste des étudiants, etc. ATTENTION, JE N'AI MIS ICI QUE LES ELEMENTS IMPORTANTS, il manque du code (la méthode qui créée les services, etc...) Faite copier/coller des trucs en gras dans le code du TP EJB3 et GWT, petit exemple avec Netbeans.

/*
 * GWTServiceUsageExample.java
 *
 * Created on 18 novembre 2008, 08:11
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package org.yournamehere.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;

import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import fr.unice.howto.gwtejb.entities.Student;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Example class using the GWTService service.
 *
 * @author buffa
 */
public class GWTServiceUsageExample extends VerticalPanel {

    private Label lblServerReply = new Label();
    private TextBox txtFirstName = new TextBox();
    private TextBox txtLastName = new TextBox();
    private Button btnSend = new Button("Send to server");
    private Button btnGetAllStudents = new Button("List all students");
    private FlexTable studentTable = new FlexTable();

    public GWTServiceUsageExample() {
        add(creeFormulaireDeSaisi

        add(new Label("FirstName & LastName: "));
        add(txtFirstName);  // modifie
        add(txtLastName); // rajoute
        add(btnSend);
        add(lblServerReply);

        // Bouton qui permet de lister tous les étudiants
        add(btnGetAllStudents);

        // FlextTable (table dont on peut rajouter ou enlever des lignes ou des colonnes
        initStudentTable();
        add(studentTable);     

        // Callback du services getAllStudent()
        final AsyncCallback getAllStudentscallback = new AsyncCallback() {

            public void onSuccess(Object result) {
                Collection<Student> listOfStudents = (Collection<Student>) result;
                                // On reconstruit la table                int index = 0;
                for (Student s : listOfStudents) {
                    studentTable.setHTML(1+index, 0, s.getFirstName());
                    studentTable.setHTML(1+index, 1, s.getLastName());
                    index++;
                }
            }

            public void onFailure(Throwable caught) {
                lblServerReply.setText("Communication failed");
            }
        };      

        // On s'assure que la table des étudiants est remplie au départ
        getService().getAllStudents(getAllStudentscallback);      

        // Listen for the button clicks
        btnSend.addClickListener(new ClickListener() {

            public void onClick(Widget w) {

                getService().addStudent(prenomTextBox.getText(), nomTextBox.getText(), callback);
                // QUand on ajoute un étudiant on redessine la table
                getService().getAllStudents(getAllStudentscallback);
            }
        });

        // Ecouteur du bouton qui liste tous les étudiants
        btnGetAllStudents.addClickListener(new ClickListener() {
            public void onClick(Widget w) {
                getService().getAllStudents(getAllStudentscallback);
            }
        });
    }

    // Methode utilitaire qui contruit une Flex Table
    private void initStudentTable() {

        FlexCellFormatter cellFormatter = studentTable.getFlexCellFormatter();
        studentTable.addStyleName("cw-FlexTable");
        studentTable.setWidth("32em");
        studentTable.setCellSpacing(5);
        studentTable.setCellPadding(3);

        // Add some text
        cellFormatter.setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER);
        studentTable.setHTML(0, 0, "Liste des étudiants");
        cellFormatter.setColSpan(0, 0, 2);

    }
}

Et alors ça marche ?

Ben non, ça ne marche pas !!!! Même si on a mis une référence du projet EJB dans le projet WAR (ce qui a pour effet de rajouter au classpath les classes du projets ejb, situées dans GWTEJB-ejb.war), le compilateur ne trouve pas les sources de Student.java, c'est normal, car une application GWT se compose de modules. Un module est défini par un fichier xml qui indique où se trouvent les répertoires qui contiennent les sources qui seront candidats à la traduction en javascript, où se trouvent les feuilles css utilisées par le projet etc...



Regardons la définition du module GWT de ce projet. Il s'agit du fichier Main.gwt.xml, situé dans le package org.yournalehere (nom du package par defaut quand on créée un projet GWT, nous aurions du le changer pour quelque cose de plus parlant).



Par défaut ce fichier indique les préférences du module :



<?xml version="1.0" encoding="UTF-8"?>
<module>
    <inherits name="com.google.gwt.user.User"/>
        

 
      <!--   package "public" par defaut -->
    <stylesheet src="student.css"/>
    
    <entry-point class="org.yournamehere.client.MainEntryPoint"/>
    <!-- Do not define servlets here, use web.xml -->
</module>

Ce fichier indique qu'il hérite d'un autre module : le module com.google.gwt.user.User qui est en fait un répertoire dans le fichier .jar de la distribution GWT. Ce jar contient les classes MAIS AUSSI LES SOURCES des classes GWT qui vont être candidats à la compilation en javascript. Par defaut, et de manière implicite, le module ajoute aussi les fichiers .java situées DANS LE SOUS PACKAGE "client" du package où se trouve le fichier xml du module. Dans ce cas, le répertoire contenant le package org.yournamehere.client est dans ce cas, si bien que les classes GWTService.java, etc... seront bien prises en compte par le compilateur.

Au passage, les feuilles de style et autres ressources HTML, gif, jpeg, etc... sont en général situées dans le sous-package "public" (dans notre cas org.yournamehere.public, qui contient entre autre la feuille student.css :

.cw-FlexTable td {
    border: 1px solid #BBBBBB;
    padding: 3px;
}

Comment indiquer que l'on veut rajouter les sources de nos entity beans dans les chemins de recherche ?

La marche à suivre consiste à créer un module GWT dans le projet EJB, dans le package juste au-dessus de celui qui contient les EJBs entities, et d'indiquer dans le fichier de configuration que ce module a ses sources dans le sous-package qui contient les entity beans :

ejb6.jpg

 

Malehureusement ça ne suffit pas encore ! Il faut aussi indiquer au module principal qu'il "hérite" du module que nous venons de créer !

Il suffit d'ajouter une ligne dans le fichier Main.gwt.xml du projet war :

 

ejb2.jpg


 

Voilà, ça commence à prendre forme ! Mais heu ... j'ai bien lu la documentation et ça ne marche toujours pas, j'ai toujours l'erreur de compilation qui me dit que je ne trouve pas les sources ??? Pourtant j'ai suivi à la lettre la documentation de GWT, etc...


Penser à modifier les propriétés du projet EJB POUR QU'IL METTE LES SOURCES DANS LE .jar qui est créé lors d'un build !
ejb5.jpg

Bon, rebuildez, là ça trouve les sources mais.... argl ! Il y a plein d'erreurs de compilation !!!!! C'est à cause des annotations de code dans les entity beans !

Que faire ???? Le problème est assez compliqué et fait l'objet de nombreuses discussions dans la communauté GWT : faut-il ignorer les annotations ? Non évidemment car il y en a de nombreuses qui existent et qui n'ont pas de rapport avec les EJBs, il faut donc les conserver ! La solution consiste pour le moment à rajouter les sources des classes correspondant à des annotations dans le chemin de recherche du compilateur javascript. Sur le site de la communauté les développeurs ont mis à disposition un fichier .jar qu'il suffit de mettre dans le classpath du projet war pour que cela fonctionne :

ejb4.jpgPour le rajouter, créez un répertoire lib dans le projet war, et copiez dedans ce fichier : jpa-annotations-source.jar

 


Ensuite, allez dans properties/libraries et ajoutez le fichier (cliquez sur la case "chemin relatif").


Si tout va bien, construisez, deployez, exécutez le projet entreprise, ça doit fonctionner.

 

ET MAINTENANT ?


Bien, maitenant, vous reprenez le TP sur les comptes bancaires et vous le mettez à fond à la sauce GWT, une seule page de gui, des pannels avec des tabs, des zones de saisie, des boutons, des flextables et vous refaites entièrement l'interface ! Vous savez comment mélanger GWT et EJB3 à 100% alors allez-y !