Add SOAP header object using pure JAX-WS
Asked Answered
G

5

21

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
Ge answered 18/5, 2012 at 14:35 Comment(0)
G
34

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

Ge answered 19/5, 2012 at 15:9 Comment(9)
Managed to invoke PayPal API's web service method and obtain successful response, hence accepting this answer.Ge
I think you should include Martin Straus's outbound check as well to your code.Translatable
From user: stackoverflow.com/users/2425676/kai-witte I believe the list of handlers must be created and filled first, THEN set as the handler chain. SOAPHandlers that are added after calling sethandlerChain won't register.Peaslee
So you have not managed to find any other way than just using the MessageHandler to set SOAPHeaders? Do you know the reason why this cannot be done the same was a by using com.sun.* packages?Pepi
I think this can be done using classes from com.sun.* packages. The only problem with them is that these classes will be present only in Sun's (Oracle) JVM and not present in other JVM implementations, so code which uses com.sun.* packages will not run on other JVMs.Ge
Hey @Yuva I am also facing same issue here for paypal. I have also creatd soap client using wsdl, and trying to add request header as shown in your answer, but not getting some files. This is my post https://mcmap.net/q/659072/-use-paypal-with-javaLineation
Please let me know if you can guide me ? ObjectFactory is not created in my web service client, I have used simple web service client option in eclipse.Also tried wsdl to java(axis generator) but getting some fileNotFound excpetion.Lineation
thank you a lot, this solution saved me a lot of time. does anyone know why the header is not automatically generated?Peyton
@Peyton I'm glad that it helped you! And I don't have the slightest idea why header is not automatically generated :)Ge
U
11

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;
}
Upbraid answered 19/12, 2012 at 22:6 Comment(2)
Hello Martin, I am new in web services, please give me a hint what is inbound and outbound messages ?Aeolis
Hello Martin, Following is my problem when i try to add jaxb element in SOAP header. #24321175Aeolis
D
5

I created a web service exposing method with params user and password as header like this:

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

Try compiling it and testing generated from WSDL class. I hope this helps.

Disrupt answered 20/5, 2012 at 15:24 Comment(2)
Thanks a lot - I will check how my solution works with your sample web service implementation and report back here.Ge
Concerning WSAuthentication class - I've managed to create and run web service out of it, and send requests to it. However, I ran into the problem similar to this one: #7381261 and could solve it. Nevertheless, it was nice experience, thus +1 :)Ge
G
1

I found this answer:

JAX-WS - Adding SOAP Headers

Basically you add -XadditionalHeaders to the compiler options and objects in the headers also appear in your generated code as parameters of the method.

Gustafson answered 11/1, 2016 at 14:4 Comment(1)
That's interesting approach and it looks quite graceful, however I'm failing to understand how to do this exactly in this case, could you please elaborate more?Ge
P
1

If you are using maven, and the jaxws-maven-plugin all you have to do is add the xadditionalHeaders flag to true and the client will be generated with the methods that have the headers as input.

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

Paperback answered 11/3, 2016 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.