Affichage des articles dont le libellé est web service. Afficher tous les articles
Affichage des articles dont le libellé est web service. Afficher tous les articles

06 janvier 2011

RPC/Encoded avec JAX-WS

A l'époque ou SUN a voulu faire prendre à Java le train des services web, le JCP a accouché de JAX-RPC. Nous avons été nombreux à choisir l'implémentation Apache Axis pour exposer ou consommer de tels services.

Mauvaise pioche, le "marché" a par la suite adopté l'approche document, alors que JAX-RPC était un RMI pour les services web, basé sur l'approche RPC. Pour la version 2.0 de la norme, le JCP a changé son fusil d'épaule et créé JAX-WS, norme qui exclue le mode RPC/Encoded et repose sur JAXB.

Voilà pour l'historique. Aujourd'hui, que vous choisissiez Metro, JbossWSAxis2 ou Apache CXF (mon préféré), vous  partez sur une stack web-services éprouvée et interopérable ... jusqu'à ce qu'un partenaire vous expose un vieux WSDL bien naze en RPC/Encoded.

Un réflexe que j'ai constaté plusieurs fois dans ces circonstances consiste à déterrer Axis pour communiquer avec ce service web façon grand-mère. Autrement dit, partir sur un outil qui n'a plus évolué depuis 5 ans, abandonné par ses développeurs au profit d'Axis2 et ... techniquement discutable (pour avoir du déboguer un problème de namespace dedans, je vous déconseille d'essayer)

Si la norme JAX-WS exclue le support du mode RPC/Encoded, cela ne signifie pas qu'il est impossible d'invoquer un service de ce type. On peut toujours exploiter notre pile web-services, incluant ses mécanismes de sécurité, de gestion de ressources, de log et monitoring, etc. Par contre on ne bénéficiera pas des mécanisme de binding entre le flux XML et du code Java généré.

Le moyen le plus simple que j'ai trouvé (je suis ouvert à toutes suggestion) est de construire à la main la trame de requête (utiliser SoapUi pour avoir une base valide), à l'aide d'un mécanisme de template, puis d'aller piocher les éléments du flux de réponse via son arbre DOM. Ca donne ça :

// Construction de la trame requête, 
// utiliser un mécanisme de template style freemarker si besoin
String request = "<soapenv:Envelope "
  + " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
  + " xmlns:xsd='http://www.w3.org/2001/XMLSchema' "
  + " xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' "
  + " xmlns:urn='urn:Naze'>"
  + "<soapenv:Header/>"
  + "<soapenv:Body>"
  + "  <urn:foo soapenv:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>"
  + "    <id xsi:type='xsd:string'>42</id>"
  + "    <query xsi:type='xsd:string'>ndeloof</query>"
  + "  </urn:foo>"
  + "</soapenv:Body>"
  + "</soapenv:Envelope>";

// Un peu de manipulation des API XML, désolé
QName serviceName = new QName( "urn:Naze", "NazeService" );
QName portName = new QName( "urn:Naze", "NazePort" );
Service service = Service.create( getWSDL(), serviceName );

// Utilisation de l'API de niveau "message" de JAX-WS pour transférer le message
Dispatch dispatch = 
   service.createDispatch( portName, Source.class, Service.Mode.MESSAGE );
dispatch.getRequestContext().put( 
   BindingProvider.USERNAME_PROPERTY, "login" );
dispatch.getRequestContext().put( 
   BindingProvider.PASSWORD_PROPERTY, "password" );
Source response = dispatch.invoke( 
   new StreamSource( new StringReader( request ) ) );

MessageFactory msgFactory = MessageFactory.newInstance();
SOAPMessage msg = msgFactory.createMessage();
SOAPPart env = msg.getSOAPPart();
env.setContent( request );

if ( msg.getSOAPBody().hasFault() )
  throw new RemoteAccessException( "oups..." );

// récupération des éléments XML qui nous intéressent
NodeList arrayOfString = 
  msg.getSOAPBody().getElementsByTagName( "result" );
Node node = arrayOfString.item( 0 );
return node.getTextContent();

Je vous accorde que c'est un peu laborieux ;)

Une autre solution, plus légère mais moins portable, consiste à appeler Spring-WS à notre secours. Les templates de Spring permettent d'invoquer un service web en manipulant directement les messages, comme nous venons de le faire, mais avec une couche de Spring pour simplifier le code. Spring-WS n'utilise pas notre  pile JAX-WS mais des appels HTTP directs avec Commons-httpclient et XPath pour extraire les données du message de réponse, avec des classes très "Spring" pour mapper les noeuds XML sur nos classes Java.

Le résultat est une très grande souplesse pour s'adapter à des services web pas trop standards (et il y en a, vous pouvez me croire, qui lisent les normes avec les pieds) :

private final WebServiceTemplate webServiceTemplate;

private final XPathOperations xpath;

public NazeClient( String url ) throws Exception {
   webServiceTemplate = new WebServiceTemplate();
   webServiceTemplate.setDefaultUri( url );
   CommonsHttpMessageSender sender = configureSender();
   webServiceTemplate.setMessageSender( sender );
   xpath = new Jaxp13XPathTemplate();
}

private CommonsHttpMessageSender configureSender()  throws Exception {
   CommonsHttpMessageSender sender = new CommonsHttpMessageSender();
   sender.setCredentials( 
       new UsernamePasswordCredentials( "login", "password" ) );
   sender.afterPropertiesSet();
   return sender;
}

public List sendAndReceive() {
   String message = ""; // idem exemple précédent
   StreamSource source = new StreamSource( new StringReader( message ) );
   Result result = new StringResult();
   webServiceTemplate.sendSourceAndReceiveToResult( source, result );

   List values = xpath.evaluate( "//result", 
       new StringSource( result.toString() ),
       new NodeMapper() {
          public String mapNode( Node node, int nodeNum ) {
              return node.getTextContent();
          }
      } );
   return values;
}

23 décembre 2007

services-web : Metro vs CXF vs Axis2

J'ai utilisé depuis X années Axis1 comme pile Soap, parfois avec douleur quand il a fallu rentrer dans le code pour identifier un problème de sérialisation... le code n'étant pas vraiment limpide !

Avec la norme Jax-WS et les "nouvelles" piles SOAP, d'autres solutions sont possibles. J'ai eu l'occasion sur un projet qui démare de faire une rapide évaluation :
  • Metro (implémentation de référence de Jax-WS, faisant partir du projet Glassfish)

A priori attractif, en raison de l'aspect "respect des standard". L'essai n'a pas été très concluant :

  1. la gestion des dépendances Maven est délicate, car on fait référence à un grand nombre de jars javax.* et com.sun.* qui sont dans le repository maven1 de dev.java.net. On doit donc jongler avec les repositories
  2. le plugin wsdl -> service, basé sur jaxb2.1 ne fonctionne pas sous Java6. Merci à Sun d'avoir pensé son API de manière si évolutive !
  3. le plugin plante sur mon WSDL, qui utilise des imports.
  4. le support pour Spring utilise un repository TRES surprenant : http://ndeloof.free.fr, mon site perso à moi que j'ai ! Celui sur lequel j'ai placé les -sources des jars Spring à tritre temporaire...
  • Axis2

Après le succes de fait de Axis 1 (faute d'alternative ?), axis2 semble la solution à suivre.

  1. D'après ce que j'ai pu lire, les premières versions ont été une calamité en terme de bugs
  2. Le plugin wsdl2java plante sur mon WSDL avec un joli NullPointerException
  3. Le POM.xml du projet fait référence à repo1.maven.org/maven1 + repo1.maven.org/maven2. Non seulement ce sont les repository par défaut, mais le premier est une redirection vers le second. Détail, mais qui montre le niveau de support pour maven2
  4. Le principe de packages un "aar" pour le déployer dans la webapp est hyper lourd. Je n'ai JAMAIS eu besoin de déployer à chaud un service web, sans avoir à relivrer toute l'application qui va avec. Je suppose qu'il y en a à qui ça sert, mais ça devrait n'être qu'une option.
  • Apache CXF (ex apache Cletix + codehaus Xfire fusionnés)

J'ai déjà entendu pas mal de bien de XFire, qu'en est-il de sa fusion avec Celtix ?

  1. La génération Wsdl -> service fonctionne. C'est déjà bien
  2. Les méta données Maven2 sont bonnes, via le repository de l'incubateur apache
  3. L'intégration avec Spring est triviale, via un namespace dédié et trois lignes de XML
  4. Un transport "local" permet de tester le service web (sérialisation SOAP incluse) sans déployer de serveur HTTP. Super pratique pour écrire des tests unitaires.
  5. Possibilité de tracer facilement les messages SOAP dans les logs : pratique

Pour ce qui me concerne, CXF est donc vainqueur par KO. J'ai pu développer et écrire un test unitaire pour mon service en quelques heures, en ne connaissant rien à l'outil à priori. La doc est claire et correspond à ce qu'on obtient réellement (ce qui est rare ;-p). L'intégration avec maven2 et Spring est très bonne, critère qui n'est pas forcément celui de tout le monde mais qui a son importance pour moi.

Mon second choix irait à Metro si les "petits problèmes" sont corrigés

Quand à Axis2 - "pourquoi faire simple ..." - moi qui cherche un moyen de faire du service web via juste quelques fichiers de conf, voila qu'il faut faire un projet dédié, packager un "aar", le déployer, créer des modules et je ne sais quoi... tout ça pour un "hello world" ?