GWT Tips – 1/2 – Compiling, Debugging et Logging

logo-185x175GWT [1] est un framework (ou plus précisément un Toolkit regroupant plusieurs outils) très éloigné de ceux que nous utilisons habituellement. Il est donc nécessaire de s’adapter à ses particularités en développant une manière de penser propre à GWT. Et il en va de même concernant les outils à notre disposition.

Cet article se concentre sur différents outils ou concepts indispensables à tout projet GWT.

L’ensemble des sources sont disponibles via GitHub, chaque partie ayant son propre propre tag contenant le code des parties précédentes.

gwt-maven-plugin

[Code Source (tar.gz)] [Code Source (zip)]

Il y a un peu plus de 4 ans, lorsque j’ai utilisé GWT pour la première fois, et malgré toutes les bonnes intentions du monde – en voulant utiliser Maven par exemple – tout était compliqué : la compilation, le packaging, le debugging, etc. Heureusement aujourd’hui, le plugin GWT [2] pour Maven est arrivé à maturité et tout est beaucoup plus simple, comme nous allons le voir.

Commençons tout d’abord par les dépendances dont nous avons besoin :

<!-- Extrait du pom.xml -->
<dependencies>
  <dependency>
    <groupId>com.google.gwt</groupId>
    <artifactId>gwt-servlet</artifactId>
    <version>2.5</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>com.google.gwt</groupId>
    <artifactId>gwt-user</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
  </dependency>
<dependencies>

gwt-user contient le code client de GWT, qui sera transformé en javascript après compilation. Par conséquent, il nous est utile uniquement pendant la phase de développement.

gwt-servlet contient le code serveur, dans le cas où vous souhaitez utiliser le framework RPC de GWT.

A présent, nous devons rajouter le plugin :

<!-- Extrait du pom.xml -->
<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>gwt-maven-plugin</artifactId>
      <version>2.5.0</version>
    </plugin>
  </plugins>
</build>

Pour compiler rien de plus simple :

mvn clean install gwt:compile

Et pour lancer l’application (où gwttips.html est le fichier .html du module GWT) :

mvn gwt:run -DrunTarget=gwttips.html

Deux simplifications peuvent être effectuées.

La première consiste à rajouter le goal gwt:compile dans le cycle d’exécution de Maven et la seconde à ajouter le paramètre runTarget dans la partie configuration du plugin :

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>gwt-maven-plugin</artifactId>
  <version>2.5.0</version>
  <executions>
    <execution>
      <goals>
        <goal>compile</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <runTarget>gwttips.html</runTarget>
  </configuration>
</plugin>

A présent, la compilation et l’exécution s’effectuent de la manière suivante :

mvn clean install
mvn gwt:run

Vous avez surement constaté un warning pour chacune des commandes.

Le premier provient du plugin maven-war-plugin qui souhaite avoir le chemin au fichier web.xml du projet.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>${maven-war-plugin.version}</version>
  <configuration>
    <webxml>src/main/webapp/WEB-INF/web.xml</webxml>
  </configuration>
</plugin>

Et le second provient du plugin gwt-maven-plugin :

<build>
  <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
</build>

Aucun de ces éléments n’est très important. Néanmoins éliminer tous les warnings le plus tôt possible dans un projet permet d’identifier de futurs problèmes potentiels plus rapidement.

De plus, concernant les plugins Maven, j’ai l’habitude de définir tous les plugins utilisés lors d’un build pour notamment contrôler leur version.

A l’instar d’un projet n’utilisant pas le plugin GWT, le Hot Swapping est possible dans une certaine limite. Il reste nécessaire d’arrêter le serveur sans recompiler le projet (même s’il arrive que le projet doit être recompilé dans certains cas), si vous souhaitez que les modifications sur les fichiers suivants soient prises en compte:  *.gwt.xml, *.css, web.xml, etc.

D’autres goals sont tout autant intéressants :

  • gwt:i18n [3] – génère les interfaces I18N pour les messages  et les constantes
  • gwt:generateAsync [4] – génère les interfaces *Async.java
  • gwt:css [5] – génère les interfaces pour des fichiers css
  • gwt:test [6] – lance les tests unitaires GWT

Il est possible de rajouter ces goals dans le cycle de vie de Maven en les mettant dans le goal gwt:compile ou de les lancer manuellement. Ceci a l’avantage de vous laisser la main sur toutes les classes de votre projet et d’avoir la possibilité de supprimer le code inutile généré.

Pour plus de détails sur ces goals, je vous invite à consulter la documentation officielle du plugin.

Note sur le code : Pour compiler et exécuter l’application :

$ mvn clean install
$ mvn gwt:run

Débugger

Pouvoir utiliser un debugger est indispensable sur tout projet. GWT n’échappant pas à la règle, nous allons voir comment utiliser le debugger avec Eclipse.

Nous allons commencer par créer une commande permettant de lancer le plugin GWT en mode debug.

Eclipse 1

Eclipse 2

Eclipse 3

Name : Nom de la commande

Location : Chemin d’accès au script Maven mvn.bat (sous Windows) ou mvn (sous Unix).

Working Directory : Chemin d’accès au projet

Arguments : Goal du plugin GWT que nous utiliserons (gwt:debug)

Pour finir cliquez sur Apply -> Close/Run ou Run.

Nous devons ensuite créer une commande de lier le debugger d’Eclipse au plugin GWT.

Eclipse 4

Eclipse 5

Eclipse 6

Name : Nom de la commande (doit être unique)

Project : Projet à débugger

Allow termination of remote VM : Permet d’arrêter la Machine Virtuelle du debugger d’Eclipse en même temps que celle du plugin. Ceci évite d’avoir à “killer” des processus à la main.

Pour finir cliquez sur Apply -> Close/Debug ou Debug.

Il est important de noter que les deux commandes doivent être lancées dans un ordre précis. Tout d’abord, celle définie dans “External Tools”, puis après que la ligne suivante soit apparue dans la console :

[INFO] Listening for transport dt_socket at address: 8000

la commande définie dans “Debug”.

Si vous avez cliquez sur Apply, sans Run et Debug, il est nécessaire de retourner dans “External Tools Configurations…”, de sélectionner la commande précédemment créée, puis de cliquer sur Run. Enfin, dans “Debug Configurations”, sélectionnez la commande précédemment créée, puis cliquez sur Debug.

A noter que, pour les exécutions suivantes, vous trouverez les commandes directement dans les Favoris de “External Tools” et “Debug”.

Eclipse 7

Pour arrêter le mode Debug, en perspective JEE, il suffit de cliquer sur le bouton “Stop” de la console, et en perspective Debug, sur celui présent dans la barre d’outil.

Remote Logging

[Code Source (tar.gz)] [Code Source (zip)]

Logger des informations depuis la partie « server » d’un projet GWT se fait de la même manière que pour tout autre Framework, en utilisant votre librairie de logging favorite. En revanche, pour la partie « client », les choses se corsent. Toute la partie cliente étant transformée en JavaScript, utiliser une librairie standard telle que Log4j ou Logback n’est pas possible. Heureusement, Google a tout prévu [7] et propose différentes solutions de logging, dont la majorité est – selon moi – complètement inutile, même pendant la phase de développement. Nous souhaitons avoir des logs dans un shell, dans une console d’un IDE, ou pour une application déployée en production, dans un fichier.

Il est donc nécessaire de faire des appels ajax consistant à envoyer des messages au serveur, qui seront ensuite traités pour être « journalisés » dans une console ou un fichier.

Le framework de logging de GWT émule java.util.logging, l’API de logging de la JDK. Par conséquent, dans la partie cliente, lorsque vous souhaitez récupérer un Logger, il est nécessaire d’utiliser la bonne classe :

import java.util.logging.Logger;

public class MyClass {
  final static Logger LOGGER = Logger.getLogger(MyClass.class.getName());
}

Pour configurer le système de logging, il faut rajouter quelques lignes dans le fichier *.gwt.xml :

<!-- Extrait du fichier gwt-tips.gwt.xml -->
<inherits name="com.google.gwt.logging.Logging" />

<set-property name="gwt.logging.enabled" value="TRUE" />
<set-property name="gwt.logging.logLevel" value="FINEST" />

<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
<set-property name="gwt.logging.consoleHandler" value="DISABLED" />
<set-property name="gwt.logging.developmentModeHandler" value="DISABLED" />
<set-property name="gwt.logging.popupHandler" value="DISABLED" />
<set-property name="gwt.logging.systemHandler" value="DISABLED" />
<set-property name="gwt.logging.firebugLogHandler" value="DISABLED" />

Tout d’abord, nous indiquons au compilateur de GWT que nous souhaitons utiliser le framework de logging :

<inherits name="com.google.gwt.logging.Logging" />

Ensuite, nous lui indiquons que nous souhaitons activer le système de logging :

<set-property name="gwt.logging.enabled" value="TRUE" />

« FALSE » désactive le système de logging. Toutes les références aux Loggers ne seront pas compilées en Javascript.

Nous définissons aussi un  seuil à partir duquel le logger sera actif.

<set-property name="gwt.logging.logLevel" value="FINEST" />

Les niveaux suivants sont possibles (du moins important au plus important) :

  • FINEST
  • FINER
  • FINE
  • CONFIG
  • INFO
  • WARNING
  • SEVERE

En utilisant le niveau FINEST, tout sera loggé.

Il est important de noter que si vous définissez un niveau SEVERE, tous les niveaux inférieurs seront ignorés lors de la phase de compilation en Javascript.

Et pour finir, nous indiquons les handlers à activer ou à désactiver. Par défaut, en utilisant le framework de Logging  (<inherits …/>), tous les handlers sont activés. Par conséquent, nous allons ajouter les 5 handlers et activer ceux qui nous intéressent. Ceci est beaucoup plus propre qu’ajouter uniquement les handlers que nous souhaitons désactiver.

Comme je l’ai indiqué dans l’introduction, la majorité sont inutiles puisqu’ils « log » côté client. Si vous souhaitez plus d’informations sur le sujet, je vous invite à consulter la documentation officielle sur le sujet [7].

Nous avons donc activé uniquement le handler qui enverra les messages au serveur :

<set-property name=”gwt.logging.simpleRemoteHandler” value=”ENABLED” />

Pour que le serveur puisse être appelé, il est nécessaire de définir la servlet qui sera en charge de réceptionner les messages, qui sont en réalité des java.util.logging.LogRecord.

<!-- Extrait du fichier web.xml -->
<servlet>
  <servlet-name>remoteLogging</servlet-name>
  <servlet-class>org.isk.gwttips.server.servlet.LoggerServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>remoteLogging</servlet-name>
  <url-pattern>/gwttips/remote_logging</url-pattern>
</servlet-mapping>

L’objet LogRecord contient plusieurs informations, dont celles qui nous seront utiles sont :

  • Le nom du logger
  • Un message
  • Le niveau du log
  • Et potentiellement une exception
/*
* org.isk.gwttips.server.servlet.LoggerServlet
*/
public class LoggerServlet extends RemoteServiceServlet implements RemoteLoggingService {
  @Override
  public String logOnServer(LogRecord record) {
    // Transférer les logs à une API de logging
    return null;
  }
}

Pour l’exemple, j’ai utilisé Log4j, mais évidemment ce n’est pas une obligation, et rien ne vous empêche de réutiliser l’API de logging standard.

Une possible implémentation de la méthode logOnServer() :

/*
* org.isk.gwttips.server.servlet.LoggerServlet
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public String logOnServer(LogRecord record) {

  final Level level = record.getLevel();
  final String message = record.getMessage();
  final Throwable thrown = record.getThrown();

  final Logger logger = LoggerFactory.getLogger(record.getLoggerName());

  if (Level.INFO.intValue() == level.intValue()) {
    logger.info(message, thrown);
  } else if (Level.WARNING.intValue() == level.intValue()
             || Level.CONFIG.intValue() == level.intValue()) {
    logger.warn(message, thrown);
  } else if (Level.SEVERE.intValue() == level.intValue()) {
    logger.error(message, thrown);
  } else {
    // FINE, FINER and FINEST
    logger.debug(message, thrown);
  }

  return null;
}

Il est important de garder à l’esprit que nous perdons toutes les informations sur le point d’origine du log, tels que le nom de la méthode dans laquelle la méthode de logging a été appelée, tout comme le numéro de la ligne :

[2012-12-15 18:04:29,062] - [btpool0-0] DEBUG | org.isk.gwttips.client.GwtTips#logOnServer() | LoggerServlet.java:37 - Module initializing...

L’explication est très simple : le code étant compilé en JavaScript puis minifié, il n’y a plus aucun lien entre le code Java d’origine et le code JavaScript généré.

Note sur le code : L’activation (ou désactivation) du framework de logging, le répertoire contenant les fichiers de log, ainsi que les différents niveaux de log, ont été déportés dans le fichier de configuration de Maven. L’exemple de la partie suivante s’appuiera sur cette solution pour l’utilisation de différents profils Maven.

<!-- Extrait du pom.xml -->
<logs.path>${basedir}</logs.path>
<log4j.level>trace</log4j.level>
<gwt.log.enabled>TRUE</gwt.log.enabled>
<gwt.log.level>FINEST</gwt.log.level>

${basedir} étant le répertoire contenant le pom.xml

J’ai aussi encadré les tokens entre des ‘@‘ pour les distinguer des tokens provenant des fichiers properties, généralement définis de la manière suivante ${token}.

Pour compiler et exécuter l’application :

$ mvn clean install
$ mvn gwt:run

Ressources

[1] https://developers.google.com/web-toolkit/

[2] http://mojo.codehaus.org/gwt-maven-plugin/

[3] http://mojo.codehaus.org/gwt-maven-plugin/i18n-mojo.html

[4] http://mojo.codehaus.org/gwt-maven-plugin/generateAsync-mojo.html

[5] http://mojo.codehaus.org/gwt-maven-plugin/css-mojo.html

[6] http://mojo.codehaus.org/gwt-maven-plugin/test-mojo.html

[7] https://developers.google.com/web-toolkit/doc/latest/DevGuideLogging

Nombre de vue : 425

COMMENTAIRES 2 commentaires

  1. Manuel dit :

    Bonjour,

    J’ai été enthousiaste de lire cet article et je me suis mis à essayer…
    J’ai donc fait :
    git clone https://github.com/yohanbeschi/gwt-tips.git
    cd gwt-tips
    mvn clean install

    Malheureusement je suis tombé sur un os :

    [INFO] --- gwt-maven-plugin:2.5.0:compile (default) @ front ---
    [INFO] auto discovered modules [org.isk.gwttips.gwt-tips]
    [INFO] Loading inherited module 'org.isk.gwttips.gwt-tips'
    [INFO] [ERROR] Invalid property value '@gwt.log.enabled@'
    [INFO] [ERROR] Failure while parsing XML
    [INFO] com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
    [INFO] at com.google.gwt.dev.cfg.ModuleDefSchema$PropertyValueAttrCvt.convertToArg(ModuleDefSchema.java:1244)

    Que devrais-je faire pour que cela fonctionne ?

    Merci encore pour cet article et pour les sources qui sont avec !

  2. Yohan Beschi dit :

    Bonjour,

    La branche “master” est identique au tag et à la branche “spring-hibernate”. Il faut donc se référer au point “Spring and Hibernate” du README.

    Les commandes maven sont les suivantes :
    $ mvn clean install sql:execute -P dev
    $ mvn gwt:run -P dev

    Mais je te conseille de faire un checkout de la branche liée à la partie que tu es en train de lire pour éviter la pollution d’éléments introduits dans les parties suivantes. Sachant que pour les 2 premières parties, les branches “basics” et “remote-logging” n’ont pas besoin de profil. Un simple :

    $ mvn clean install

    est suffisant.

    Yohan

AJOUTER UN COMMENTAIRE