I. Introduction

I-1. Prérequis

Le lecteur devra avoir une connaissance de java J2EE et du framework struts. Cet article est d'un niveau intermédiaire.

I-2. Pourquoi cet article ?

L'objectif de cet article est tout d'abord à but d'auto-formation. Le but était d'intégrer GWT dans une appli déjà existante et de l'éprouver face à des méthodes de développement industriel. Le travail réalisé lors de cet apprentissage peut permettre à d'autres personnes dans des situations similaires de ne pas recommencer à partir de zéro. Il s'agit d'une base de travail mais en aucun cas d'une référence absolue puisque cet article s'est construit à tâtons et que les solutions décrites ne sont pas obligatoirement les meilleures. Le framework GWT est récent puisqu'il apparaît en 2006. Sa communauté est encore jeune et la documentation autour du produit reste limité à celle du site officiel, il s'agissait d'enrichir cette communauté.

I-3. Qu'est-ce que GWT ?

Pour définir GWT je citerais un article de ce même site. Les grandes lignes y sont décrites et je prendrais comme hypothèse dans la suite de l'article que les principes généraux sont connus.

I-4. Problématique abordée

Le framework GWT s'il peut paraître très prometteur a de quoi laisser perplexe les développeurs J2EE. En effet celui-ci n'est pas un simple framework java vers Javascript, c'est aussi une autre façon de concevoir un site web. La méthode de développement est proche d'une appli swing avec son modèle évènementiel.

Partant du constat que l'approche est très différente, il semble d'un premier abord que l'utilisation de GWT est réservée aux nouveaux développements, aux sites web partant de zéro. Cependant ce n'est pas exact, en effet allons justement nous pencher sur le fonctionnement de GWT pour approfondir la façon dont il peut s'interfacer avec d'autres frameworks, non sans mal malheureusement. Je vous encourage cependant vivement à lire l'article suivant avant celui ci : mon premier projet GWT.

II. L'existant : une application struts et tiles

II-1. Organisation

Pour démarrer, nous allons partir d'un projet testGWTStep1 entièrement basé sur struts et tiles. Les sources sont fournies en annexe. Voici tout d'abord l'organisation générale du projet :

Organisation
Organisation du projet

II-2. Caractéristiques

Ses caractéristiques principales sont d'afficher une liste de contacts et de permettre de filtrer l'affichage des résultats par un petit formulaire. Le projet est construit avec maven2 et on peut visualiser le résultat aisément grâce au plugin cargo.

Le projet n'est ici qu'une illustration ayant pour but de coller à l'application décrite dans le tutorial mon premier projet GWT. Tout est regroupé dans le même projet mais les packages font ressortir une organisation classique :

  • un package regroupant les modèles de l'application
  • un package regroupant les services
  • un package regroupant les actions

Dans le paramétrage struts on retrouve l'utilisation de tiles et notamment l'utilisation d'un layout dont le contenu est alimenté par l'action, les headers et footers étant génériques pour tout le site.

III. Intégration de GWT

III-1. Le build : Maven 2

Une contrainte évidente est de continuer à utiliser maven 2 pour le build. Il était hors de question d'intégrer GWT dans une appli sans avoir un processus automatique de build. GWT est fourni avec un shell permettant de produire un script de compilation assez basique et non industriel mais heureusement il existe déjà plusieurs plugins maven actuellement disponibles sur Internet qui m'éviteront la tâche d'en réaliser un :

Je n'ai testé que les deux premiers, si une personne désire tester le troisième qui n'est pour l'instant que dans le sandbox des plugins codehaus ce sera un enrichissement intéressant de cet article.

Le test du plugin google s'est révélé peu concluant, la documentation est laconique et les erreurs signalées ne m'ont pas permis de poursuivre. Sa configuration est complexe en regard du plugin gwtforge. Un élément intéressant cependant est que la doc indique l'existence d'un repository mavenrepository maven. On y retrouvera notamment les jars GWT et GWTx.

Ce sera finalement le plugin gwtforge qui sera mon choix puisque j'ai réussi à l'utiliser pour builder un artefact. Lui aussi est couplé avec un repository mavenrepository maven. Ci-dessous un exemple de configuration avec ce plugin :

Configuration maven2
Sélectionnez

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">


	<groupId>com.developpez.gwt</groupId>
	<version>1.0.0</version>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>testGWTStep1</artifactId>
	<name>testGWTStep1</name>
	<description>testGWTStep1</description>
	<packaging>war</packaging>

	<properties>
		<tomcat.containerId>tomcat5x</tomcat.containerId>
		<tomcat.downloadUrl>http://www.apache.org/dist/tomcat/tomcat-5/v5.5.26/bin/apache-tomcat-5.5.26.zip</tomcat.downloadUrl>
		<tomcat.port>8082</tomcat.port>
		<tomcat.installDir>${java.io.tmpdir}/cargoapache/installs</tomcat.installDir>
		<java.awt.headless>true</java.awt.headless>
		<cargo.wait>true</cargo.wait>
	</properties>
	<repositories>
		<repository>
			<id>gwtforge</id>
			<url>http://gwtforge.com/maven-repository/trunk/</url>
		</repository>
		<repository>
			<id>gwt-maven</id>
			<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>gwt-maven</id>
			<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
		</pluginRepository>
		<pluginRepository>
			<id>internal</id>
			<url>http://build-1.rack.sourcelabs.com/repos/internal/</url>
		</pluginRepository>
		<pluginRepository>
			<id>gwtforge</id>
			<url>http://gwtforge.com/maven-repository/trunk/</url>
		</pluginRepository>
	</pluginRepositories>
	<build>
		<plugins>
			<plugin>
				<groupId>org.codehaus.cargo</groupId>
				<artifactId>cargo-maven2-plugin</artifactId>
				<version>0.3.1</version>
				<configuration>
					<wait>${cargo.wait}</wait>
					<container>
						<containerId>${tomcat.containerId}</containerId>
						<log>${project.build.directory}/${tomcat.containerId}/cargo.log</log>
						<zipUrlInstaller>
							<url>${tomcat.downloadUrl}</url>
							<installDir>${tomcat.installDir}</installDir>
						</zipUrlInstaller>
					</container>
					<configuration>
						<home>${project.build.directory}/${tomcat.containerId}/container</home>
						<properties>
							<cargo.servlet.port>${tomcat.port}</cargo.servlet.port>
							<cargo.logging>high</cargo.logging>
						</properties>
					</configuration>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.cargo</groupId>
				<artifactId>cargo-maven2-plugin</artifactId>
				<executions>
					<execution>
						<id>start-container</id>
						<phase>pre-integration-test</phase>
						<goals>
							<goal>start</goal>
						</goals>
					</execution>
					<execution>
						<id>stop-container</id>
						<phase>post-integration-test</phase>
						<goals>
							<goal>stop</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>gwtforge.gwtplugin</groupId>
				<artifactId>gwtforge.gwtplugin</artifactId>
				<version>1.4.61</version>

				<!--Associate the plugin to the maven compile phase-->

				<executions>
					<execution>
						<phase>compile</phase>
						<goals>
							<goal>compile</goal>
						</goals>
					</execution>
				</executions>

				<configuration>
					<!-- The plugin will discover the modules by itself -->
					<out>gwt</out>
					<loglevel>INFO</loglevel>
				</configuration>

			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.4</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>struts</groupId>
			<artifactId>struts</artifactId>
			<version>1.2.9</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>com.google.gwt</groupId>
			<artifactId>gwt-servlet</artifactId>
			<version>1.4.61</version>
		</dependency>
		<dependency>
			<groupId>com.google.gwt</groupId>
			<artifactId>gwt-user</artifactId>
			<version>1.4.60</version>
			<scope>compile</scope>
		</dependency>
	</dependencies>
</project>

Nous pourrons noter les éléments importants rajoutés à ce fichier pom.xml par rapport au projet initial :

  • Le scope compile des jars liés à GWT, ceux-ci ne servent plus une fois les pages compilées (sauf si vous utilisez GWT RPC).
  • Le rattachement du goal compile du plugin gwtforge à la phase de compilation de maven.

A noter que cette étape n'est cependant pas à la hauteur de l'intégration voulue, je n'ai pas réussi à séparer correctement les ressources GWT du code java comme nous le verrons par la suite. J'avais en effet pris l'habitude de séparer le code dans src/main/java, les ressources dans src/main/resources et les ressources web dans src/main/webapp. Ici je vais toujours conserver mon fichier de description de module xml, mes fichiers html et css dans src/main/java contre toute logique.

III-2. Ajout d'un point d'entrée GWT

Un point d'entrée pour GWT (ou entry point) permet de spécifier la classe principale de l'application. C'est le point de démarrage.

Premier temps de notre intégration, nous allons simplement intégrer les fichiers du tutorial mon premier projet GWT après quelques légères modifications. Nous rajouterons juste une seconde entrée dans le menu supérieur pour accéder directement à cette page.

Première constatation, après l'ajout du fichier MonApplication.gwt.xml l'intégration avec maven 2 semble fonctionner correctement d'après le log :

 
Sélectionnez

[INFO] + Discovering gwt modules at E:\workspaceEuropa\testGWTStep1\src\main\java
[INFO]     - Module found at E:\workspaceEuropa\testGWTStep1\src\main\java\com\developpez\gwt\MonApplication.gwt.xml
[INFO] + Adding Modules to compilation target
[INFO]     - Add Module com.developpez.gwt.MonApplication
[INFO] Target compilation directory [E:\workspaceEuropa\testGWTStep1\target\testGWTStep2-1.0.0\gwt]
[INFO] + Lauching GwtCompiler .... Module : com.developpez.gwt.MonApplication
[INFO]     - Output will be written into E:\workspaceEuropa\testGWTStep1\target\testGWTStep2-1.0.0\gwt\com.developpez.gwt.MonApplication
[INFO]     - Copying all files found on public path
[INFO]     - Compilation succeeded
[INFO] GwtCompiler .... Module : com.developpez.gwt.MonApplication Build Success !

Voici les points importants à noter lors de cette première étape :

  • 3 nouveaux packages apparaissent : client, public et server et on y notera des doublons par rapport au package model et service déjà existants.
  • l'intégration des ressources GWT directement dans src/main/java
  • l'ajout d'une servlet dans le web.xml afin de pointer vers le service des contacts
  • la page généré n'est pas liée au reste du site
Organisation
Résultat du build

Les prochaines étapes vont donc porter sur :

  • la normalisation des packages et une tentative de conserver notre modèle d'avant.
  • la normalisation de l'utilisation des ressources dans une arborescence maven2
  • l'utilisation de notre action struts pour remplacer la servlet GWT
  • l'intégration de la page générée dans une tiles du site original

III-3. Normalisation des packages

III-3-1. package client

Ici nous nous attarderons sur les packages contenus dans com.developpez.gwt.client. Pour rappel GWT va utiliser les classes présentes ici pour les exposer côté client en Javascript. Ce qui implique que ce package ne doit contenir que des classes convertibles et donc un sous-ensemble de java contenu dans java.util et java.lang. Le projet GWTx propose cependant d'étendre ce sous-ensemble aux classes suivantes :

  • java.io.Serializable
  • java.beans.PropertyChangeSupport
  • java.beans.PropertyChangeListener
  • java.beans.PropertyChangeListenerProxy
  • java.beans.PropertyChangeEvent
  • java.beans.IndexedPropertyChangeEvent
  • java.util.EventListenerProxy
  • java.util.StringTokenizer
  • java.util.logging.*

Pour en bénéficier il suffit de rajouter la dépendance dans le pom :

Dépendance GWTx
Sélectionnez

        <dependency>
            <groupId>com.googlecode.gwtx</groupId>
            <artifactId>GWTx</artifactId>
            <version>20070605</version>
            <scope>compile</scope>
        </dependency>

Dans cette phase, le package com.developpez.gwt.client.util va disparaître puisque nous ne tenons pas à conserver deux références vers un modèle identiques, la classe Contact. Dans cet exemple il aurait été simple de se contenter d'utiliser les deux ou bien de n'utiliser que celle de GWT mais imaginez l'intégration de GWT dans un contexte où vous avez déjà une cinquantaine de beans modèles existants et inclus dans un jar à part.

La première tentative naîve consistant à supprimer la classe dans com.developpez.gwt.client.util échouera :

 
Sélectionnez

[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Gwt Compiler Errors :    [ERROR] Errors in 'E:\workspaceEuropa\testGWTStep1\src\main\java\com\developpez\gwt\client\ContactService.java'
      [ERROR] Line 3:  The import com.developpez.gwt.model cannot be resolved
      [ERROR] Line 8:  Contact cannot be resolved to a type
   [ERROR] Errors in 'E:\workspaceEuropa\testGWTStep1\src\main\java\com\developpez\gwt\client\panel\ContactsPanel.java'
      [ERROR] Line 5:  The import com.developpez.gwt.model cannot be resolved

En effet le plugin GWT s'attend à trouver une arborescence normalisée dans client et seul le module gwt.client constitue son classpath. Il va donc être nécessaire de modifier le fichier descripteur du module en nous penchant sur la documentation officielledocumentation officielle.

 
Sélectionnez

Adds packages to the source path by combining the package in which the module XML is found with the specified path to a subpackage. 
Any Java source file appearing in this subpackage or any of its subpackages is assumed to be translatable.

Cette phrase d'apparence anodine a cependant des implications importantes, toute classe de mon package doit être "translatable", c'est à dire pouvant être inclus du côté client et n'utiliser que du java.util ou java.lang. Si c'est bien le cas dans notre exemple, cette limitation est cependant très importante. Nous en reparlerons dans la conclusion.

Pour continuer nous ajoutons donc les deux lignes suivantes

MonApplication.gwt.xml
Sélectionnez

	<source path='model'/>
	<source path='client'/>

Comme ne le dit pas la documentation, les chemins sont en relatifs par rapport au fichier descripteur de module mais si on le surcharge, il faut lister tout les packages nécessaires. On peut supposer que dans le cas de packages n'étant pas dans la sous arborescence du descripteur cette notation soit problématique. De plus on imaginera bien que la multiplication de ce type de configuration sera vite difficile à suivre, l'IDE ne fournissant pas de moyen pour détecter ces erreurs avant la phase de compilation.

Au passage, si vous oubliez de rendre vos beans serializable, vous obtiendrez l'erreur ci-après. Dans notre cas il s'agissait bien d'un oubli que nous réparons tout de suite.

 
Sélectionnez

[INFO] + Lauching GwtCompiler .... Module : com.developpez.gwt.MonApplication
[INFO]     - Computing all possible rebind results for 'com.developpez.gwt.client.ContactService'
[INFO]     -    Rebinding com.developpez.gwt.client.ContactService
[INFO]     -       Invoking <generate-with class='com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator'/>
[INFO]     -          Generating client proxy for remote service interface 'com.developpez.gwt.client.ContactService'
[INFO]     -             Analyzing 'com.developpez.gwt.client.ContactService' for serializable types
[INFO]     -                Analyzing methods:
[INFO]     -                   public abstract com.developpez.gwt.model.Contact[] getAllContact()
[INFO]     -                      Return type: com.developpez.gwt.model.Contact[]
[INFO]     -                         com.developpez.gwt.model.Contact[]
[INFO]     -                            Analyzing component type:
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Gwt Compiler Errors :                               [ERROR] Type 'com.developpez.gwt.model.Contact' was not serializable and has no concrete se
rializable subtypes
[ERROR] Errors in 'E:\workspaceEuropa\testGWTStep1\src\main\java\com\developpez\gwt\client\panel\ContactsPanel.java'
   [ERROR] Line 89:  Failed to resolve 'com.developpez.gwt.client.ContactService' via deferred binding
[ERROR] Cannot proceed due to previous errors
[ERROR] Build failed

III-3-2. package serveur

Ici deux choix s'ouvrent à moi, celui de passer par GWT RPC ou celui de m'intégrer dans une action struts. En passant par GWT RPC il me suffit d'adapter l'appel de mon service déjà existant dans com.developpez.gwt.service et de l'utiliser par délégation dans com.developpez.gwt.server. L'avantage est de bénéficier de GWT RPC et des facilités données par l'API google pour déclarer ces services et les utiliser coté client.

Pour rappel, un service GWT RPC est composé de 3 entités : l'interface MyService qui étend RemoteService, l'interface MyServiceAsync utilisant un callback et une implémentation côté serveur MyServiceImpl qui étend RemoteServiceServlet et implémente MyService. Les noms et localisations de ces 3 entités sont normalisés par l'API google qui s'attend à les trouver à ces endroits précis.

GWT RPC prend en charge la sérialisation des objets et leur envoi, il masque la complexité lié au protocole d'échange et s'intègre plus facilement dans le code côté client. L'inconvénient que j'y vois c'est l'obligation de déclarer chaque nouvelle servlet dans le web.xml. De plus, les services n'étant plus des actions struts mais des actions gwt, il faut revoir éventuellement les pattern de vos filtres si vous en utilisez. Le point fort cependant c'est son intégration côté client comme le montre le code suivant :

ContactsPanel.java
Sélectionnez

	private void getAllContacts(){
		contacts = new Contact[0];
		// define the service you want to call
	    ContactServiceAsync svc = (ContactServiceAsync) GWT.create(ContactService.class);
	    ServiceDefTarget endpoint = (ServiceDefTarget) svc;
	    String moduleRelativeURL =  getContextUrl () + "/contactService";
	    
	    endpoint.setServiceEntryPoint(moduleRelativeURL);
	    
	    AsyncCallback callback = new AsyncCallback() 
		{
	      public void onSuccess (Object result)
	      {
	          contacts = (Contact[]) result;
	          filtre();
	      }

	      public void onFailure (Throwable ex)
	      {
	    	  contacts = new Contact[0];
	    	  filtre();
	      }
	    };
	    
	    // execute the service
	    svc.getAllContact(callback);

	  }

En contrepartie, se passer de GWT RPC implique de gérer soi-même les échanges d'information. Pour struts2 il existe un pluginhttp://cwiki.apache.org/S2PLUGINS/gwt-plugin-tutorial.html capable de s'occuper de la communication en JSON. En struts1 il n'existe pas de tel helper il va donc falloir passer par la gestion du retour à la main. L'appel se faisant cette fois de la façon suivante :

 
Sélectionnez

public void onClick(Widget sender) {
		String url = "http://host/context/url.gp3?param1=valeur1&amp;param2=valeur2";
		boolean good = HTTPRequest.asyncPost(url, "données envoyées depuis le client", new ResponseTextHandler(){
 
			public void onCompletion(String responseText) {
				// ici on devrait parser la réponse 
			
				Window.alert(responseText);
			}
		});
	}

Il n'existe pas selon moi de "meilleur choix", c'est pourquoi j'aborderais les deux solutions et mais ne proposerais que l'implémentations de la solution GWT RPC.

III-3-2-a. GWT RPC

Pour la première je me contenterais de faire appel à mon service dans ContactServiceImpl du package serveur.

ContactServiceImpl.java
Sélectionnez

	public List getAllContact() {
		
	    return com.developpez.gwt.service.ContactServiceImpl.getAllContact(null, null);
	}

On peut noter que le filtre ne se situe plus côté serveur mais côté client désormais. On notera aussi que je suis passé d'un tableau à une liste sans encombre. Sans encombre ? Pas tout à fait en réalité puisque j'ai un avertissement à la compilation :

Résultat
Sélectionnez

[INFO] + Lauching GwtCompiler .... Module : com.developpez.gwt.MonApplication
[INFO]     - Computing all possible rebind results for 'com.developpez.gwt.client.ContactService'
[INFO]     -    Rebinding com.developpez.gwt.client.ContactService
[INFO]     -       Invoking <generate-with class='com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator'/>
[INFO]     -          Generating client proxy for remote service interface 'com.developpez.gwt.client.ContactService'
[INFO]     -             Analyzing 'com.developpez.gwt.client.ContactService' for serializable types
[INFO]     -                Analyzing methods:
[INFO]     -                   public abstract java.util.List getAllContact()
[INFO]     -                      Return type: java.util.List
[INFO]     -                         [WARN] Type 'java.util.List' should be parameterized to help the compiler produce the smallest code size possible
 for your module. Since the gwt.typeArgs javadoc annotation is missing, all subtypes of Object will be analyzed for serializability even if they are n
ot directly or indirectly used
[INFO]     -                            [WARN] Checking all subtypes of Object which qualify for serialization
[INFO]     - Output will be written into E:\workspaceEuropa\testGWTStep1\target\testGWTStep3-1.0.0\gwt\com.developpez.gwt.MonApplication
[INFO]     - Compilation succeeded

Nous rajouterons donc l'annotation demandée sur l'interface du service (dommage tout de même que les generics ne soient pas gérés) :

ContactService.java
Sélectionnez

    /**
     * @gwt.typeArgs <java.lang.String>
     */
	public List getAllContact();

III-3-2-b. Action struts

Dans la seconde solution, nous devons gérer nous même la sérialization/désérialisation des données. Je ne détaillerais dans les sources mais je donnerais la théorie. Les deux étapes majeures consistent à envoyer du json d'une part en sortie de notre action struts, et de le lire dans un second temps côté client. Pour envoyer du json, il suffit de créer une action qui renverra vos données comme vous feriez dans un mode jsp classique. Sauf que la jsp permettra d'obtenir du json et non du html. Autrement dit vous allez créer une jsp qui va se présenter comme suit :

 
Sélectionnez

        <logic:iterate id="list" name="ContactList">
        	// création de vos items json
        	// ....
        </logic:iterate>

Et coté client, vous devez parser la réponse pour la désérialiser :

 
Sélectionnez

			public void onCompletion(String responseText) {
				// ici on devrait parser la réponse  responseText
				
				Window.alert(responseText);
			}

Rien ne vous oblige à passer par du json, cependant json est compris nativement en Javascript et son interprétation sera plus rapide que de faire un parsing de votre cru sur un formalisme non standard.

III-4. Normalisation des ressources et utilisation de tiles

Dans ce chapitre nous allons voir comment faire disparaître les fichiers ressources et de façon plus générale comment intégrer nos widgets GWT dans une jsp. Pour cela, revenons sur les fichiers de ressources utilisés par GWT :

  • MonApplication.css
  • MonApplication.html
  • MonApplication.gwt.xml

Le fichier MonApplication.css est inclus dans le fichier descripteur du module MonApplication.gwt.xml. Ce fichier peut tout simplement être déplacé dans le répertoire src/main/webapp/css ou même fusionné avec notre css déjà existante.

Le fichier MonApplication.html s'il est présent est copié dans gwt/com.developpez.gwt.MonApplication de la webapp. Cependant si l'on regarde la structure de ce fichier :

MonApplication.html
Sélectionnez

<html>
<head>
<!--                                           -->
<!-- Any title is fine                         -->
<!--                                           -->
<title>MonApplication GWT</title>

<!--                                           -->
<!-- The module reference below is the link    -->
<!-- between html and your Web Toolkit module  -->
<!--                                           -->
<meta name='gwt:module'
    content='com.developpez.gwt.MonApplication'>
</head>

<body>
<!--                                            -->
<!-- This script is required bootstrap stuff.   -->
<!-- You can put it in the HEAD, but startup    -->
<!-- is slightly faster if you include it here. -->
<!--                                            -->
<script language="javascript" src="gwt.js"></script>

<!-- OPTIONAL: include this if you want history support -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>

<div id="contentGWT"></div>
</body>
</html>

Seuls l'inclusion du jsp et le meta placés dans le header semblent important. Nous allons donc tout de suite passer à l'intégration du widget dans une jsp pour se passer de ce fichier.

Première étape, ajouter la définition de ma feuille de style dans le layout.jsp, nous allons profiter par la même occasion pour rajouter une tiles qui contiendra les metas. Les metas pouvant changer en fonction de la page et donc du widget utilisé :

layout.jsp
Sélectionnez

<%@ include file="includeTLD.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html:html xhtml="true">
<head>

<tiles:insert attribute="metas" flush="true" />

<link rel="stylesheet" href="css/standard.css" type="text/css"/>
<link rel="stylesheet" href="css/MonApplication.css" type="text/css"/>



<title>Test GWT (etape1)</title>
</head>

Nous rajoutons la définition du tiles metas dans le fichier tiles-def.xml et nous ne profitons pour définir une nouvelle page qui va l'utiliser.

tiles-def.xml
Sélectionnez

	<definition name=".layout" path="/jsp/layout/layout.jsp">
	    <put name="metas" value="/jsp/layout/metas.jsp" />
		<put name="header" value="/jsp/layout/header.jsp" />
		<put name="footer" value="/jsp/layout/footer.jsp" />
	</definition>
	<definition name="listContact" extends=".layout">
		<put name="body" value="/jsp/gestion/ContactList.jsp" />
	</definition>
    <definition name="listContactGwt" extends=".layout">
        <put name="metas" value="/jsp/gestion/metasGwt.jsp" />
        <put name="body" value="/jsp/gestion/ContactListGwt.jsp" />
    </definition>	

Et nous créons maintenant les deux nouvelles jsp qui nous intéressent : metasGwt.jsp et ContactListGwt.jsp

metasGwt.jsp
Sélectionnez

<meta name='gwt:module'
    content='com.developpez.gwt.MonApplication'>
ContactListGwt.jsp
Sélectionnez

<%@ include file="/jsp/layout/includeTLD.jsp"%>
<div id="contentGWT"/>
<script language="javascript" 
src="${pageContext.request.contextPath}/gwt/com.developpez.gwt.MonApplication/gwt.js"></script>

Il ne manque plus que la définition de l'action dans le fichier struts-config.xml

struts-config.xml
Sélectionnez

        <action path="/gwt/com.developpez.gwt.MonApplication/listContactGwt"
            type="com.developpez.gwt.control.contact.ContactAction" 
            scope="request" 
            validate="false" 
            parameter="doList">
            <forward name="SUCCESS" path="listContactGwt" />
        </action>       	

On notera l'url un peu incongrue de notre action, qui est malheureusement du à GWT. GWT va rechercher tout ces fichiers html, js, fichiers de cache etc... à l'url où il est appelé. Ce qui nécessite ce type d'url "alien" dans notre fichier struts.

Mais voilà, nous avons terminé, nous n'avons donc plus ce répertoire public dans nos sources et le widget gwt est désormais intégré à une tiles.

IV. Les outils

IV-1. l'IDE : Eclipse

Il me paraissait intéressant de trouver un plugin eclipse dans le cas d'un développement plus poussé avec GWT. J'apprécie quand les tâches fastidieuses sont faites pour moi alors pourquoi s'en priver. J'en suis arrivé à la maigre liste suivante :

Voici mes prérequis lors de la recherche de plugin eclipse :

  • 1. gratuit puisque j'évaluais le produit pour moi-même.
  • 2. me permettant de designer une page en WYSIWYG.
  • 3. détectant automatiquement les futures erreurs de compilation dans la partie cliente (qui ne peut utiliser toutes les librairies java)
  • 4. pouvant démarrer le mode hosted de GWT
  • 5. capable de gérer les fichiers module
  • 6. capable de gérer les services GWT RPC
  • 7. être capable d'ajouter la nature GWT sur un projet existant

Au final le point 3 n'est couvert par aucun plugin. Le point 1 a malheureusement écarté le plugin gwtdesigner, celui-ci n'aurait certainement pas été écarté dans un contexte entreprise mais ici je faisais une évaluation personnelle. Le but de cet article étant d'intégrer GWT à un projet existant, le point 7 a écarté le plugin cypal. En effet on ne peut ajouter la nature "dynamic web project" à un projet déjà existant. Mais il paraissait de toute façon assez pauvre, ne couvrant que les points 1, 5, et 6. En conclusion, la décision a été de se passer de plugin spécifique, une bonne connaissance d'eclipse suffira pour la suite.

V. Conclusion

Au final, ce bref petit plongé m'a permis de mieux comprendre GWT et de se faire une idée sur son utilisation suivant les cas. Il est exact que nous avons réussi à réaliser une intégration de notre widget dans une page jsp mais certains inconvénients ont tendance à me rebuter pour conseiller son utilisation dans un projet déjà existant à base de struts, spring mvc etc..., notamment :

  • des outils associés (plugins maven ou eclipse) encore un peu jeune
  • la difficulté potentielle de reprendre son modèle existant côté client
  • les urls barbare imposées par GWT, je ne tiens pas à avoir une partie de mes pages sur des urls GWT et pas le reste du site
  • l'absence d'outils standard pour l'adaptation des actions struts actuelles en services GWT (en tout cas pour struts1)

De plus je n'ai pas abordé la question des formulaires. L'approche n'est pas la même dans les deux frameworks puisque GWT client va proposer des validations et contrôles côté client codées en GWT tandis que si je veux conserver mon action struts je vais avoir une seconde passe côté serveur par l'intermédiaire d'un commons-validator. Le choix sera difficile à faire si je ne dois en garder qu'un mais le mécanisme de validation de struts est pour l'instant plus standard. Au final, si le besoin se fait sentir de conserver une ergonomie agréable côté client, je conseillerais plutôt un framework Javascript simple, tel que yahoo ui ou même extjs (utilisé par l'extension GWT-ext).

Cependant malgré tous ces petits bémols, dans le cas d'un nouveau projet GWT peut être une alternative intéressante. Gageons que ce framework a de l'avenir et s'améliorera dans le temps.

V-1. Liens utiles

VI. Remerciements

Je tiens à remercier RideKick et Ricky81 pour leurs corrections et leur aide à la publication de cet article.