Mavenisez une application Web.
Apprenez les bases de REST.
Maven est :
Maven décrit un projet informatique, ses ressources, son packaging, sa documentation, etc, dans un document XML appelé POM (Project Object Model), et favorise l'usage de conventions plutôt que de la configuration explicite. C'est à dire qu'en spécifiant à minima un projet Maven, les sources seront trouvés à un emplacement convenu, les classes seront compilées également dans un emplacement convenu, etc.
Maven s'utilise en ligne de commande mais s'intègre bien dans les IDE tels qu'Eclipse.
Ce tutorial se propose de créer un projet Web avec Maven dans lequel une transformation XSLT pourra être invoquée avec un service REST.
Vérifiez en premier lieu que Maven
est installé, sinon installez-le et ajoutez la commande mvn
dans le path de votre interpréteur de commande :
(référez-vous à la documentation
de Maven en cas de besoin)
$ mvn -v Apache Maven 3.0.4 (r1232337; 2012-01-17 09:44:56+0100) Maven home: /usr/share/maven Java version: 1.7.0_12-ea, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/jdk1.7.0_12.jdk/Contents/Home/jre Default locale: fr_FR, platform encoding: UTF-8 OS name: "mac os x", version: "10.7.5", arch: "x86_64", family: "mac"
Maven en définitive ne fait pas grand chose, à part lire un fichier POM, et, grâce à son contenu, exécute la tâche demandée par l'utilisateur. Les commandes prennent corps dans des plugins que soit Maven "connait", soit qu'il est possible d'ajouter au POM.
Le plugin Maven Archetype permet de créer le squelette d'une
application, et s'exécute avec le goal archetype:generate
.
Un goal est une tâche à accomplir qui accepte des arguments.
La syntaxe d'exécution est : mvn pluginId:goalId -Dparam=value
.
Créez votre projet (vous pouvez utiliser
vos propres groupId
, artifactId
et packageName
)
en choisissant comme archetype maven-archetype-webapp
:
$ mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp \ -DgroupId=org.inria.ns.tp \ -DartifactId=tp-web-rest \ -DpackageName=org.inria.ns.tp.rest \ -Dversion=1.0-SNAPSHOT [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Stub Project (No POM) 1 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>> [INFO] [INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<< [INFO] [INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom --- [INFO] Generating project in Interactive mode Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-webapp/1.0/maven-archetype-webapp-1.0.jar Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-webapp/1.0/maven-archetype-webapp-1.0.jar (4 KB at 33.2 KB/sec) Downloading: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-webapp/1.0/maven-archetype-webapp-1.0.pom Downloaded: http://repo.maven.apache.org/maven2/org/apache/maven/archetypes/maven-archetype-webapp/1.0/maven-archetype-webapp-1.0.pom (533 B at 9.1 KB/sec) [INFO] Using property: groupId = org.inria.ns.tp [INFO] Using property: artifactId = tp-web-rest [INFO] Using property: version = 1.0-SNAPSHOT [INFO] Using property: package = org.inria.ns.tp.rest Confirm properties configuration: groupId: org.inria.ns.tp artifactId: tp-web-rest version: 1.0-SNAPSHOT package: org.inria.ns.tp.rest Y: : y [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-webapp:1.0 [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: org.inria.ns.tp [INFO] Parameter: packageName, Value: org.inria.ns.tp.rest [INFO] Parameter: package, Value: org.inria.ns.tp.rest [INFO] Parameter: artifactId, Value: tp-web-rest [INFO] Parameter: basedir, Value: /Users/myhomedir/workspace [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] project created from Old (1.x) Archetype in dir: /Users/myhomedir/workspace/tp-web-rest [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 9.185s [INFO] Finished at: Tue Oct 15 14:51:46 CEST 2013 [INFO] Final Memory: 16M/125M [INFO] ------------------------------------------------------------------------
Vérifiez que vous obtenez l'arborescence suivante, typique d'une application Web :
Désormais, toutes les commandes Maven seront lancées depuis le répertoire racine du projet, celui contenant le POM. Maven utilisera systématiquement ce POM, donc votre projet.
Consultez le contenu des 3 fichiers obtenus.
Editez le pom.xml
et insérez le plugin
Jetty
(Jetty est un serveur Web similaire à Tomcat)
dans la balise <build>
:
<plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins>
Lancez le serveur Web :
$ mvn jetty:run [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building the effective model for org.inria.ns.tp:tp-web-rest:war:1.0-SNAPSHOT [WARNING] 'build.plugins.plugin.version' for org.mortbay.jetty:maven-jetty-plugin is missing. @ line 21, column 15 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building tp-web-rest Maven Webapp 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> maven-jetty-plugin:6.1.26:run (default-cli) @ tp-web-rest >>> [INFO] [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ tp-web-rest --- [debug] execute contextualize [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ tp-web-rest --- [INFO] No sources to compile [INFO] [INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ tp-web-rest --- [debug] execute contextualize [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory /Users/myhomedir/workspace/tp-web-rest/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ tp-web-rest --- [INFO] No sources to compile [INFO] [INFO] <<< maven-jetty-plugin:6.1.26:run (default-cli) @ tp-web-rest <<< [INFO] [INFO] --- maven-jetty-plugin:6.1.26:run (default-cli) @ tp-web-rest --- [INFO] Configuring Jetty for project: tp-web-rest Maven Webapp [INFO] Webapp source directory = /Users/myhomedir/workspace/tp-web-rest/src/main/webapp [INFO] Reload Mechanic: automatic [INFO] Classes = /Users/myhomedir/workspace/tp-web-rest/target/classes 2013-10-15 18:04:01.215:INFO::Logging to STDERR via org.mortbay.log.StdErrLog [INFO] Context path = /tp-web-rest [INFO] Tmp directory = determined at runtime [INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] web.xml file = /Users/myhomedir/workspace/tp-web-rest/src/main/webapp/WEB-INF/web.xml [INFO] Webapp directory = /Users/myhomedir/workspace/tp-web-rest/src/main/webapp [INFO] Starting jetty 6.1.26 ... 2013-10-15 18:04:01.323:INFO::jetty-6.1.26 2013-10-15 18:04:01.429:INFO::No Transaction manager found - if your webapp requires one, please configure one. 2013-10-15 18:04:01.676:INFO::Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server
Lancez un navigateur Web : http://localhost:8080/tp-web-rest/
D'où provient l'affichage obtenu ?
web.xml
indique quel fichier est utilisé par défaut :
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
Pour arrêter Jetty, utilisez simplement CTRL+C
.
Regardez les traces d'exécution dans la console
[INFO] Classes = /Users/myhomedir/workspace/tp-web-rest/target/classes
WEB-INF/classes
et
WEB-INF/lib
pour les librairies.
Nous apprenons que Maven est susceptible de compiler ses classes dans
le répertoire target
, mais que Jetty utilise src/main/webapp
(vérifiez dans les traces d'exécution).
Indiquez à Maven de compiler vers une arborescence conforme à une application Web,
en ajoutant après la section <plugins>
la directive suivante :
<!-- target/tp-web-rest/WEB-INF/classes --> <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
Notez comment certaines variables du projet peuvent s'utiliser dans le POM.
Relancez Jetty. Qu'indiquent les logs ?
Le path est conforme à nos attentes :
[INFO] Classes = /Users/myhomedir/workspace/tp-web-rest/target/tp-web-rest/WEB-INF/classes
Examinez l'arborescence du répertoire target
. Que constatez-vous ?
target/classes
et target/tp-web-rest/WEB-INF/classes
target/tp-web-rest/WEB-INF/web.xml
Tapez mvn clean
et examinez à nouveau l'arborescence
Tapez mvn war:war
et vérifiez le résultat.
Plusieurs commandes Maven peuvent être soumises en une seule fois : mvn clean war:war
.
Il y a une différence entre mvn clean
et mvn war:war
. La première forme
n'invoke pas le goal d'un plugin (ce qui est le cas de la deuxième forme),
mais une phase du cycle de vie de Maven.
Le cycle de vie de l'élaboration d'un projet est une suite ordonnée de phases aboutissant à sa construction. Plusieurs cycles de vie peuvent être considérés, mais le plus commun (le cycle de vie par défaut) commence par une phase de validation de l'intégrité du projet et se termine par le déploiement du projet. Les phases sont laissées vagues intentionnellement (validation, test, déploiement, etc) de sorte à pouvoir leur faire endosser un sens particulier selon le type de projet. Par exemple, pour un projet qui produit une archive Java, la phase package va construire un Jar ; pour un projet qui produit une application Web, elle produira un fichier War.
Regardez dans le POM, quel est le <packaging>
du projet ?
<packaging>war</packaging>
Ainsi, en soumettant la commande mvn package
, Maven identifie grâce au
type de projet que la phase "package" appartient au cycle de vie par défaut, et
applique une à une les phases de ce cycle jusqu'à la phase "package" ; chaque phase
est associée par défaut à une ou plusieurs tâches (goal d'un plugin).
En consultant les tableaux ci-contre, identifiez les tâches exécutées par la commande
mvn package
Vérifiez dans les traces d'exécution en lançant la commande mvn package
que les tâches ont bien été exécutées.
Phase | Goal |
---|---|
pre-clean | |
clean | clean:clean |
post-clean |
Nous verrons qu'un plugin peut se configurer pour être associé à une phase arbitraire.
(voir la référence Maven)
Phase | Goal | |
---|---|---|
Packaging jar | Packaging war | |
validate | ||
initialize | ||
generate-sources | ||
process-sources | ||
generate-resources | ||
process-resources | resources:resources | |
compile | compiler:compile | |
process-classes | ||
generate-test-sources | ||
process-test-sources | resources:testResources | |
generate-test-resources | ||
process-test-resources | ||
test-compile | compiler:testCompile | |
process-test-classes | ||
test | surefire:test | |
prepare-package | ||
package | jar:jar | war:war |
pre-integration-test | ||
integration-test | ||
post-integration-test | ||
verify | ||
install | install:install | |
deploy | deploy:deploy |
Dans les traces d'exécution peuvent se trouver quelques avertissements concernant l'encodage. Avant de nous occuper des librairies nécessaires au projet, complétons le POM avec quelques propriétés.
Ajoutez avant la section <dependencies>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties>
Les logs indiquent également que la version du plugin Jetty est manquante, et mentionne celle qui est utilisée.
Corrigez la version du plugin Jetty.
<version>6.1.26</version>
Vérifiez les logs en lançant la commande mvn clean package
Il nous reste à ajouter les dépendances. Mais où trouver les librairies utiles ?
Allez à http://search.maven.org/ et
cherchez la librairie RESTEasy. Il y en a un peu trop... par tâtonnement
on peut inférer qu'une version récente est la "3.0.4.Final" de chez "org.jboss.resteasy".
Faites une recherche avancée, et saisissez le groupe, la version et
le packaging "jar" ; la requête devrait montrer :
g:"org.jboss.resteasy" AND v:"3.0.4.Final" AND p:"jar"
.
En vous réferrant à la documentation de RESTEasy vous devriez identifier
quel artéfact utiliser pour le projet.
Insérez la dépendance RESTEasy appropriée dans le POM :
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.0.4.Final</version> </dependency>
Dans une application Web, on utilise des classes du
package javax.servlet
.
Utilisez http://search.maven.org/ pour trouver l'artéfact et ajoutez-le aux dépendances du POM.
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency>
Notez le <scope>
assigné aux dépendances :
pour les tests la valeur est "test", ce qui signifie que la
librairie ne servira que pour compiler et exécuter les tests
mais ne fera pas partie de l'application. La valeur "provided"
indique que la librairie est utile à la compilation, mais ne
doit pas être packagée dans l'application ; en effet, les classes
du package javax.servlet
sont fournies par
le conteneur de servlet (Tomcat, Jetty, ou autre).
Créez votre classe org.inria.ns.tp.rest.ZooService.java
et placez-là dans le répertoire src/main/java
.
Quelle commande Maven permet de compiler ?
mvn compile
Ecrivez le corps de votre classe en utilisant des annotations JAX-RS (voir JAX-RS Javadoc) afin :
welcome.txt
"
@javax.ws.rs.Path
@javax.ws.rs.GET
text/plain
@javax.ws.rs.Produces
package org.inria.ns.tp.rest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/welcome.txt") public class ZooService { @GET @Produces("text/plain") public String welcome() { return "Bienvenue au Zoo !"; } }
Une fois que votre code compile sans erreur, faites en un paquet.
Quelle commande Maven permet de faire un paquet ?
mvn package
Examinez l'arborescence en particulier le répertoire WEB-INF
de la cible,
que constatez-vous ?
On voit dans WEB-INF/lib
la librairie RESTEasy mentionnée
dans le POM, mais également toutes les librairies dépendantes,
dont celles propres à JAXRS.
Des milliers de librairies son accessibles depuis l'entrepot central
de Maven, qui sont téléchargées en fonction des besoins en local
dans le répertoire ~/.m2/repository
qui constitue
l'entrepot local.
Il est possible d'ajouter d'autres entrepots au POM.
La phase install
permet d'installer les artéfacts du projet
(jar ou war, doc, sources, etc) dans l'entrepot local.
Lancez la commande mvn dependency:tree
et examinez
la hiérarchie des dépendances.
Que constatez-vous ?
$ mvn dependency:tree [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building tp-web-rest Maven Webapp 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ tp-web-rest --- [INFO] org.inria.ns.tp:tp-web-rest:war:1.0-SNAPSHOT [INFO] +- junit:junit:jar:3.8.1:test [INFO] +- javax.servlet:javax.servlet-api:jar:3.0.1:provided [INFO] \- org.jboss.resteasy:resteasy-jaxrs:jar:3.0.4.Final:compile [INFO] +- org.jboss.resteasy:jaxrs-api:jar:3.0.4.Final:compile [INFO] +- org.slf4j:slf4j-simple:jar:1.5.8:runtime [INFO] | \- org.slf4j:slf4j-api:jar:1.5.8:runtime [INFO] +- org.scannotation:scannotation:jar:1.0.3:compile [INFO] | \- javassist:javassist:jar:3.12.1.GA:compile [INFO] +- org.jboss.spec.javax.annotation:jboss-annotations-api_1.1_spec:jar:1.0.1.Final:compile [INFO] +- javax.activation:activation:jar:1.1:compile [INFO] +- org.apache.httpcomponents:httpclient:jar:4.2.1:compile [INFO] | +- org.apache.httpcomponents:httpcore:jar:4.2.1:compile [INFO] | +- commons-logging:commons-logging:jar:1.1.1:compile [INFO] | \- commons-codec:commons-codec:jar:1.6:compile [INFO] +- commons-io:commons-io:jar:2.1:compile [INFO] \- net.jcip:jcip-annotations:jar:1.0:compile [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.525s [INFO] Finished at: Sun Oct 20 09:51:02 CEST 2013 [INFO] Final Memory: 9M/151M [INFO] ------------------------------------------------------------------------
Notre programme compile mais ne s'exécute pas encore... Il manque
l'intervention de RESTEasy pour traiter les requêtes HTTP et
les router vers notre classe ZooService
.
Editez src/main/webapp/WEB-INF/web.xml
et déclarez
la servlet RESTEasy et les paramètres utiles en vous inspirant de
la documentation RESTEasy.
<?xml version="1.0" encoding="UTF-8"?> <web-app 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-app_3_0.xsd" version="3.0"> <!-- Auto scan REST service --> <context-param> <param-name>resteasy.scan</param-name> <param-value>true</param-value> </context-param> <!-- this need same with resteasy servlet url-pattern --> <context-param> <param-name>resteasy.servlet.mapping.prefix</param-name> <param-value>/zoo</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <!-- Handles org.inria.ns.tp.rest.ZooService --> <servlet> <servlet-name>ResteasyServlet</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>ResteasyServlet</servlet-name> <url-pattern>/zoo/*</url-pattern> </servlet-mapping> <!-- Default page to serve --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Lancez Jetty. Quelle est l'URL d'accès à la classe ZooService
?
[INFO] Context path = /tp-web-rest
web.xml
spécifie sur quel path est mappé la
servlet RESTEasy : <url-pattern>/zoo/*</url-pattern>
;
le paramètre <param-name>resteasy.servlet.mapping.prefix</param-name>
mentionne que la partie utile du path est après la valeur
<param-value>/zoo</param-value>
ZooService
spécifie sur quel path est mappé
la classe : @Path("/welcome.txt")
L'URL obtenue est http://localhost:8080/tp-web-rest/zoo/welcome.txt
Mais cette URL provoque une erreur :
1063157 [1101161875@qtp-296187438-0] ERROR org.jboss.resteasy.core.ExceptionHandler - failed to execute javax.ws.rs.NotFoundException: Could not find resource for full path: http://localhost:8080/tp-web-rest/zoo/welcome.txt at org.jboss.resteasy.core.registry.ClassNode.match(ClassNode.java:73) at org.jboss.resteasy.core.registry.RootClassNode.match(RootClassNode.java:48) at org.jboss.resteasy.core.ResourceMethodRegistry.getResourceInvoker(ResourceMethodRegistry.java:444) at org.jboss.resteasy.core.SynchronousDispatcher.getInvoker(SynchronousDispatcher.java:234) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:171) at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:401) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:450) at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) at org.mortbay.jetty.Server.handle(Server.java:326) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542) at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:928) at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:549) at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212) at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404) at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410) at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Cette trace nous indique que la servlet org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
est bien sollicitée, donc le web.xml
est correct.
Mais RESTEasy ne trouve de mapping vers notre classe ZooServlet
.
Pourtant dans web.xml
le paramètre <param-name>resteasy.scan</param-name>
est bien positionné à <param-value>true</param-value>
Examinez dans les logs avant la stacktrace les paramètres concernant la configuration de Jetty, que constatez-vous ?
Jetty utilise ce path pour la Webapp :
[INFO] Webapp source directory = /Users/myhomedir/workspace/tp-web-rest/src/main/webapp
,
donc dans la branche src
.
RESTEasy va tenter d'y trouver des classes .class
qui utilisent REST,
mais il n'en trouvera pas ; il ne trouvera que la source ZooServlet.java
dont il n'a que faire mais pas la classe compilée, puisqu'elle est dans la branche
target
.
Dans la documentation du plugin Jetty trouvez le paramètre qui permet de corriger ce problème, et ajoutez-le au POM.
Dans le POM, le plugin Jetty devient :
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <!-- target/tp-web-rest --> <webAppSourceDirectory>${project.build.directory}/${project.build.finalName}</webAppSourceDirectory> </configuration> </plugin>
Relancez Jetty, vérifiez les traces. Que constatez-vous ?
[INFO] Webapp source directory = /Users/myhomedir/workspace/tp-web-rest/target/tp-web-rest
,
Jetty positionne le path de la Webapp dans la branche target
280 [main] INFO org.jboss.resteasy.plugins.server.servlet.ConfigurationBootstrap -
Adding scanned resource: org.inria.ns.tp.rest.ZooService
Lancez un navigateur sur l'URL : http://localhost:8080/tp-web-rest/zoo/welcome.txt
.
Quel est le résultat obtenu ?
Bienvenue au Zoo !
Yeah ! Ca marche enfin !
On veut pouvoir réduire le path : http://localhost:8080/zoo/welcome.txt
.
Configurez Jetty dans le POM pour que l'application soit déployée à la racine.
<contextPath>/</contextPath>
Maintenant que nous disposons d'une Web application REST correctement Mavenisée, aggrémentons la d'une transformation XSLT.
Utilisez ce document XML, cette feuille de style XSLT et ce module XSLT, avec cette image, celle-ci et celle-là. La transformation étant susceptible de s'opérer sur le serveur, quel est le répertoire le plus approprié dans lequel déposer ces fichiers ?
WEB-INF
pour les fichiers XSLT car ils n'ont pas à être publics,
et éventuellement pour le fichier XML en fonction de la visibilité qu'on
souhaite lui donner.
img
(dans la branche contenant les sources de la webapp bien sûr)
Ajoutez à votre classe :
public ZooService(@Context ServletContext ctxt)L'annotation
@javax.ws.rs.core.Context
permet de
transmettre au constructeur le contexte de la servlet.
Le contexte sert à résoudre le path d'une ressource relative
à
l'application grâce à la méthode getRealPath()
.// la feuille de style compilée javax.xml.transform.Templates zooTemplate; public ZooService(@Context ServletContext ctxt) { String xsltPath = ctxt.getRealPath("WEB-INF/zoo.xsl"); this.zooTemplate = // compiler la feuille de style XSLT }
animals.html
et
produisant une ressource du type MIME text/html
;
au lieu de retourner une String
, cette méthode doit
retourner un javax.ws.rs.core.StreamingOutput
.// ajouter les annotations public StreamingOutput getAnimals(final @Context ServletContext ctxt) { return new StreamingOutput() { // transformer WEB-INF/zoo.xml avec zooTemplate }; }
package org.inria.ns.tp.rest; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletContext; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.StreamingOutput; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; @Path("/") public class ZooService { Templates zooTemplate; public ZooService(@Context ServletContext ctxt) throws TransformerConfigurationException, TransformerFactoryConfigurationError { String xsltPath = ctxt.getRealPath("WEB-INF/zoo.xsl"); this.zooTemplate = TransformerFactory.newInstance().newTemplates(new StreamSource(xsltPath)); } @GET @Path("/welcome.txt") @Produces("text/plain") public String welcome() { return "Bienvenue au Zoo !"; } @GET @Path("/animals.html") @Produces("text/html") public StreamingOutput getAnimals(final @Context ServletContext ctxt) { return new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { try { String xmlPath = ctxt.getRealPath("WEB-INF/zoo.xml"); Source xmlSource = new StreamSource(xmlPath); Result streamResult = new StreamResult(output); Transformer xslt = ZooService.this.zooTemplate.newTransformer(); xslt.transform(xmlSource, streamResult); } catch (Exception e) { throw new WebApplicationException(e); } } }; } }
La feuille de style XSLT accepte un paramètre :
<xsl:param name="liste-par-nom" select="true()" />
Modifiez votre classe pour que ce paramètre puisse être passé dans l'URL et transmise à la feuille de style XSLT.
@javax.ws.rs.QueryParam
et @javax.ws.rs.DefaultValue
@GET @Path("/animals.html") @Produces("text/html") public StreamingOutput getAnimals( final @Context ServletContext ctxt, final @DefaultValue("true") @QueryParam("listByName") boolean listByName) { return new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { try { String xmlPath = ctxt.getRealPath("WEB-INF/zoo.xml"); Source xmlSource = new StreamSource(xmlPath); Result streamResult = new StreamResult(output); Transformer xslt = ZooService.this.zooTemplate.newTransformer(); xslt.setParameter("liste-par-nom", listByName); xslt.transform(xmlSource, streamResult); } catch (Exception e) { throw new WebApplicationException(e); } } }; }
A tester sur les 3 URLs :
http://localhost:8080/zoo/animals.html?listByName=false
http://localhost:8080/zoo/animals.html?listByName=true
http://localhost:8080/zoo/animals.html
La phase "package" va construire le .war du projet. Mais ce n'est pas suffisant, on pourrait également avoir besoin de distribuer les sources. Le plugin Assembly est conçu pour produire des archives, et parmi les assemblages prédéfinis l'un permet d'empaqueter les sources.
Après consultation de la documentation, ajoutez le plugin Assembly à votre POM et configurez-le pour empaqueter les sources.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>src</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
Notez que le goal assembly:single
est configuré pour être
associé à la phase package
.
Lancez mvn package
et regardez les fichier produits dans le
répertoire target
.
Vous y trouverez les fichiers assemblés dans différents formats :
tp-web-rest-src.tar.bz2
tp-web-rest-src.tar.gz
tp-web-rest-src.zip
Voici l'application complète :
target/tp-web-rest-src.zip
target/tp-web-rest.war
Et les fichiers complets principaux :
<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>org.inria.ns.tp</groupId> <artifactId>tp-web-rest</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>Webapp REST/XSLT avec Maven</name> <url>http://www-sop.inria.fr/members/Philippe.Poulard/tp-web-rest.html</url> <developers> <developer> <id>ppoulard</id> <name>Philippe Poulard</name> <email>philippe.poulard@inria.fr</email> <url>http://www-sop.inria.fr/members/Philippe.Poulard</url> <organization>Inria</organization> <organizationUrl>http://www.inria.fr</organizationUrl> <roles> <role>designer</role> <role>developer</role> </roles> </developer> </developers> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <resteasyversion>3.0.4.Final</resteasyversion> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>${resteasyversion}</version> </dependency> </dependencies> <build> <finalName>tp-web-rest</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <webAppSourceDirectory>${project.build.directory}/${project.build.finalName}</webAppSourceDirectory> <contextPath>/</contextPath> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>src</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <web-app 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-app_3_0.xsd" version="3.0"> <context-param> <param-name>resteasy.scan</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>resteasy.servlet.mapping.prefix</param-name> <param-value>/zoo</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>ResteasyServlet</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>ResteasyServlet</servlet-name> <url-pattern>/zoo/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
package org.inria.ns.tp.rest; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletContext; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.StreamingOutput; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; @Path("/") public class ZooService { Templates zooTemplate; public ZooService(@Context ServletContext ctxt) throws TransformerConfigurationException, TransformerFactoryConfigurationError { String xsltPath = ctxt.getRealPath("WEB-INF/zoo.xsl"); this.zooTemplate = TransformerFactory.newInstance().newTemplates(new StreamSource(xsltPath)); } @GET @Path("/welcome.txt") @Produces("text/plain") public String welcome() { return "Bienvenue au Zoo !"; } @GET @Path("/animals.html") @Produces("text/html") public StreamingOutput getAnimals( final @Context ServletContext ctxt, final @DefaultValue("true") @QueryParam("listByName") boolean listByName) { return new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { try { String xmlPath = ctxt.getRealPath("WEB-INF/zoo.xml"); Source xmlSource = new StreamSource(xmlPath); Result streamResult = new StreamResult(output); Transformer xslt = ZooService.this.zooTemplate.newTransformer(); xslt.setParameter("liste-par-nom", listByName); xslt.transform(xmlSource, streamResult); } catch (Exception e) { throw new WebApplicationException(e); } } }; } }
Maven est d'une richesse sans fin ; il vous reste à apprendre :