web-dev-qa-db-fra.com

Personnalisation du préfixe JAX-WS d'une réponse SOAP

Objectif

J'implémente un service Web pour une interface assez ancienne (mais malheureusement immuable). J'ai un problème où le client qui appelle mon service attend un certain espace de noms dans la réponse SOAP et j'ai du mal à le changer pour qu'il corresponde.

Considérant un exemple de bonjour, je veux ceci:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <ns2:helloResponse xmlns:ns2="http://test/">
         <return>Hello Catchwa!</return>
      </ns2:helloResponse>
   </S:Body>
</S:Envelope>

ressembler à ceci:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <customns:helloResponse xmlns:customns="http://test/">
         <return>Hello Catchwa!</return>
      </customns:helloResponse>
   </S:Body>
</S:Envelope>

J'ai trouvé quelque chose de similaire à ce que j'essaie de faire ici mais j'ai du mal à obtenir un code similaire pour qu'il s'exécute correctement. (Je voudrais rester avec Metro et ne pas avoir à changer de cxf ou d'axe)


Exécution

Mon implémentation de JAXBContextFactory qui renvoie un JAXBRIContext ressemble à ceci:

import com.Sun.xml.bind.api.JAXBRIContext;
import com.Sun.xml.bind.api.TypeReference;
import com.Sun.xml.ws.api.model.SEIModel;
import com.Sun.xml.ws.developer.JAXBContextFactory;
import Java.util.ArrayList;
import Java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;

public class HelloJaxbContext implements JAXBContextFactory
{
  @Override
  public JAXBRIContext createJAXBContext(SEIModel seim, List<Class> classesToBind, List<TypeReference> typeReferences) throws JAXBException {
    List<Class> classList = new ArrayList<Class>();
    classList.addAll(classesToBind);

    List<TypeReference> refList = new ArrayList<TypeReference>();
    for (TypeReference tr : typeReferences) {
        refList.add(new TypeReference(new QName(tr.tagName.getNamespaceURI(), tr.tagName.getLocalPart(), "customns"), tr.type, tr.annotations));
    }
    return JAXBRIContext.newInstance(classList.toArray(new Class[classList.size()]), refList, null, seim.getTargetNamespace(), false, null);
  }  
}

Un code de test pour le service Web est simplement:

import com.Sun.xml.ws.developer.UsesJAXBContext;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;

@WebService(serviceName = "Hello")
@UsesJAXBContext(value = HelloJaxbContext.class)
public class Hello
{
  @WebMethod(operationName = "hello")
  public String hello(@WebParam(name = "name") String txt)
  {
    return "Hello " + txt + "!";
  }
}

Problèmes

Dans Tomcat 7.0.32 et Glassfish 3.1.2 utilisant jaxws-rt 2.2.7 (de Maven), le code ci-dessus n'affecte pas la sortie de mon service Web (le préfixe d'espace de noms est toujours "ns2").

19
Catchwa

Si vous avez démarré à partir du WSDL de l'ancien service et généré toutes les différentes classes de wrapper de demande et de réponse annotées JAXB en utilisant wsimport, alors dans le package généré, vous devriez trouver un package-info.Java Tel que

@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/")
package com.example.test;

JAXB fournit un mécanisme vous permettant de suggérer des mappages de préfixes sur l'annotation @XmlSchema, Vous pouvez donc essayer de modifier package-info.Java Pour lire

@javax.xml.bind.annotation.XmlSchema(namespace = "http://test/",
   xmlns = { 
      @javax.xml.bind.annotation.XmlNs(prefix = "customns", 
         namespaceURI="http://test/")
   }
)
package com.example.test;

et voyez si cela fait une différence dans les messages générés. Cela a également l'avantage d'être purement conforme aux spécifications JAXB (c'est-à-dire qu'il ne dépend pas de la fabrique de contexte personnalisé spécifique au RI).

Si vous devez relancer wsimport, vous pouvez l'empêcher d'écraser votre package-info Modifié en passant l'option -npa À xjc (cela lui dit de ne pas générer un package-info.Java mais à la place pour mettre tous les paramètres namespace nécessaires sur les annotations au niveau de la classe). La façon exacte dont vous procédez dépend de la façon dont vous exécutez wsimport.

ligne de commande:

wsimport -B-npa ....

Fourmi:

<wsimport wsdl="..." destdir="..." .... >
  <xjcarg value="-npa" />
</wsimport>

Maven:

<plugin>
  <groupId>org.jvnet.jax-ws-commons</groupId>
  <artifactId>jaxws-maven-plugin</artifactId>
  <version>2.2</version>
  <executions>
    <execution>
      <goals>
        <goal>wsimport</goal>
      </goals>
      <configuration>
        <xjcArgs>
          <xjcArg>-npa</xjcArg>
        </xjcArgs>
      </configuration>
    </execution>
  </executions>
</plugin>
24
Ian Roberts

La manière recommandée/standard de réaliser ce que vous essayez de réaliser est d'utiliser un SOAPMessage Handler . Ils sont analogues à Java filtres d'application Web (qui en théorie pourraient également fonctionner ici) car ils sont utilisés pour implémenter le modèle de chaîne de responsabilité . Par exemple, dans votre cas, vous pouvez avoir ceci:

import Java.util.Set;
import Java.util.logging.Level;
import Java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;


public class SOAPBodyHandler implements SOAPHandler<SOAPMessageContext> {

static final String DESIRED_NS_PREFIX = "customns";
static final String DESIRED_NS_URI = "http://test/";
static final String UNWANTED_NS_PREFIX = "ns";

@Override
public Set<QName> getHeaders() {
   //do nothing
   return null;
}

@Override
public boolean handleMessage(SOAPMessageContext context) {
    if ((boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { //Check here that the message being intercepted is an outbound message from your service, otherwise ignore.
        try {
            SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //get the SOAP Message envelope
            SOAPBody body = msg.getBody();
            body.removeNamespaceDeclaration(UNWANTED_NS_PREFIX);
            body.addNamespaceDeclaration(DESIRED_NS_PREFIX, DESIRED_NS_URI); 
        } catch (SOAPException ex) {
            Logger.getLogger(SOAPBodyHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return true; //indicates to the context to proceed with (normal)message processing
}

@Override
public boolean handleFault(SOAPMessageContext context) {
      //do nothing
   return null;
}

@Override
public void close(MessageContext context) {
      //do nothing

}

}

Sur votre déclaration de classe de Service Implementation Bean, ajoutez

  @HandlerChain(file = "handler-chain.xml")

L'annotation ci-dessus est une référence au fichier de configuration qui permet réellement à votre gestionnaire de démarrer. Le fichier de configuration ressemblera à ceci

  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
     <javaee:handler-chains xmlns:javaee="http://Java.Sun.com/xml/ns/javaee" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <javaee:handler-chain>
           <javaee:handler>
              <javaee:handler-class>your.handler.FQN.here</javaee:handler-class>
           </javaee:handler>
        </javaee:handler-chain>
     </javaee:handler-chains> 

Essayez ceci à la maison. Ce code spécifique n'a PAS été testé

4
kolossus

Dans l'implémentation de référence, je l'ai fait pour enfin le faire fonctionner. Voir schemaLocation ignoré lors du marshaling des classes JAXB à l'aide de Metro

0
Zagrev