Category: Java J2EE

Eclipse compiler & m2e

By , 24/01/2012

Récemment, j’ai appris que l’IDE eclipse n’utilisait pas le compilateur de la JDK (javac) pour transformer les fichiers Java en Class. C’est d’ailleurs pour cela qu’il est possible de développer et compiler du code sans avoir installer la JDK sur son ordinateur.

Par défaut, Maven utilise javac pour compiler les sources Java. Or, dans certains cas, le compilateur eclipse (JDT) accepte de compiler des classes là où javac sort en erreur. Par exemple, voici un code qui compile avec le JDT et pas avec javac :

package com.test;

public class GenericMethod {

    private Object value;

    @SuppressWarnings("unchecked")
    public <X> X getValue() {
        return (X) value;
        // le cast n'est obligatoire que pour que javac compile
        // avec le compilateur eclipse, il n'est pas nécessaire
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

Voici la classe pour appeler la méthode :

package com.test;

public class GenericMethodTest {

    public static void main(String[] args) {
        GenericMethod method = new GenericMethod();
        method.setValue(3);

        int[] array = new int[] {1, 2};

        array[0] = method.getValue();
        System.out.println(array[0]);
    }
}

Avec javac, la ligne 11 de la classe GenericMethodTest provoque l’erreur suivante :

type parameters of <X>X cannot be determined;no unique maximal instance exists for type variable X with upper bounds int,java.lang.Object

Il peut donc être essentiel d’utiliser dans le processus de build un autre compilateur que javac. Il est possible de configurer le plugin maven-compiler-plugin afin d’utiliser un compilateur alternatif. Par exemple, pour utiliser le compilateur eclipse c’est très simple :

<project>
  [...]
  <build>
    [...]
    <plugins>
      [...]
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <compilerId>eclipse</compilerId>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.plexus</groupId>
            <artifactId>plexus-compiler-eclipse</artifactId>
          </dependency>
        </dependencies>
      </plugin>
  [...]
</project>

Le build maven fonctionne parfaitement, en revanche l’utilisation du compilateur alternatif a des effets secondaires lorsqu’il est utilisé par le plugin eclipse m2e. La modification des fichiers .classpath et .project ne fonctionne plus correctement et cela provoque :

  • La nature java du projet n’est pas détectée,
  • Les dépendances ne sont plus récupérées,
  • Les répertoires sources (src/main/java, src/main/resources, etc.) ne sont plus référencés

Le problème du nouveau plugin m2e c’est qu’il ne reconnaît les projets maven en tant que projets java que lorsque le compilateur utilisé est javac. Pour configurer un projet eclipse utilisant le compilateur eclipse, il est donc nécessaire d’indiquer à m2e qu’il s’agit bien d’un projet Java :

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.eclipse.m2e</groupId>
      <artifactId>lifecycle-mapping</artifactId>
      <version>1.0.0</version>
      <configuration>
        <lifecycleMappingMetadata>
          <pluginExecutions>
            <pluginExecution>
              <pluginExecutionFilter>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <versionRange>[2.0,)</versionRange>
                <goals>
                  <goal>compile</goal>
                  <goal>testCompile</goal>
                </goals>
              <parameters>
                <compilerId>eclipse</compilerId>
              </parameters>
             </pluginExecutionFilter>
             <action>
               <configurator>
                 <id>org.eclipse.m2e.jdt.javaConfigurator</id>
               </configurator>
             </action>
           </pluginExecution>
          </pluginExecutions>
        </lifecycleMappingMetadata>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

Dès que m2e reconnaît le projet en tant que projet java, la fonction Update project configuration, configurera les répertoires sources et mettra à jour le classpath du projet.

Hibernate, JPA : DTYPE et requêtes JPQL (Java Persistence Query Language)

By , 22/12/2011

Avec JPA, il est possible de modéliser son modèle de données avec une notion d’héritage. Par exemple, ci-dessous, il existe deux entités Pomme et Poire qui héritent de l’entité Fruit :

Exemple d'une hérarchie d'entités JPA

La stratégie par défaut du lien d’héritage consiste à stocker dans une même table l’ensemble des données des objets et d’utiliser une colonne discriminante permettant de définir pour chaque ligne le type de la donnée (un Fruit, une Pomme ou une Poire).

Avec Hibernate, cette colonne s’appelle DTYPE. Par défaut, il n’y a aucune référence à cette colonne dans le code Java. Dans ces conditions, il existe plusieurs solutions pour utiliser cette colonne dans les requêtes JPQL :

  • Implicite : utiliser le type Fruit ou l’un des sous-types Pomme ou Poire pour filtrer sur le type du fruit :
from Fruit where ...

from Pomme where...

from Poire where ...
  • Explicite : ajouter une nouvelle variable d’instance neutre pour mapper la colonne DTYPE de la classe mère :
@Column(insertable = false, updatable = false)
private String dtype;
...
// Exemple d'un requête JPQL utilisant dtype pour ordonner les entités par type :
"from Fruit where ... order by dtype";

Wiser : tester l’envoi d’un email

By , 21/10/2011

Wiser est un petit serveur SMPT facilement intégrable dans une classe de test unitaire (Junit, TestNg, etc.). Il réceptionne les messages et les sauvegardes dans un tableau au lieu de les envoyer sur le réseau. Ces messages sont ensuite disponibles pour vérification.

Avec ce serveur, il est possible de vérifier tous les types de mails même ceux qui contiennent des pièces jointes. Il est développé dans le projet subethasmtp.

Dans un projet maven il est nécessaire d’ajouter les dépendances suivantes :

<dependency>
    <groupId>org.subethamail</groupId>
    <artifactId>subethasmtp-wiser</artifactId>
    <version>1.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.8.2</version>
    <scope>test</scope>
</dependency>

Il faut, ensuite, écrire une méthode qui va envoyer un email et la tester dans une classe JUnit. Je vous propose d’utiliser JavaMail. Remarque : vous pouvez aussi envoyer un email à partir d’un logiciel comme thunderbird s’il est configuré avec Wiser comme serveur SMTP.

 /**
  * Méthode qui envoie un email.
  */
public void sendEmail(String serveur, int port) throws AddressException, MessagingException {
   Properties prop = System.getProperties();
   prop.put("mail.smtp.host", serveur);
   prop.put("mail.smtp.port", String.valueOf(port));

   Session session = Session.getDefaultInstance(prop,null);
   InternetAddress[] internetAddresses =
   new InternetAddress[] {new InternetAddress("toi@xxx.com")};

   Message message = new MimeMessage(session);
   message.setFrom(new InternetAddress("moi@xxx.com"));
   message.setRecipients(Message.RecipientType.TO, internetAddresses);
   message.setSubject("Test");
   message.setText("test mail");

   Transport.send(message);
 }

 /**
 * Démarrage de Wiser avant l'éxécution du test.
 */
 @Before
 public void setUp() {
   wiser = new Wiser();
   wiser.setPort(2500); // port par défaut : 25
   wiser.start();
 }

 @After
 public void tearDown() {
   wiser.stop();
 }

 @Test
 public void testSendEmail() {
   try {
     sendEmail("localhost", 2500);   // Appel de la méthode qui envoie l'email.
     assertEquals(1, wiser.getMessages().size());

     for (WiserMessage message : wiser.getMessages()) {
       String envelopeSender = message.getEnvelopeSender();
       String envelopeReceiver = message.getEnvelopeReceiver();

       MimeMessage mess = message.getMimeMessage();

       // Il ne reste plus qu'a vérifier le contenu de l'email.
     }

   } catch (Exception e) {
     fail();
   }
 }

Vous pouvez retrouver mon projet exemple ici.

Dans la distribution de subethasmtp, vous retrouverez des exemples pour tester des emails plus compliqués avec notament des pièces jointes.

mod_jk : Apache & tomcat

By , 19/08/2011

A chaque fois que j’essaie de configurer un tomcat derrière un serveur apache, je tombe toujours sur les mêmes problèmes, je perds toujours du temps à retrouver les mêmes solutions dans les documentations. C’est pour cela que j’écris cet article que j’espère le plus simple et pratique que possible.

Après avoir installé les deux serveurs, il faut télécharger le module apache mod_jk (la version 1.2) sur ce site : http://tomcat.apache.org/download-connectors.cgi.

Déposer le fichier mod_jk.so (qui se trouve dans l’archive téléchargée) dans le répertoire $APACHE_HOME/modules/. Pour configurer ce connecteur, deux fichiers sont nécessaires

  • workers.properties : permet au connecteur de se connecter au serveur tomcat.
  • httpd.conf : permet au connecteur de rediriger les requêtes HTTP vers le serveur tomcat.

Voici un fichier worker.properties exemple :

  # La liste des workers
  worker.list= worker1, jkstatus

  # Configuration du worker1 : connexion à tomcat
  worker.worker1.type=ajp13
  worker.worker1.host=localhost
  worker.worker1.port=8009

  # Configuration de jkstatus : agrège des statistiques sur le connecteur
  worker.jkstatus.type=status

Voici quelques lignes à ajouter au fichier httpd.conf pour configurer mod_jk :

  LoadModule    jk_module  modules/mod_jk.so
  JkWorkersFile workers.properties
  JkLogFile     /usr/local/apache/logs/mod_jk.log
  JkLogLevel    info
  JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "

Une fois cette communication faite il faut rendre les applications tomcat visibles depuis Apache. Cette étape peut être aussi bien réalisée à partir du fichier httpd.conf que worker.properties. Par exemple, voici la directive à ajouter au fichier de configuration du serveur Apache :

   JkMount /my-webapplication/* worker1
   JkMount /my-webapplication worker1
   JkMount /jkstatus* jkstatus

Il existe beaucoup d’autre façons de configurer ces deux serveurs. Pour plus d’information voici quelques sites qui décrivent dans les détails les différentes configurations possibles :

Appliation J2EE6 avec JBoss 7 et Maven

By , 03/08/2011

JBoss propose un certain nombre d’archetypes dont un qui génère  une application J2EE6 pour JBoss 7 : org.jboss.spec.archetypes:jboss-javaee6-webapp. Or, par défaut, maven ne connait pas cet archetype. Voici comment réussir à l’utiliser dans Eclipse.

Avant toute chose, il faut vérifier que maven est configuré pour aller chercher des librairies sur le repository JBoss : le fichier settings.xml (~/.m2/settings.xml ou $MAVEN_HOME/conf/settings.xml) doit contenir le code XML suivant :

<settings>
 ...
  <profiles>
    ...
    <profile>
      <id>jboss-public-repository</id>
      <repositories>
        <repository>
          <id>jboss-public-repository-group</id>
          <name>JBoss Public Maven Repository Group</name>
          <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
          <layout>default</layout>
          <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>jboss-public-repository-group</id>
          <name>JBoss Public Maven Repository Group</name>
          <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
          <layout>default</layout>
          <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
          </snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>

  </profiles>

  <activeProfiles>
    <activeProfile>jboss-public-repository</activeProfile>
  </activeProfiles>
  ...
</settings>

Ensuite, pour créer le projet dans Eclipse, il faut commencer par installer le catalogue JBoss : Windows -> Preferences… puis dans la fenêtre Maven -> Achetypes cliquer sur le bouton  Add Remote Catalog… Saisir https://repository.jboss.org/nexus/content/groups/public/archetype-catalog.xml dans l’intitulé Catalogue File et JBoss dans Description. Après l’opération, il ne reste plus qu’à accéder au wizard de création d’un projet Maven (File -> New -> Project… puis Maven project) et de sélectionner l’archetype jboss-javaee6-webapp.

Le squelette de l’application est celui-ci :

/home/…/jboss-webapp-j2ee6
|– pom.xml
|– readme.html
|– readme.md
`– src
|– main
|   |– java
|   |   `– test
|   |       `– jboss_webapp_j2ee6
|   |           |– controller
|   |           |   `– MemberRegistration.java
|   |           |– data
|   |           |   `– MemberListProducer.java
|   |           |– model
|   |           |   `– Member.java
|   |           |– rest
|   |           |   |– JaxRsActivator.java
|   |           |   `– MemberResourceRESTService.java
|   |           `– util
|   |               `– Resources.java
|   |– resources
|   |   |– import.sql
|   |   `– META-INF
|   |       `– persistence.xml
|   `– webapp
|       |– index.html
|       |– index.xhtml
|       |– resources
|       |   |– css
|       |   |   `– screen.css
|       |   `– gfx
|       |       |– banner.png
|       |       `– logo.png
|       `– WEB-INF
|           |– beans.xml
|           |– faces-config.xml
|           `– templates
|               `– default.xhtml
`– test
|– java
|   `– test
|       `– jboss_webapp_j2ee6
|           `– test
|               `– MemberRegistrationTest.java
`– resources
`– arquillian.xml

Ce qui équivaut à une application de départ avec un exemple JSF2 s’appuyant sur une base de données embarquée avec quelques classes constituant un modèle MVC, un exemple de service web REST, et un exemple de test unitaire avec Arquillian. A noter aussi, l’utilisation d’un EJB3 archivé dans un WAR et non un EAR comme il aurait fallu le faire avec J2EE5.

Pour déployer l’application il faut la packager et utiliser le plugin JBoss de déploiement déjà configuré par l’archetype (maven package jboss-as:deploy). Attention, pour que tout se déroule correctement, il faudra avoir démarré votre serveur JBoss7 au préalable.

Eclispe IDE : MoreUnit

By , 29/06/2011

Il y a quelques mois, on m’a fait découvrir le plugin eclipse MoreUnit. Comme je le trouve très pratique, je profite de mon blog pour en parler. Il permet de gagner du temps pour le développement et l’exécution des tests unitaires. Il facilite aussi l’association entre les classes destinées à l’exécution et celles qui vont les tester. Ce plugin est disponible sur le marketplace.

Si l’option Decorate Classes with Test Case est activée (Window > Preferences… > General > Appearance > Label Decorations), les classes qui sont testées sont représentées avec un icon différent :

La même marque verte apparaît au niveau de la marge de l’éditeur Java au début des méthodes testées :

A partir d’une classe, un menu contextuel permet les actions suivantes :

  • Ouvrir la classe de test associée
  • Générer la classe de test si celle-ci n’existe pas
  • Exécuter la classe de test en entier
  • Exécuter les méthodes de test associées à une sélection de méthodes de la classe d’origine

Le plugin sait aussi prendre en compte les refactorings de la classe testée :

  • Si la classe testée est renommée, la classe de test est renommé elle aussi
  • Idem, si la méthode testée est renommée, les méthodes de test le sont aussi
  • Si la classe testée change de package, la classe de test change aussi de package

Enfin, la dernière fonctionnalité que je trouve très utile dans certains contextes, MoreUnit peut générer les mocks des objets utilisés dans la classe testée. Il est compatible avec Mockito et EasyMock. Il ne reste plus qu’a les paraméter.

JSF : générer un fichier à partir d’un managed bean

By , 18/05/2011

Dans cet exemple, je vais générer un fichier Excel à partir d’un managed bean.

L’idée de base est de récupérer le context de la servlet pour créer une réponse et rediriger l’utilisateur vers cette dernière. Voici à quoi ressemble mon bean :

@Model
public class MonBean {

	public String execute() throws IOException {
		// Création du fichier excel :
		String fileName = "azerty.xls";
		HSSFWorkbook wb = new HSSFWorkbook();
		HSSFSheet sheet = wb.createSheet(fileName);
		HSSFHeader header = sheet.getHeader();
		header.setCenter(fileName);

		// Récupération de la réponse HTTP
		ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
		HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
		response.setContentType("application/vnd.ms-excel");
		response.setHeader("Content-disposition", "attachment; filename="+ fileName);

		// Ecriture de la réponse
		ServletOutputStream out = response.getOutputStream();
		wb.write(out);
		out.close();

		FacesContext.getCurrentInstance().responseComplete();

		return null;
	}
}

Ensuite, il suffit de demander la méthode execute() du bean.

<h:form>
        <h:commandButton action="#{monBean.execute}" value="download" />
</h:form>

Vous pouvez trouver le code source de cet exemple ici. Pour exécuter l’exemple il faut lancer la commande mvn compile tomcat:run et se diriger vers l’adresse http://localhost:9090/home.jsf

tomcat-maven-plugin : debug

By , 29/04/2011

Le plugin tomcat de Maven est très pratique lors du développement d’une application web. Voici comment le configurer :

 <!-- Embedded Tomcat (package tomcat:run) -->
 <!-- Standalone Tomcat (package tomcat:deploy) -->
 <plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>tomcat-maven-plugin</artifactId>
   <configuration>
     <path>/${project.build.finalName}</path>
     <!-- Embedded port -->
     <port>9090</port>
   </configuration>
 </plugin>

Le plugin démarre le serveur dans la sa propre JVM. Pour pouvoir débuger votre application, il faut donc uniquement démarrer Maven avec l’option de debug comme cela :

mvn tomcat:run -DXdebug -DXnoagent -Djava.compiler=NONE -DXrunjdwp:transport=dt_socket,address=3998,suspend=n,server=y

Ou bien, si vous utilisez eclipse avec le plugin m2eclipse : cliquer avec le bouton droit de votre souris sur le projet : Debug As -> Maven build … .

Tests unitaires et multithreading

By , 17/04/2011

Comment savoir si une méthode développée fonctionne bien dans un contexte multithread ? Pour répondre à cette question je vous propose cet exemple qui se base sur une classe simple utilisant une instance  statique de SimpleDateFormat qui n’est pas un objet threadsafe. Cette classe possède deux méthodes l’une n’est pas threadsafe l’autre l’est grâce à un bloc “synchronisé”.

public class DateUtil {
 /** Static pour bénéficier d'une instance partagée par l'ensemble des threads.*/
 private static DateFormat df = new SimpleDateFormat("dd/MM/yyyy");

 public static Date parse(String date) throws ParseException {
   Date result = null;
   result = df.parse(date);
   return result;
 }

 public static Date parseThreadSafe(String date) throws ParseException {
   Date result = null;
   // Ce bloc garantit que la méthode fonctionne en multithread.
   synchronized (df) {
     result = df.parse(date);
   }
   return result;
 }
}

La classe de test utilise la librairie TestNg et non JUnit car cette dernière ne propose pas de support du multithread. De son coté, TestNg permet de paramétrer une méthode de test avec, entre autre, deux paramètres :

  • invocationCount : le nombre d’invocation de la méthode (valeur par défaut : 1)
  • threadPoolSize : la taille du pool de threads qui vont exécuter la méthode (valeur par défaut : 0)
public class DateUtilTest {

 @Test(threadPoolSize=20, invocationCount=100)
 public void testParse() {
   try {
     for (int i = 10; i < 20; i++) {
       assertEquals(new SimpleDateFormat("dd/MM/yyyy").parse("01/01/20" + i),
         DateUtil.parse("01/01/20" + i));    
     }
   } catch (ParseException e) {
     fail("TEST KO : " + e.getMessage());
   }
 }

 @Test(threadPoolSize=20, invocationCount=100)
 public void testParseThreadSafe() {
   try {
     for (int i = 10; i < 20; i++) {
       assertEquals(new SimpleDateFormat("dd/MM/yyyy").parse("01/01/20" + i),
         DateUtil.parseThreadSafe("01/01/20" + i));    
     }
   } catch (ParseException e) {
     fail("TEST KO : " + e.getMessage());
   }
 }

}

Les boucles for servent à augmenter la probabilité d’appel simultanés des méthodes testées. Le résultat de l’exécution est assez simple a analyser car les seules méthodes qui tombent en erreur sont celles qui exécutent la méthode sans le bloc synchronisé.

Vous pouvez retrouver le projet maven à l’adresse suivante : https://subversion.assembla.com/svn/everythingiswrong/multithread-tests

Java : Problème de certificat SSL

By , 04/04/2011

Lorsqu’un traitement Java fait appel à un site internet sécurisé avec le protocole HTTPS,  la JVM vérifie si le certificat SSL est validé par un organisme tiers. Pour cela, il existe dans la JRE une librairie avec des certificats de confiance : $JAVA_HOME/jre/lib/security/cacerts. Voici une erreur possible lors d’un échange entre un client et un serveur SSL :

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Lorsque cette erreur arrive, cela veut dire que le certificat délivré par le serveur n’est pas vérifié par une autorité tierce reconnue par la JVM. Pour résoudre ce problème, il y a deux solutions :

  • Annuler la vérification du certificat
  • Ajouter le certificat à la liste des certificats reconnus de la JVM

Annuler la vérification des certificats

Je vous propose d’annuler la vérification de façon programmatique. Pour cela, il faut créer un “TrustManager” qui ne va rien vérifier puis l’utiliser dans le contexte de session SSL.

// Créer un "trust manager" qui ne valide pas les certificats
TrustManager[] trustAllCerts = new TrustManager[]{
    new X509TrustManager() {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
        public void checkClientTrusted(
            java.security.cert.X509Certificate[] certs, String authType) {
        }
        public void checkServerTrusted(
            java.security.cert.X509Certificate[] certs, String authType) {
        }
    }
};

// Utiliser le manager qui ne valide pas les certificat
try {
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
}

// Accéder à la page en HTTPS
try {
    URL url = new URL("https://hostname/index.html");
} catch (MalformedURLException e) {
}

Je rappelle qu’en supprimant l’étape de vérification, le client devient sensible aux attaques du type man-in-the-middle. Je déconseille donc cette solution dans le cas général.

Ajouter le nouveau certificat à la JVM

Pour ajouter le certificat dans la JVM, il faut l’exporter depuis votre navigateur. Par exemple, avec Firefox, il faut se rendre dans la fenêtre des options puis  dans le menu Avancé -> Chiffrement cliquer sur le bouton Afficher les certificats. Dans la fenêtre suivante, il est possible d’exporter un certificat.

Enfin pour importer le certificat dans la JVM il faut exécuter cette commande :

keytool -keystore $JAVA_HOME/jre/lib/security/cacerts -file certificat.crt -alias certificat

Le mot de passe par défaut est “changeit”.

Astuce

Pour débugger une conversation SSL, il faut ajouter l’option JVM suivante : -Djavax.net.debug=all

OfficeFolders theme by Themocracy