mercredi 22 décembre 2010

Créer un service web XML avec JAX-WS et Maven


JAX-WS 2.0 ou la JSR 224 constitue une pile normée qui réduit considérablement la complexité de mise en oeuvre d'un service web.
Il est cependant indispensable de posséder quelques notions de base sur les technologies liées aux services web.

Notions fondamentales préalables


Tout d'abord, il faut dire un mot sur XML, ce "langage extensible de balisage". XML est un moyen indépendant de toute plateforme pour représenter des informations structurées.
Dans le cadre des services web, XML sert de base à la communication entre les services et leurs clients.
SOAP est une grammaire XML dédié à la transmission de messages entre objets distants. Un message SOAP se compose d'une enveloppe (les informations nécessaire à l'acheminement et à son traitement) et d'un modèle de données (le format du message et son contenu).
WSDL est une grammaire XML qui décrit les services distribués sur le réseau comme un ensemble de points qui opèrent à partir de messages.
Je vous invite à consulter mon petit lexique des services webs.

Vérifions ...


La bonne nouvelle c'est que l'api JAX-WS fait partie de l'API standard  de votre JDK à partir de la version 6.
Quant aux implémentations plusieurs sont disponibles : Metro (SUN/Oracle), CXF (Apache) , Axis 2 (Apache).
Metro est intégré d'office dans le JDK 6. C'est l'implémentation de référence de cette API.
Comme toutes les implémentations de référence elle couvre l'intégralité de la JSR 224 et bien plus.
Metro est aussi intéressant pour la collaboration des ingénieurs de SUN et de Microsoft : Metro affiche une compatibilité avec .NET 3.0 et .NET.3.5.
Vous trouverez dans le dossier JAVA_HOME/bin deux utilitaires fournis avec l'implémentation de référence:
  • wsgen : génère les ressources nécessaire au déploiement d'un webservice JAX-WS.
  • wsimport : importe un WSDL et génère les classes nécessaires à la réalisation d'un client.

Un petit test en ligne de commande permet de connaître la version de l'implémentation de référence :
macbook-de-olivier-schmitt:Commands oschmitt$ wsgen -version
JAX-WS RI 2.1.6 in JDK 6
Est-ce ok pour vous ?

Création du projet Yellow Book Service


Nous allons utiliser Maven pour réaliser ce service web.
Je vous conseille d'utilisez un IDE qui supporte Maven correctement comme Netbeans ou IntelliJ.
Un conteneur JEE qui supporte JAX-WS vous permettra également de vous concentrer sur l'essentiel.
Utilisez par exemple Glassfish 3.0.1 dans sa version "web profile" pour déployer le WAR qui contient le service web. Il est tout à fait possible d'utiliser Tomcat mais cela demande une configuration particulière (décrite par ailleurs dans les instructions d'installation de Metro).
Nous utiliserons le POM Corporate fourni depuis ce billet.
Tout d'abord il convient de créer un projet Maven avec un packaging de type war. En effet, un service web est souvent déployé depuis une application web (WAR).
Le plugin archetype permet de créer rapidement un projet Maven.
Exemple testé avec Maven 2.011, 2.1.0, 2.2.1, 3.0 :
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=jee.architect.cookbook.jws -DartifactId=yellow-book-service -Dversion=1.0-SNAPSHOT -Dpackage=jee.architect.cookbook.jws.yellowbook


Ajoutez la référence au POM corporate :
<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">
   <modelVersion>4.0.0</modelVersion>
        
   <parent>
       <groupId>jee.architect.cookbook</groupId>
       <artifactId>corporate-pom</artifactId>
       <version>1.0</version>
   </parent>
        
   <groupId>jee.architect.cookbook.jws</groupId>
   <artifactId>yellow-book-service</artifactId>
   <version>1.0-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Yellow Book Service</name>

Notre premier service web


JAX-WS propose deux approches classiques : "top down" et "bottom up".
L'approche top down consiste à partir d'un WSDL pour générer des éléments Java, tandis que l'approche "bottom up" consiste à partir du code Java.
Nous utiliserons ici l'approche "bottom up".
Avec JAX-WS cette approche se traduit par l'utilisation d'annotations dédiées que l'on pose sur des POJOs.
Bien souvent, un service web expose une fonction métier modélisée sous la forme d'une interface.
On parle aussi de services métiers déployés au sein d'une couche métier qui définit (interfaces) et implémente (classes concrètes, mapping ORM, ...) l'ensemble de la logique métier d'une application, d'un système d'information ou de l'entreprise.
Ainsi, le service web n'est que la partie émergée du métier.
Parfois plusieurs modes d'exposition d'une fonction métier peuvent cohabiter pour des raisons techniques ou pratiques : EJB (transactionnalité, ... ), REST (consultation simple, ...), API à embarquer, ....

Nous considèrerons ainsi qu'il existe une interface qui définit notre service métier :
public interface YellowBook {
   String getPhoneNumber(String firstName, String lastName);
}

Notre service des pages jaunes fournit un numéro de téléphone pour un nom et un prénom.
Cette interface sera éventuellement implémentée par plusieurs classes.
Ici, notre classe Java qui va implémenter le service web va utiliser ce contrat.

public class YellowBookWSImpl implements YellowBook  {

   public String getPhoneNumber(String firstName, String lastName){          
       return "999999999";
   }
 
}

A ce stade notre classe ne se distingue en rien d'une classe Java standard.
Nous allons utiliser les annotations de l'API JAX-WS pour la transformer en service web.
Tout d'abord l'annotation @WebService qui transforme une classe en service web.
@WebService
public class YellowBookWSImpl implements YellowBook  {

   public String getPhoneNumber(String firstName, String lastName){          
       return "999999999";
   }
 }

Cette classe est désormais un service web qui présente une opération "getPhoneNumber". Par défaut, ce sont toutes les méthodes de votre classe qui deviennent des opérations.
L'annotation @WebService propose plusieurs attributs importants qui déterminent la manière dont votre service web sera exposé.
La JSR 224 définit une correspondance entre Java et WSDL. In fine, vos clients ne connaitront probablement que le WSDL.
Il est donc important de maîtriser cette correspondance :
  • serviceName : le nom du service web.
  • name : le nom du type de port du service web. Par défaut c'est le nom de la classe ou de l'interface annotée, ici, "YellowBookWSImpl".
  • targetNamespace : espace de nommage cible. Par défaut c'est le nom complet du package inversé avec "http://" comme préfixe.
  • portName : le nom du port ou point de terminaison.
Nous pouvons affiner notre définition :
@WebService(name="YellowBookPortType",serviceName="YellowBookService", portName = "YellowBookPort")
public class YellowBookWSImpl implements YellowBook  {

   public String getPhoneNumber(String firstName, String lastName){          
       return "999999999";
   }
}

JAX-WS propose aussi des annotations à poser sur les méthodes du service web.
Elles permettent de piloter la définition du service (le WSDL) notamment au niveau des données échangées en entrée et en sortie.
Dans notre exemple, nous avons pas besoin d'utiliser ces annotations car le type de données String est pris en charge nativement par JAXB.
JAX-WS sous traite la sérialisation XML des objets Java à JAXB.
JAXB proposera une représentation XML de vos données ainsi qu'un schéma XML associé qui sera associé au WSDL.
Nous ne traiterons pas de cette problématique dans cet article mais probablement dans un autre !

Utilisation du plugin JAX-WS


Ce plugin permet de générer le WSDL du service web.
Pour que le WSDL soit toujours synchronisé avec la classe d'implémentation du service web, il suffit de lier l'exécution du goal "wsgen" à la phase "process-classes".
Cette phase vient après la compilation, or le plugin a besoin de la classe compilée pour générer le WSDL.
En attachant le déclenchement du goal "wsgen" à cette phase on est certain de disposer de la classe compilée.
Consultez la documentation Maven pour le détail du cycle de vie d'un projet.
   <build>
       <plugins>
           <plugin>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>jaxws-maven-plugin</artifactId>
               <version>1.10</version>
               <executions>
                   <execution>
                       <phase>process-classes</phase>
                       <goals>
                           <goal>wsgen</goal>
                       </goals>
                   </execution>
               </executions>
               <configuration>
                   <sei>jee.architect.cookbook.jws.yellowbook.YellowBookWSImpl</sei>
                   <genWsdl>true</genWsdl>
                   <keep>true</keep>
                   <resourceDestDir>${basedir}/src/main/webapp/WEB-INF/wsdl</resourceDestDir>
               </configuration>
           </plugin>
       </plugins>
   </build>
Exécutez un clean install pour déclencher la génération du WSDL et du schéma ou des schémas associés pour les types de données.
Pour que JAX-WS utilise le WSDL généré il faut indiquer l'emplacement de ce fichier grâce à l'attribut "wsdlLocation":
@WebService(name="YellowBookPortType",serviceName="YellowBookService", portName = "YellowBookPort",wsdlLocation="YellowBookService.wsdl")
public class YellowBookWSImpl implements YellowBook  {

   public String getPhoneNumber(String firstName, String lastName){          
       return "999999999";
   }
}

Déployons


Nous devons d'abord construire le WAR via une suite de goal Maven
mvn clean install
Ensuite déployez ce WAR sur un serveur JEE compatible JAX-WS comme Glassfish 3.0.1 Web Profile.
Glassfish est fourni avec une CLI puissante, asadmin (GLASSFISH_HOME/bin/asadmin).
Démarrez Glassfish :
./asadmin start-domain
Puis déployez le WAR :
./asadmin deploy /Users/oschmitt/java/github/the-jee-architect-cookbook/jws/yellow-book-service/target/yellow-book-service-1.0-SNAPSHOT.war
Vérifiez ensuite le déploiement de votre service web : http://localhost:8080/YellowBook/YellowBookService
Les sources (produites avec Netbeans 6.9.1 et Maven 3.0.1) de cet article sont disponibles sur le dépôt Github.

Contrat Creative Commons
the jee architect cookbook by Olivier SCHMITT est mis à disposition selon les termes de la licence Creative Commons Paternité - Pas d'Utilisation Commerciale - Pas de Modification 3.0 Unported.

mardi 16 novembre 2010

Industrialisez vos build avec Maven


Maven est un outil de gestion du projet informatique désormais bien installé dans le monde de l'entreprise.
Il peut vous apporter beaucoup dans la maîtrise et l'industrialisation de vos processus de gestion de projet.
Les processus standards sont :
  • l'initialisation
  • la compilation
  • le packaging
  • la documentation
  • le déploiement
  • la livraison
  • ...
Cependant, sa mise en place peut s'avérer difficile : pour éviter certains désagréments et industrialiser vos builds, nous allons voir une bonne pratique facile à mettre en place.

Le coeur de Maven


Maven est constitué d'un coeur très léger : ce coeur ne contient pas les processus qui vont gérer votre projet.
Ces processus sont implémentés dans des plugins qui sont téléchargés à la volée par le coeur de Maven.
Au fil du temps, le coeur va mettre à jour les versions des plugins qui implémentent la gestion du projet (clean, compile,… ).
Cet architecture est à la fois une force et une faiblesse.
Grâce à cette architecture Maven est extrêmement modulaire, les possibilités d'extensions sont infinies, la mise à jour des plugins permet de bénéficier des dernières corrections et évolutions des processus.
D'un autre côté, cette mise à jour automatique peut provoquer une instabilité des builds, tel projet qui était livré avec le plugin release, n'est plus livrable un mois plus tard alors que le pom n'a pas évolué.[1]
En effet, chaque mise à jour vient avec son lot de nouveaux bugs et d'incompatibilités avec les autres plugins. J'ai personnellement expérimenté douloureusement cet état de fait pour la livraison d'un framework JEE dont je suis le responsable technique.
Nous effectuons plusieurs livraisons par an avec Maven et son plugin release. Une fois sur deux, la livraison se faisait dans la douleur.
Parfois, nous fumes contraints de livrer à la main une partie du logiciel.
Les problèmes que nous rencontrions étaient dû à une ou plusieurs montées en versions des plugins utilisés lors du release (compile, package, …) dont les évolutions étaient parfois non négligeables : telle option n'est plus prise en compte, telle option provoque un effet de bord, telle utilisation ne fonctionnait plus, ….
Pour palier à ces problèmes l'équipe Maven a figé un certains nombres de plugins dans une version donnée (à partir de Maven 2.1.X). C'est le super POM [2] de Maven qui contient cette configuration (ici).
Cette stratégie est intéressante mais incomplète : d'une part, elle ne couvre que les plugins cités dans le super POM, d'autre part, elle stoppe la montée en version automatique de ces plugins.
Or ces montées versions peuvent être une réponse à des problèmes que vous n'arrivez pas à résoudre (recours à du ant, ...). En effet, lorsqu'on jette un oeil sur la page JIRA des plugins Maven que le super POM référence on s'aperçoit que ces plugins ont évolué.
Par exemple, le plugin assembly est figé dans sa version 2.2-beta-2 dans le super POM de Maven 2.1.X et 2.2.X. Mais lorsqu'on regarde la page JIRA du plugin on s'aperçoit qu'il y a eu 4 versions supplémentaires. On note aussi que les bugs corrigés peuvent être conséquents, par exemple pour la version 2.2-beta-3 (ici) :
  • MASSEMBLY-242 : transitive dependencies do not get included
  • MASSEMBLY-285 : duplicate files added to the assembly
  • ...
De plus, en cas de changement de version de Maven vos builds ne s'exécuteront pas forcément avec le même arbre de dépendances de plugins : les versions de certains plugins étant fixées dans le super POM alors une nouvelle version de Maven peut apporter un super POM différent.

Industrialiser les builds


Pour bénéficier à plein de la puissance de Maven, il est indispensable de mettre en oeuvre un POM d'entreprise (dit aussi corporate POM).
Ce POM contient alors la configuration pour tous les plugins usuels pour une ou plusieurs versions de Maven (de nombreux plugins fonctionnent à partir de Maven 2.0.6).
Tout POM de projet qu'il soit simple ou multi-module hérite alors de ce POM.
Pour établir ce POM corporate vous avez besoin de la commande :
mvn help:effective-pom

Cette commande produit un POM qui correspond au POM effectif qui est le résultat de toutes les configurations applicables : la hiérarchie de POM (super POM, ...), settings.xml, ...
Exécutée sur plusieurs POM qui sont représentatifs, elle vous permettra d'établir rapidement un premier jet de votre POM corporate.
Extrait de sortie de la commande avec Maven 2.2.1 et la section pluginManagement (issue du super POM):
   <pluginManagement>
     <plugins>
       <plugin>
         <artifactId>maven-antrun-plugin</artifactId>
         <version>1.3</version>
       </plugin>
       <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <version>2.2-beta-2</version>
       </plugin>
       <plugin>
         <artifactId>maven-clean-plugin</artifactId>
         <version>2.2</version>
       </plugin>
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.0.2</version>
       </plugin>
       <plugin>
         <artifactId>maven-dependency-plugin</artifactId>
         <version>2.0</version>
       </plugin>
       <plugin>
         <artifactId>maven-deploy-plugin</artifactId>
         <version>2.4</version>
       </plugin>
       <plugin>
         <artifactId>maven-ear-plugin</artifactId>
         <version>2.3.1</version>
       </plugin>
       <plugin>
         <artifactId>maven-ejb-plugin</artifactId>
         <version>2.1</version>
       </plugin>
       <plugin>
         <artifactId>maven-install-plugin</artifactId>
         <version>2.2</version>
       </plugin>
       <plugin>
         <artifactId>maven-jar-plugin</artifactId>
         <version>2.2</version>
       </plugin>
       <plugin>
         <artifactId>maven-javadoc-plugin</artifactId>
         <version>2.5</version>
       </plugin>
       <plugin>
         <artifactId>maven-plugin-plugin</artifactId>
         <version>2.4.3</version>
       </plugin>
       <plugin>
         <artifactId>maven-rar-plugin</artifactId>
         <version>2.2</version>
       </plugin>
       <plugin>
         <artifactId>maven-release-plugin</artifactId>
         <version>2.0-beta-8</version>
       </plugin>
       <plugin>
         <artifactId>maven-resources-plugin</artifactId>
         <version>2.3</version>
       </plugin>
       <plugin>
         <artifactId>maven-site-plugin</artifactId>
         <version>2.0-beta-7</version>
       </plugin>
       <plugin>
         <artifactId>maven-source-plugin</artifactId>
         <version>2.0.4</version>
       </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <version>2.4.3</version>
       </plugin>
       <plugin>
         <artifactId>maven-war-plugin</artifactId>
         <version>2.1-alpha-2</version>
       </plugin>
     </plugins>
   </pluginManagement>

Vous n'avez plus ensuite qu'à recopier ce fragment dans votre POM corporate sans oublier de monter en version si nécessaire (voir le JIRA), d'ajouter des détails de configuration ou les plugins qui n'y sont pas.
Exemple incomplet d'un POM corporate (voir le dépôt github) pour la version 2.1.0 de Maven :
<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"> 
 <modelVersion>4.0.0</modelVersion>
 <groupId>jee.architect.cookbook</groupId>
 <artifactId>corporate-pom</artifactId>
 <packaging>pom</packaging> 
 <version>1.0</version> 
 <name>mon-organisation-base-pom</name> 
 <url>http://maven.apache.org</url>
 <build> 
     <pluginManagement> 
        <plugins> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-clean-plugin</artifactId> 
            <version>2.2</version> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-source-plugin</artifactId> 
            <version>2.0.4</version> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-resources-plugin</artifactId> 
            <version>2.3</version> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-compiler-plugin</artifactId> 
            <version>2.0.2</version> 
            <configuration> 
              <encoding>UTF-8</encoding> 
              <source>1.5</source> 
              <target>1.5</target> 
            </configuration> 
          </plugin> 
          <plugin> 
            <groupId>org.codehaus.mojo</groupId> 
            <artifactId>aspectj-maven-plugin</artifactId> 
            <version>1.1</version> 
            <configuration> 
              <encoding>UTF-8</encoding> 
              <source>1.5</source> 
              <target>1.5</target> 
              <complianceLevel>1.5</complianceLevel> 
            </configuration> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-install-plugin</artifactId> 
            <version>2.2</version> 
          </plugin> 
          <plugin> 
            <groupId>org.codehaus.cargo</groupId> 
            <artifactId>cargo-maven2-plugin</artifactId> 
            <version>1.0.1-SNAPSHOT</version> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-eclipse-plugin</artifactId> 
            <version>2.6</version> 
            <configuration>                 
               <ajdtVersion>none</ajdtVersion> 
               <wtpversion>2.0</wtpversion> 
            </configuration> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-release-plugin</artifactId> 
            <version>2.0-beta-9</version>
            <configuration> 
              <remoteTagging>true</remoteTagging> 
            </configuration> 
          </plugin> 
          <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-pmd-plugin</artifactId> 
            <version>2.4</version> 
            <configuration> 
              <targetJdk>1.5</targetJdk> 
              <rulesets> 
                <ruleset>/rulesets/basic.xml</ruleset> 
                <ruleset>/rulesets/basic-jsp.xml</ruleset> 
                <ruleset>/rulesets/braces.xml</ruleset> 
                <ruleset>/rulesets/codesize.xml</ruleset> 
                <ruleset>/rulesets/clone.xml</ruleset> 
                <ruleset>/rulesets/coupling.xml</ruleset> 
                <ruleset>/rulesets/design.xml</ruleset> 
                <ruleset>/rulesets/imports.xml</ruleset>
                <ruleset>/rulesets/migrating_to_15.xml</ruleset> 
                <ruleset>/rulesets/strings.xml</ruleset> 
                <ruleset>/rulesets/unusedcode.xml</ruleset> 
              </rulesets> 
             </configuration> 
          </plugin> 
       </plugins> 
    </pluginManagement> 
  </build> 
</project>

Extrait incomplet d'un POM d'un projet simple qui utilise ce POM de base :
<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">
 <modelVersion>4.0.0</modelVersion>
  <parent>
     <groupId>jee.architect.cookbook</groupId>
     <artifactId>corporate-pom</artifactId>
     <version>1.0</version> 
   </parent>
   <groupId>mon-projet</groupId> 
   <artifactId>mon-projet-web</artifactId> 
   <packaging>war</packaging> 
   <version>1.2-SNAPSHOT</version> 
   <name>mon-projet-web</name>

Les POMs qui utilisent le POM corporate peuvent parfaitement surcharger la configuration, en re-déclarant les plugins concernés dans le POM du projet.

Conclusion


Cette approche offre plusieurs bénéfices :
  • tous les projets qui utilisent une version donnée de Maven se base sur les même processus : même version, même configuration (encoding, JDK, … )
  • les développeurs ne perdent plus de temps à configurer Maven (ou à copier/coller plus ou moins proprement les configurations)
  • les builds sont reproductibles dans le temps [3]
  • la configuration est centralisée : la correction, l'évolution ou la montée en version de la configuration se fait dans un seul POM
Cependant n'oubliez pas que le POM d'entreprise constitue un projet en soit, il faut donc prévoir une charge pour sa mise au point et pour la maintenance corrective ou évolutive (n'oubliez pas si vous utilisez le POM Corporate du billet de mettre à jour les plugins).

Contrat Creative Commons
the jee architect cookbook by Olivier SCHMITT est mis à disposition selon les termes de la licence Creative Commons Paternité - Pas d'Utilisation Commerciale - Pas de Modification 3.0 Unported.

mardi 9 novembre 2010

The jee architect cookbook : mon premier billet !


Tout d'abord bienvenue sur mon blog !

Déjà huit années au poste d'architecte Java/JEE d'un grand compte !

J'éprouve l'envie de partager mon expérience avec d'autres.

Ce blog sera l'occasion de partager :
- des petites ou des grandes recettes autour de Java/JEE,
- de commenter l'actualité,
- ...

J'espère que ces billets vous seront utiles.

Merci de votre visite.
A bientôt.