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, JbossWS, Axis2 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 ListsendAndReceive() { String message = ""; // idem exemple précédent StreamSource source = new StreamSource( new StringReader( message ) ); Result result = new StringResult(); webServiceTemplate.sendSourceAndReceiveToResult( source, result ); Listvalues = xpath.evaluate( "//result", new StringSource( result.toString() ), new NodeMapper() { public String mapNode( Node node, int nodeNum ) { return node.getTextContent(); } } ); return values; }
2 commentaires:
N'importe quoi setContent ne prend pas de String
Quand j'ai redige ce billet il y a plus d'un an ca marchait (ce code tourne en prod...)
Enregistrer un commentaire