web-dev-qa-db-fra.com

Ajouter SOAP objet d'en-tête en utilisant JAX-WS pur

J'essaie d'implémenter un client de service Web simple pour API Paypal Express Checkout en utilisant JAX WS . API Paypal Express Checkout fournit [~ # ~] wsdl [~ # ~] fichier, à partir duquel j'ai pu pour générer Java classes en utilisant wsdl2Java de CXF utilitaire.

Pour des raisons d'authentification, il faut ajouter un en-tête SOAP à chaque demande. Cet en-tête est assez simple et devrait ressembler à ceci: https://cms.Paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECSOAPAPIBasics#id09C3I0CF0O6

Généré à partir de [~ # ~] wsdl [~ # ~] les classes incluent ebay.apis.eblbasecomponents.CustomSecurityHeaderType classe qui représente l'en-tête que je dois ajouter à chaque demande.

La question est donc: comment puis-je ajouter une instance créée manuellement de la classe CustomSecurityHeaderType à SOAP en-tête de la demande en tenant compte des éléments suivants conditions:

  1. Je ne suis pas très désireux d'utiliser des classes du package com.Sun. * comme mentionné dans la réponse ici: JAX-WS - Adding = SOAP Headers (principalement en raison de possibles problèmes de portabilité entre différents JDK)
  2. Je ne veux pas marshaler manuellement cet objet dans les instances imbriquées javax.xml.soap.SOAPElement comme mentionné dans répondez ici: Comment ajouter un en-tête SOAP en utilisant Java JAX-WS
19

Donc, il semble que j'ai trouvé une réponse possible en combinant JAX-WS & [~ # ~] jaxb [~ # ~] réponses liées de [~ # ~] donc [~ # ~] (j'apprécierais vraiment que quelqu'un expérimenté dans ces technologies puisse vérifier si ce qui suit est correct):

La chose évidente pour moi est d'y ajouter SOAP gestionnaire de messages et de modifier l'en-tête de SOAPMessage ):

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.soap.SOAPHeader;
import ebay.api.paypalapi.ObjectFactory; // class generated by wsdl2Java

// following class is generated by wsdl2Java utility Service class
final PayPalAPIInterfaceService payPalService = new PayPalAPIInterfaceService();
final PayPalAPIAAInterface expressCheckoutPort = payPalService.getPayPalAPIAA();
final Binding binding = ((BindingProvider) expressCheckoutPort).getBinding();
List<Handler> handlersList = new ArrayList<Handler>();

// now, adding instance of Handler to handlersList which should do our job:
// creating header instance
final CustomSecurityHeaderType headerObj = new CustomSecurityHeaderType();
final UserIdPasswordType credentials = new UserIdPasswordType();
credentials.setUsername("username");
credentials.setPassword("password");
credentials.setSignature("signature");
headerObj.setCredentials(credentials);

// bookmark #1 - please read explanation after code
final ObjectFactory objectFactory = new ObjectFactory();
// creating JAXBElement from headerObj
final JAXBElement<CustomSecurityHeaderType> requesterCredentials = objectFactory.createRequesterCredentials(headerObj);

handlersList.add(new SOAPHandler<SOAPMessageContext>() {
    @Override
    public boolean handleMessage(final SOAPMessageContext context) {        
        try {
            // checking whether handled message is outbound one as per Martin Strauss answer
            final Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
            if (outbound != null && outbound) {
                // obtaining marshaller which should marshal instance to xml
                final Marshaller marshaller = JAXBContext.newInstance(CustomSecurityHeaderType.class).createMarshaller();
                // adding header because otherwise it's null
                final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
                // marshalling instance (appending) to SOAP header's xml node
                marshaller.marshal(requesterCredentials, soapHeader);
            }
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    // ... default implementations of other methods go here

});

// as per Jean-Bernard Pellerin's comment setting handlerChain list here, after all handlers were added to list
binding.setHandlerChain(handlersList);

Explication de bookmark # 1: on ne doit pas marshaler l'objet d'en-tête lui-même, mais JAXBElement représentant cet objet, car sinon on obtiendra une exception. Il faut utiliser l'une des classes ObjectFactory qui sont générées à partir de [~ # ~] wsdl [~ # ~] pour créer les instances nécessaires JAXBElement à partir des objets originaux. (Merci @skaffman pour la réponse: Aucun @XmlRootElement généré par JAXB )

Il faut également se référer à Martin Straus réponse qui étend celle-ci

32

Cette solution fonctionne très bien, mais il y a un hic. Il génère cette erreur lorsque le message entrant est traité:

dic 19, 2012 7:00:55 PM com.Sun.xml.messaging.saaj.soap.impl.EnvelopeImpl addHeader
SEVERE: SAAJ0120: no se puede agregar una cabecera si ya hay una
Exception in thread "main" javax.xml.ws.WebServiceException: Java.lang.RuntimeException: com.Sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at com.Sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.Java:167)
    at com.Sun.xml.ws.handler.HandlerTube.processResponse(HandlerTube.Java:174)
    at com.Sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.Java:1074)
    at com.Sun.xml.ws.api.pipe.Fiber._doRun(Fiber.Java:979)
    at com.Sun.xml.ws.api.pipe.Fiber.doRun(Fiber.Java:950)
    at com.Sun.xml.ws.api.pipe.Fiber.runSync(Fiber.Java:825)
    at com.Sun.xml.ws.client.Stub.process(Stub.Java:443)
    at com.Sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.Java:174)
    at com.Sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.Java:119)
    at com.Sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.Java:102)
    at com.Sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.Java:154)
    at $Proxy38.wsRdyCrearTicketDA(Unknown Source)
    at ar.com.fit.fides.remedy.api.ws.ServicioCreacionTickets.crearTicket(ServicioCreacionTickets.Java:55)
    at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.crearTicket(ConectorRemedyWS.Java:43)
    at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.main(ConectorRemedyWS.Java:90)
Caused by: Java.lang.RuntimeException: com.Sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.Java:50)
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.Java:23)
    at com.Sun.xml.ws.handler.HandlerProcessor.callHandleMessageReverse(HandlerProcessor.Java:341)
    at com.Sun.xml.ws.handler.HandlerProcessor.callHandlersResponse(HandlerProcessor.Java:214)
    at com.Sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.Java:161)
    ... 14 more
Caused by: com.Sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
    at com.Sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.Java:128)
    at com.Sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.Java:108)
    at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.Java:45)

Ainsi, la solution consiste à vérifier si le message est traité si le message sortant, comme ceci:

public boolean handleMessage(SOAPMessageContext context) {
    try {
        Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
        if (outbound != null && outbound) {
            // obtaining marshaller which should marshal instance to xml
            final Marshaller marshaller = JAXBContext.newInstance(AuthenticationInfo.class).createMarshaller();
            // adding header because otherwise it's null
            final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
            // marshalling instance (appending) to SOAP header's xml node
            marshaller.marshal(info, soapHeader);
        }
    } catch (final Exception e) {
        throw new RuntimeException(e);
    }
    return true;
}
11
Martín Straus

J'ai créé une méthode d'exposition de service Web avec un utilisateur et un mot de passe params comme en-tête comme ceci:

@WebService(serviceName="authentication")
public class WSAuthentication {
   String name = null;
   String password = null;

   public WSAuthentication() {
       super();
   }

   public WSAuthentication(String name, String password) {
       this.name = name;
       this.password = password;
   }

   private static String getData(WSAuthentication sec) {
       System.out.println("********************* AUTHENTICATION ********************" + "\n" + 
       "**********USER: " + sec.name + "\n" + 
       "******PASSWORD: " + sec.password + "\n" + 
       "******************************** AUTHENTICATION ****************************");
       return sec.name + " -- " + sec.password;
   }

   @WebMethod(operationName="security", action="authenticate")
   @WebResult(name="answer")
   public String security(@WebParam(header=true, mode=Mode.IN, name="user") String user, @WebParam(header=true, mode=Mode.IN, name="password") String password) {
        WSAuthentication secure = new WSAuthentication(user, password);
        return getData(secure);
     }
}

Essayez de le compiler et de tester généré à partir de la classe WSDL. J'espère que ça aide.

5
hekomobile

J'ai trouvé cette réponse:

JAX-WS - Ajout de SOAP Headers

Fondamentalement, vous ajoutez -XadditionalHeaders aux options du compilateur et les objets dans les en-têtes apparaissent également dans votre code généré en tant que paramètres de la méthode.

1
Radboud

Si vous utilisez maven et que le plug-in jaxws-maven-plugin vous n'avez qu'à ajouter le drapeau xadditionalHeaders à true et le client sera généré avec les méthodes qui ont les en-têtes en entrée.

https://jax-ws-commons.Java.net/jaxws-maven-plugin/wsimport-mojo.html#xadditionalHeaders

1
Eduardo Dennis