Dreamisle.net Blog

Java, Rails, Security and so many ...

Seam Et L’internationalisation

| Comments

Bonjour à tous, Tout d’abord désolé pour le temps d’absence j’ai été débordé de travail ces derniers temps. Aujourd’hui un petit article assez court mais qui me semble important car j’ai été assez impressionné par le support de l’internationalisation dans Seam.

Dans le cadre d’un projet développé à l’école et probablement destiné au  logiciel libre d’ici un ou deux mois (je ferais des publications pour vous le faire savoir car je pense qu’il y aura du code la dedans qui pourra vous intéresser), j’ai eu à gérer plusieurs langues. Ce projet consiste en un cartographieur du web. Grosso modo des crawler parcours le web selon une configuration voulu, et en fonction de leurs retours un graph est généré. Vous pouvez alors naviguer dans ce graph au sein d’un applet flash qui vous permet d’ouvrir les pages via un proxy qui contrôle votre navigation dans les pages crawlées.

Souvent mettre un site ou une webapp en plusieurs langue est assez problématique. Le modèle le plus utilisé est celui des fichier de messages_[langue].properties et c’est aussi celui par Seam.

Quand j’ai su que mon projet devrait être internationalisé je me suis dit : galère ça va prendre un temps fou ça.

Et bien je me trompais, voici pourquoi.

Première étape, le selecteur de langue

La première chose est de stocker dans la session utilisateur la langue choisie par l’utilisateur. Et la Seam a tout prévu pour vous : le localeSelector est là pour ça. Donc pour mettre un selecteur de langue c’est très simple :

    <h:form>
        <h:selectOneMenu value="#{localeSelector.language}">
          <f:selectItem itemLabel="#{messages['org.sweetmap.english']}" itemValue="en"/>
          <f:selectItem itemLabel="#{messages['org.sweetmap.french']}" itemValue="fr"/>
        </h:selectOneMenu>
        <h:commandButton action="#{localeSelector.select}" value="#{messages['org.sweetmap.selectlanguage']}"/>
    </h:form>

Et là on vois déjà apparaître quelques extraits de code qui amènent à s’interroger, par exemple :

          <f:selectItem itemLabel="#{messages['org.sweetmap.english']}" itemValue="en"/>

En fait Seam intègre un composant messages interrogeable tel que via une EL, qui est lié à la locale choisie dans le locale selector et va chercher le fichier de messages.properties en fonction de cette locale. Vous l’avez peut être vu quand vous générez un projet avec Seam-Gen celui-ci vous intègre des fichiers de properties tels que messages_fr.properties, messages_en.properties etc … Par défaut la locale choisie est celle configurée dans votre fichiers faces-config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">

   <application>
      <locale-config>
         <default-locale>en</default-locale>
         <supported-locale>en</supported-locale>
         <supported-locale>fr</supported-locale>
      </locale-config>
      <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
   </application>

</faces-config>

Et donc pour mon messages['org.sweetmap.english'] j’ai dans mes fichiers de properties une ligne correspondante telle que : org.sweetmap.english=Anglais.

Voilà, ainsi quelque soit l’endroit ou j’ai du texte à ajouter, au lieu d’écrire ce texte j’écris des EL du style "#{messages['org.sweetmap.menu.themap']}" et je remplis mes fichiers de properties, et l’internationalisation est gérée toute seule. Simple non?

Enfin si côté serveur on a besoin de tester la langue de l’utilisateur, par exemple pour faire une requête ejbql en fonction de la langue, on peut récupérer la locale choisie via cet appel :

LocaleSelector.instance().getLanguage();

Celui-ci retournera une String avec par exemple en ou fr selon la langue.

Dans mon cas pour mes Entités JPA, j’ai choisi de stocker cette String en base afin de pouvoir requêter la base par langue. Autre info, les locales gérées par Seam sont du type java.util.Locale afin de rester assez générique.

Voilà j’espère que cet article vous a été utile !

Application Seam Derrière Un Proxy

| Comments

Vous avez peut être déjà eu le problème : vous êtes en entreprise ou dans n’importe quel lieu avec une connection partagée et vous êtes donc derrière un proxy. La majorité du temps aucun problème pour votre application, mais parfois le proxy utilisé un cache des pages. Dans ce cas un problème peut vite arriver : une incohérence du Session ID. En effet si le proxy a caché le cookies et/ou la page, il peut y avoir des perte de session aléatoire si le jsessionid linké n’est pas le bon.

En fait il existe une technique de “sioux” pour parer à ça, jouer sur les en têtes HTTP pour forcer la réactualisation de la page et empêcher la mise en cache. Il faut jouer sur deux en têtes : Expires, et Cache-Control.

Voici une ServletFilter que vous pouvez rajouter dans votre application pour qu’elle ajoute automatiquement les en têtes qui vont bien à toutes vos pages :

package org.sweetmap.services.utils;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;
import org.jboss.seam.web.AbstractFilter;

/**
 * Add the wanted filter to the http header.
 * @author leakim
 *
 */
@Startup
@Scope(ScopeType.APPLICATION)
@Name("responseHeaderFilter")
@BypassInterceptors
@Filter(within = "org.jboss.seam.web.ajax4jsfFilter")
public class ResponseHeaderFilter extends AbstractFilter {

  /**
   * Add the http header to bypass the proxy cache.
   * @param req the request.
   * @param res the response.
   * @param filt the filter.
   * @throws IOException ex.
   * @throws ServletException ex2.
   */
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain filt) throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) res;
    response.addHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
    response.addHeader("Cache-Control", "private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
    filt.doFilter(req, response);
  }

}

Voilà, ainsi normalement cela devrait régler vos problèmes de perte de session derrière un proxy ! Grâce à l’annotation @Filter il n’est pas necessaire de l’ajouter au fichier web.xml, Seam s’en occupe pour vous.

Seam 2.2.0.GA

| Comments

Bonjour à tous,

Seam 2.2.0.GA étant sorti, j’ai mis à jour les poms de mes projets, mais ayant eu quelques problèmes de versionning de librairies, je vous fait partager la compatibilité des librairies principales entre elles.

Tout d’abord pour Seam :

      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam</artifactId>
        <!--  type>ejb</type -->
        <version>2.2.0.GA</version>
        <exclusions>
          <exclusion>
            <groupId>jboss</groupId>
            <artifactId>javassist</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-debug</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-ui</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-pdf</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-mail</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-remoting</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-ioc</artifactId>
        <version>2.2.0.GA</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.el</groupId>
        <artifactId>jboss-el</artifactId>
        <version>1.0_02.CR4</version>
      </dependency>

Ensuite, vous devriez avoir des problèmes avec Drools si vous êtes encore sur la version 4, car Seam 2.2 est passé à Drools 5, voici donc la version à appeller, ainsi que le mvel à joindre :

 <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
        <version>5.0.1</version>
      </dependency>
    <dependency>
        <groupId>org.mvel</groupId>
        <artifactId>mvel2</artifactId>
        <version>2.0.10-SNAPSHOT</version>
      </dependency>

Si vous avez des problèmes pour recuperer mvel, ajouter ce dépot dans vos repositories :

<repository><!-- used by mvel to publish snapshots -->
            <id>codehaus-snapshot</id>
            <url>http://snapshots.repository.codehaus.org</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>

            <releases>
               <enabled>false</enabled>
            </releases>
        </repository>

Et enfin Hibernate et Lucene ( pour Hibernate Search ) :

   <!-- Hibernate validator -->
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-annotations</artifactId>
        <version>3.4.0.GA</version>
        <scope>provided</scope>
      </dependency>

     <dependency>
       <groupId>org.hibernate</groupId>
       <artifactId>hibernate-search</artifactId>
       <version>3.1.1.GA</version>
     </dependency>

      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>3.1.0.GA</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>3.4.0.GA</version>
        <exclusions>
          <exclusion>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
<!-- Hibernate search -->
     <dependency>
       <groupId>org.hibernate</groupId>
       <artifactId>hibernate-commons-annotations</artifactId>
       <version>3.1.0.GA</version>
     </dependency>
     <dependency>
       <groupId>org.hibernate</groupId>
       <artifactId>hibernate-search</artifactId>
       <version>3.1.1.GA</version>
     </dependency>
     <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-core</artifactId>
       <version>2.4.1</version>
     </dependency>
     <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-analyzers</artifactId>
       <version>2.4.1</version>
     </dependency>
     <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-highlighter</artifactId>
       <version>2.4.1</version>
     </dependency>
     <dependency>
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-snowball</artifactId>
       <version>2.4.1</version>
     </dependency>

Et voilà, en espérant vous avoir aidé !

Seam Mail Et Jboss 5.1

| Comments

Un petit post pour vous signaler un bug que j’ai eu. Avec Seam 2.1.1 et jboss 5.1.0.GA il m’était impossible d’envoyer un mail. En effet j’avais une erreur lorsque je tentais d’injecter le renderer. Pour information j’ai résolu ce bug en passant à Seam 2.1.2. Voilà ! en espérant que cela vous sera utile.

Présentation De Seam

| Comments

Suite à la présentation de Seam que j’ai faite avec Patrice Pichereau au Tours JUG le 09 Juin dernier, j’ai préparé un PDF de la présentation.

Je me suis dis que ça pourrait vous intéresser, voici donc

Le pdf :

SeamConf

La keynote (powerpoint) au format pdf :

SeamConfDiapo

seam_icon_large

Nouveau Design

| Comments

Comme vous pouvez le constater j’ai fait évolué le design du site, n’hésitez pas à me signaler tout problème, ou vos avis :)

Gestion D’un Processus Asynchrone Avec Quartz

| Comments

Il est souvent nécessaire dans une applications d’avoir des Batchs qui tournent pour effectuer des traitements diverses et variés. Lorsque ces process ne nécessitent pas interaction avec un utilisateur ceux-ci peuvent être différés pour des raisons d’optimisation entre autre. Depuis Java 1.4 il est possible de planifier des tâches de manière simple, cependant la librairie Quartz permet de gérer ces process différés de manière plus fine, avec un déclenchement similaire à l’utilisation d’un cron sous unix. Ce billet relativement court présente une manière très simple d’utiliser ce mécanisme avec Seam.

Seam intègre un mécanisme basé sur Quartz, et utilisant l’annotation @Asynchronous pour faciliter ces traitements. Par exemple si j’ai besoin d’un batch qui tourne toutes les 5 minutes voici comment je pourrais procéder.

Dans un composant on commence par écrire une méthode annotée @Asynchronous Ici FileBrowser est simplement un composant conservant l’état des données. FileBrowserService contient notre méthode asynchrone.

public class FileBrowserService {
  //[...]
  @Asynchronous
  public void synchronise(@Expiration Date when, @IntervalCron String cron) {
    logger.debug("Automatic Sync Processing");
    if (FileBrowser.getInstance().isSynchronizationInProcess()) {
      return;
    }
    try {
      FileBrowser.getInstance().setSynchronizationInProcess(true);
      FileBrowser.getInstance().setFileList(this.browseDirectory(PATH));
      FileBrowser.getInstance().setFileOutputList(this.browseDirectory(OUTPUT));
    } finally {
      FileBrowser.getInstance().setSynchronizationInProcess(false);
    }
  }
  //[...]
}

On remarque trois chose : - Il suffit d’annoter la méthode @Asynchronous pour que celle-ci le devienne. - @Expiration Date when, définit la date de départ du process. - @IntervalCron String cron, définit l’interval du cron.

Et pour le lancer on peut écrire un composant (FileBrowserInitializer dans mon cas), qui appellera la méthode lors de sa création.

fileBrowserService.synchronise(new Date(), "0 0/5 * * * ?");

Pour ce qui est du paramétrage du deuxième argument je vous renvoi à l’utilisation classique des Crons : Crontab

Et voilà, c’est très simple, mais très utile !

coffee_cup