Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add SOAP header object using pure JAX-WS

Tags:

I'm trying to implement simple web service client for PayPal Express Checkout API using JAX WS. PayPal Express Checkout API provides WSDL file, from which I was able to generate Java classes using CXF's wsdl2java utility.

From authentication reasons, it demands adding SOAP Header to each request. This header is quite simple and should look like here: https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECSOAPAPIBasics#id09C3I0CF0O6

Generated from WSDL classes include ebay.apis.eblbasecomponents.CustomSecurityHeaderType class which represents header which I need to add to each request.

So the question is: how can I add manually created instance of CustomSecurityHeaderType class to SOAP request's header taking into account following conditions:

  1. I'm not very eager to use classes from com.sun.* package as mentioned in answer here: JAX-WS - Adding SOAP Headers (mainly because of possible portability issues between different JDK's)
  2. I don't want to manually marshal that object into nested javax.xml.soap.SOAPElement instances as mentioned in answer here: How do I add a SOAP Header using Java JAX-WS
like image 881
Yuriy Nakonechnyy Avatar asked May 18 '12 14:05

Yuriy Nakonechnyy


People also ask

How do I add a SOAP Action header in Java?

wsimport.exe put the soap action in the "Content-Type" http header, the the service says the Action header is missing in the SOAP message. The WSDL specifies the Action as an attribute of the <operation ..> element inside the <binding ..>

What is mustUnderstand attribute in SOAP header?

The other attribute that must be added only to a SOAPHeaderElement object is mustUnderstand. This attribute says whether or not the recipient (indicated by the actor attribute) is required to process a header entry.


2 Answers

So, it looks like I've found possible answer while combining JAX-WS & JAXB related answers from SO (I would really appreciate if somebody experienced in these technologies can check whether following is correct):

The obvious thing for me is to add SOAP message handler and alter header of SOAPMessage instance in it:

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); 

Explanation of bookmark #1: one should marshal not the header object itself, but JAXBElement representing that object, because otherwise one will get an exception. One should use one of ObjectFactory classes which are generated from WSDL for creating needed JAXBElement instances from original objects. (Thanks @skaffman for answer: No @XmlRootElement generated by JAXB )

One should also refer to Martin Straus answer which extends this one

like image 112
Yuriy Nakonechnyy Avatar answered Oct 04 '22 12:10

Yuriy Nakonechnyy


This solution works great, but there's a catch. It generates this error when the inbound message is processed:

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) 

So, the solution is to check whether the message being handled if the outbound message, like this:

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; } 
like image 25
Martín Straus Avatar answered Oct 04 '22 13:10

Martín Straus