TP implémentation de services et de clients via JAXWS

Mises à jour

  • 2011-10-15: ajout d'une indication sur la partie client.
  • 2011-10-15: ajout de la date de rendu.
  • 2011-10-10: mise à jour du dépôt pour correction de dépendance sur le projet modele

Modalités

Ce TP est à rendre pour le 23/10/2011, à raison d'un rendu par binôme.

  • Les binômes sont à constituer au sein des groupes de 4-5 pour les étudiants faisant partie d'un tel groupe. Les étudiants sans binôme (dans les groueps de 5) peuvent constituer un binôme entre eux.
  • Les autres étudiants peuvent constituer un binôme ad-hoc pour ce TP.

Il est demandé de rendre le TP sous forme d'un dépôt Mercurial qui aura été initialisé en clonant le dépôt indiqué ci-dessous. Mercurial est un gestionnaire de version, au même titre que par exemple git. A la différence de svn ou CVS c'est système décentralisé, permettant de réaliser des commit sans connexion avec un quelconque serveur central. Une introduction rapide à Mercurial est à disposition ici.

  • Si le temps le permet, un dépôt sera mis en place pour chaque binôme sur le serveur https://forge.univ-lyon1.fr. Il suffira alors de synchroniser votre travail sur ce dernier via la commande
    hg push url-du-depot-distant
  • Dans le cas contraire le rendu se fera par envoi via e-mail du dépôt zippé. Dans ce cas, il sera demandé de ne pas mettre les fichiers issus de la compilation du projet dans le zip, ce qui peut se faire d'une des manières suivantes:
    • cloner le projet courant dans un nouveau répertoire:
      hg clone mon-repertoire-de-dev le-repertoire-a-zipper

      puis zipper le répertoire ainsi obtenu;

    • alternativement, exécuter
      mvn clean

      dans le projet racine (projet) avant la compression, en ayant quitter l'environnement de développement afin d'éviter toute recompilation intempestive.

Préliminaires

Mise à jour des sources de base

Mettre à jour le projet de base:

hg pull https://forge.univ-lyon1.fr/hg/tiw5-2011-tp-base

Importer le projet services-impl dans le workspace Eclipse.

Logiciels additionnels nécessaires

  • soapUI, soit sous forme de logiciel indépendant, soit sous forme de plugin1).
  • Une base PostgreSQL avec un rôle de connexion etudiant/etudiant ayant accès au schéma public de la base postgres.

Configuration de Tomcat et Eclipse

Ajouter, si ce n'est déjà fait, un serveur tomcat 7.0 à votre configuration Eclipse2).

Créer un fichier src/main/webapp/META-INF/context.xml dans le projet web-interface:

<Context>
	<Resource auth="Container" driverClassName="org.postgresql.Driver"
		maxActive="100" maxIdle="30" maxWait="10000" name="jdbc/EtudiantDS"
		password="etudiant" type="javax.sql.DataSource" url="jdbc:postgresql:postgres"
		username="etudiant" />
</Context>

Dans le projet web-interface, ajouter le code suivant au fichier web.xml:

 <resource-ref>
  <res-ref-name>jdbc/EtudiantDS</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
 </resource-ref>

et créer un fichier src/main/webapp/WEB-INF/classes/META-INF/persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
   version="1.0">
    <persistence-unit name="etudiant">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
    	<non-jta-data-source>java:/comp/env/jdbc/EtudiantDS</non-jta-data-source>
        <class>tiw5.modele.Album</class>
        <class>tiw5.modele.Piste</class>
        <class>tiw5.modele.Artiste</class>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
        </properties>
    </persistence-unit>
</persistence>

Étapes

Un premier service JAX-WS

Dans ce TP, on utilisera Apache CXF comme implémentation de JAX-WS.

Dans le projet services-impl, créer un répertoire src/main/java et ajouter ce répertoire au sources dans Eclipse3). Y créer une classe tiw5.services.impl.AlbumDataService sur le modèle suivant:

package tiw5.services.impl;
 
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import tiw5.modele.Album;
import tiw5.modele.Artiste;
 
@WebService(targetNamespace = "http://master-info.univ-lyon1.fr/M2TI/TIW5/services", 
            serviceName = "AlbumDataService", 
            name = "AlbumDataPortType", 
            portName = "AlbumDataPort")
public class AlbumDataService {
 
	private static final Logger log = LoggerFactory.getLogger(AlbumDataService.class);
 
	@WebMethod
	public Album getAlbumDescription(long albumId) {
		EntityManager em = Persistence.createEntityManagerFactory("etudiant")
				.createEntityManager();
		Album album = em.find(Album.class, albumId);
		return album;
	}
 
	@Oneway
	@WebMethod
	public void addAlbumDescription(Album album) {
		EntityManager em = Persistence.createEntityManagerFactory("etudiant")
				.createEntityManager();
		em.getTransaction().begin();
		if (em.find(Album.class, album.getId()) != null) {
			log.info("found {}",album.getId());
			em.merge(album);
		} else {
			log.info("did not found {}",album.getId());
			em.persist(album);
		}
		for(Artiste a : album.getArtistes()) {
			if (em.find(Artiste.class, a.getUri()) != null) {
				em.merge(a);
			} else {
				em.persist(a);
			}
		}
		em.getTransaction().commit();
	}
 
}

Dans le projet web-interface ajouter au fichier web.xml le code suivant4):

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:/services.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<servlet>
		<display-name>CXF Servlet</display-name>
		<servlet-name>CXFServlet</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/services/*</url-pattern>
	</servlet-mapping>

créer le fichier src/main/webapp/WEB-INF/classes/services.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
         http://cxf.apache.org/jaxws
         http://cxf.apache.org/schemas/jaxws.xsd">
 
  <import resource="classpath:META-INF/cxf/cxf.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
 
  <jaxws:endpoint id="album-data"
      implementor="tiw5.services.impl.AlbumDataService"
      address="/AlbumDataService"/>
 
</beans>

Vérifier que le fichier pom.xml contient les dépendances suivantes:

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
    	<groupId>tiw5</groupId>
    	<artifactId>modele</artifactId>
    	<version>1.0-SNAPSHOT</version>
    	<type>jar</type>
    	<scope>compile</scope>
    </dependency>
    <dependency>
    	<groupId>javax.servlet</groupId>
    	<artifactId>servlet-api</artifactId>
    	<version>2.5</version>
    	<type>jar</type>
    	<scope>compile</scope>
    </dependency>
    <dependency>
    	<groupId>tiw5</groupId>
    	<artifactId>services-impl</artifactId>
    	<version>1.0-SNAPSHOT</version>
    	<type>jar</type>
    	<scope>compile</scope>
    </dependency>
    <dependency>
    	<groupId>org.apache.cxf</groupId>
    	<artifactId>cxf-rt-frontend-jaxws</artifactId>
    	<version>${cxf.version}</version>
    	<type>jar</type>
    	<scope>runtime</scope>
    </dependency>
    <dependency>
    	<groupId>org.apache.cxf</groupId>
    	<artifactId>cxf-rt-transports-http</artifactId>
    	<version>${cxf.version}</version>
    	<type>jar</type>
    	<scope>compile</scope>
    </dependency>
    <dependency>
    	<groupId>org.hibernate</groupId>
    	<artifactId>hibernate-entitymanager</artifactId>
    	<version>3.6.7.Final</version>
    	<type>jar</type>
    	<scope>runtime</scope>
    </dependency>
    <dependency>
    	<groupId>org.slf4j</groupId>
    	<artifactId>slf4j-simple</artifactId>
    	<version>1.6.2</version>
    	<type>jar</type>
    	<scope>runtime</scope>
    </dependency>
    <dependency>
    	<groupId>postgresql</groupId>
    	<artifactId>postgresql</artifactId>
    	<version>9.0-801.jdbc4</version>
    	<type>jar</type>
    	<scope>runtime</scope>
    </dependency>
  </dependencies>
  <properties>
  	<cxf.version>2.4.2</cxf.version>
  </properties>

Exécuter l'application web dans le tomcat configuré dans Eclipse, vérifier le bon déploiement ici: http://localhost:8080/web-interface/services/ et comprendre le fichier WSDL associé au service déployer. Le comparer à la classe AlbumDataService pour comprendre le rôle des différentes annotations qui y sont utilisées.

Test du service

Créer un nouveau projet soapUI5) et indiquer l'adresse du WSDL généré par CXF. Remplir les données des requêtes prédéfinies et les exécuter pour tester le service.

Client pour le service

  1. Enregistrer le fichier wsdl généré par CXF dans le fichier src/main/resources/album-data.wsdl du projet client.
  2. Modifier le pom.xml de ce projet pour y ajouter le code permettant de générer un client pour ce service via apache CXF, en utilisant la documentation ici et .
  3. Utiliser les classes Java générées pour réaliser un client en ligne de commande pour le service précédent. Si l'argument en ligne de commande est un numéro, alors le client récupérera la description XML de l'album correspondant, sinon, et argument sera un nom de fichier xml contenant un descriptif d'album à insérer dans les données.
    • Afin de faciliter l'usage de JAXB, on s'autorisera à créer une classe tiw5.client.AlbumDescription qui contiendra un album du type généré par CXF pour représenter les albums du service. En particulier, cette classe pourra être annotée via @XmlRootElement et servir de point de départ à une (dé)sérialisation6).

Nouveau service

  1. Dans le projet services-impl, créer un fichier WSDL src/main/resources/stock.wsdl décrivant des opérations sur un entrepôt de disques, avec les opérations suivantes:
    • disponible: indique si le stock contient encore des disques correspondant au numéro passé en argument.
    • assureCapacite: prend une liste de couples (numéro d'album, quantité) et assure que la quantité requise est disponible pour chaque album.
    • commande: prend une liste de numéros d'albums et retire 1 à la quantité disponible de chaque album listé.
  2. Générer, de manière similaire à la partie précédente, du code pour cette fois-ci implémenter le service.
  3. Implémenter le service en héritant de l'interface Java générée (qui correspond au port type). L'implémentation de assureCapacite pourra se faire par simple mise à jour du nombre de disques disponibles.
  4. Rendre le service ainsi créé disponible dans web-interface.
  5. Tester avec SOAPUI.
1)
pour Eclipse, Netbeans ou IntelliJ
2)
onglet servers → New → Server
3)
clic droit → Build Path → Use as source folder
5)
ouvrir la perspective SoapUI si vous utilisez le plugin Eclipse
6)
marshall/unmarshall