Accueil > Java EE, Web Services - SOA > Ecrire un client WebService avec CXF (3/3) – ajouter une pièce jointe

Ecrire un client WebService avec CXF (3/3) – ajouter une pièce jointe

Cet article, proposé par Henri Darmet, fait suite à celui publié la semaine dernière et qui expliquait comment sécuriser l’accès à un web service CXF avec WS-Security.

At last, but not at least : Ajouter une pièce jointe

Cette étape est assez simple à réaliser en soi, elle demande essentiellement, de faire quelques ajustements au niveau de la définition de l’objet passé par la requête SOAP. Je m’explique.

  • Il s’agit d’un objet binaire (une image, un PDF) dont la nature en soi n’intéresse pas notre Web Service (ni SOAP, ni CXF). Pour nous, ce sera un tableau d’octets.
  • Il est possible (quoique onéreux) de passer cet objet en base 64 au sein même de l’enveloppe SOAP. Mais ici, nous allons utiliser une optimisation standard, MTOM qui permet d’inclure directement le document dans une requête http multipart.
  • Il s’agit d’une optimisation, c’est-à-dire que CXF peut décider – ou pas – de la réaliser. Pour le développeur, c’est à peu près transparent, à condition bien sûr de ne pas chatouiller CXF de trop près.
  • Le souci c’est que si un certain nombre de choses de ne sont pas faites, l’optimisation n’est pas déclenchée et le document est passé en base 64 avec éventuellement des problèmes de taille de message SOAP (Tomcat est assez récalcitrant…).

L’interface  de notre WebService change un peu :

package com.objetdirect.mailer;

 

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

 

import javax.activation.DataHandler;

import javax.activation.DataSource;

import javax.xml.bind.annotation.XmlAccessType;

import javax.xml.bind.annotation.XmlAccessorType;

import javax.xml.bind.annotation.XmlMimeType;

import javax.xml.bind.annotation.XmlRootElement;

 

@XmlRootElement

@XmlAccessorType(XmlAccessType.FIELD)

publicclass Mail {

String from;

String to;

String topic;

String message;

@XmlMimeType(« application/octet-stream »)

DataHandler file;

public Mail(String from, String to, String topic, String message,

byte[] file)

{

super();

this.from = from;

this.to = to;

this.topic = topic;

this.message = message;

if (file!=null) {

setFile(file);

}

}

public void setFile(finalbyte[] content) {

DataSource ds = new DataSource() {

@Override

public OutputStream getOutputStream() throws IOException {

return new ByteArrayOutputStream();

}

@Override

public String getName() {

return « mydoc.pdf »;

}

@Override

public InputStream getInputStream() throws IOException {

return new ByteArrayInputStream(content);

}

@Override

public String getContentType() {

return « application/octet-stream »;

}

};

file = new DataHandler(ds);

}

 

public String getFrom() {

return from;

}

public String getTo() {

return to;

}

public String getTopic() {

return topic;

}

public String getMessage() {

return message;

}

}

Pour utiliser MTOM, CXF réclame trois actions :

  • définir la pièce jointe sous la forme d’un DataHandler,
  • annoter le champ incarnant la pièce jointe (de type DataHandler donc) par @XmlMimeType(« application/octet-stream »)
  • instruire l’objet client du WebService qu’il faut activer MTOM.

Les deux premières actions sont assurée par l’objet Mail ci-dessus. Jetons un coup d’œil sur l’instanciation du DataHandler. Volontairement, j’ai implémenté ici ce DataHandler de la façon la plus générique et donc la plus passe-partout. Ceci dit, java propose des classes de facilitation dans des cas courant comme les ressources Web ou les fichiers. Si votre besoin correspond au mieux, c’est-à-dire si votre document vous arrive sous la forme d’un tableau d’octets, la solution présentée ci-dessus devrait correspondre exactement à votre besoin.

Dernière action, donc : déclarer au client que l’on veut activer MTOM. Cela se fait aussi au niveau de la méthode getClient() dont la version finale est la suivante :

package com.objetdirect.mailer;

 

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

 

import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import javax.xml.ws.BindingProvider;

import javax.xml.ws.soap.SOAPBinding;

 

import org.apache.cxf.interceptor.LoggingInInterceptor;

import org.apache.cxf.interceptor.LoggingOutInterceptor;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import org.apache.ws.security.WSConstants;

import org.apache.ws.security.WSPasswordCallback;

import org.apache.ws.security.handler.WSHandlerConstants;

 

publicclass MailerClient {

publicstatic Mailer getClient(

String url,

String username,

final String password)

{

JaxWsProxyFactoryBean
factory =
new JaxWsProxyFactoryBean();

 

Map<String,Object> outProps = new HashMap<String,Object>();

outProps.put(WSHandlerConstants.ACTIONWSHandlerConstants.USERNAME_TOKEN);

outProps.put(WSHandlerConstants.USER, username);

outProps.put(WSHandlerConstants.PASSWORD_TYPEWSConstants.PW_TEXT);

class ClientPasswordCallback implements CallbackHandler {


public ClientPasswordCallback() {


}


publicvoid handle(Callback[] callbacks) throws IOException,

UnsupportedCallbackException {

WSPasswordCallback
pc = (WSPasswordCallback) callbacks[0];

pc.setPassword(password);


}

}

outProps.put(WSHandlerConstants.PW_CALLBACK_REFnew ClientPasswordCallback());

WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);

wssOut.setAllowMTOM(true);

factory.getInInterceptors().add(new LoggingInInterceptor());

factory.getOutInterceptors().add(new LoggingOutInterceptor());

factory.getOutInterceptors().add(wssOut);

factory.setServiceClass(Mailer.class);

factory.setAddress(url);

Mailer client = (Mailer) factory.create();

((SOAPBinding)((BindingProvider)client).getBinding()).setMTOMEnabled(true);

return client;

}

}

Il faut rajouter deux lignes :

  • une première au niveau de l’intercepteur WS-Security qui indique à ce dernier qu’il doit prendre en compte des pièces jointes
  • une seconde, qui utilisant le client créé , va rechercher l’objet SOAPBinding attaché et indique à cet objet que MTOM est acceptée

Il faut que ces deux appels soient effectifs pour que CXF daigne utiliser MTOM avec WS-Security ! Sinon, MTOM est silencieusement ignoré. Autant le second appel est (relativement) bien documenté, autant le premier est un secret jalousement gardé (ai-je commis une trahison ? la NSA va-t-elle s’intéresser à mon cas ?). Si vous l’oubliez, MTOM est silencieusement ignoré et aucun message de log, aucune documentation, aucune googlisation (jusque-là) ne vous renseigne sur la raison…

Thats’s all folks.

Categories: Java EE, Web Services - SOA Tags:
  1. Pas encore de commentaire
  1. Pas encore de trackbacks


un + = 4