Reverse engineering: How to generate SOAP Request XML in the backend?

Viewed 617

I have the following classes:

WS Interface:

package com.mypackage;

import javax.ejb.Remote;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

@Remote
@SOAPBinding(style = Style.DOCUMENT)
@WebService(name = "MathService", targetNamespace = "http://mypackage.com/")
public interface MathServiceWS {

    @WebResult(name = "result", targetNamespace = "http://mypackage.com/")
    @RequestWrapper(localName = "addRequest", className = "AddRequest", targetNamespace = "http://mypackage.com/")
    @WebMethod(action = "http://mypackage.com/add", operationName = "add")
    @ResponseWrapper(localName = "addResponse", className = "AddResponse", targetNamespace = "http://mypackage.com/")
    Long add(@WebParam(name = "add", targetNamespace = "http://mypackage.com/") AddBean add);
}

WS Implementation:

package com.mypackage;

import javax.ejb.Stateless;
import javax.jws.WebService;

@Stateless(mappedName = "MathService")
@WebService(serviceName = "MathService", endpointInterface = "com.mypackage.MathServiceWS", portName = "MathServicePort", targetNamespace = "http://mypackage.com/")
public class MathService implements MathServiceWS {

    @Override
    public Long add(AddBean add) {
        Long first = new Long(add.getFirst().intValue());
        Long second = new Long(add.getSecond().intValue());
        return Long.valueOf(Math.addExact(first.longValue(), second.longValue()));
    }
}

The bean:

package com.mypackage;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
    name = "Add",
    namespace = "http://mypackage.com/",
    propOrder = {
        "first",
        "second"
    }
)
public class AddBean implements Serializable {

    private static final long serialVersionUID = -7727938355039425419L;

    @XmlElement(required = true)
    private Integer first;

    @XmlElement(required = true)
    private Integer second;

    public AddBean() {

    }

    public Integer getFirst() {
        return first;
    }

    public void setFirst(Integer first) {
        this.first = first;
    }

    public Integer getSecond() {
        return second;
    }

    public void setSecond(Integer second) {
        this.second = second;
    }
}

After deploying this WS, when I'm adding the WSDL in SoapUI, the add method request is as follows after giving the user input:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:myp="http://mypackage.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <myp:addRequest>
         <myp:add>
            <first>1</first>
            <second>2</second>
         </myp:add>
      </myp:addRequest>
   </soapenv:Body>
</soapenv:Envelope>

Now I want to have the above SOAP request XML in my com.mypackage.MathService.add(AddBean) method with the given user input.

  • Using JAXB on com.mypackage.AddBean only generates partial request
  • The WebService Handlers is not useful to fulfill my requirement

Any pointer would be very helpful.

2 Answers

You may create an custom SOAPHandler object and can read the request payload and set it to SOAPMessageContext via custom property. Make sure you set the scope as application.

In your service class, inject the javax.xml.ws.WebServiceContext using @javax.annotation.Resource and access payload set via your custom property.

For example:
1. Create Handler and register it.

public class PopulateSOAPMessagePayloadHandler implements SOAPHandler<SOAPMessageContext> {
    public static final String SOAP_MESSAGE_PAYLOAD = "__soap_message_payload";

    @Override
    public boolean handleMessage(SOAPMessageContext smc) {
        Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (!outboundProperty.booleanValue()) {
            // for incoming:
            ByteArrayOutputStream bout = new ByteArrayOutputStream(1024);
            try {
                smc.getMessage().writeTo(bout);
                String payload = bout.toString(StandardCharsets.UTF_8.name());

                smc.put(SOAP_MESSAGE_PAYLOAD, payload);  //Set payload
                smc.setScope(SOAP_MESSAGE_PAYLOAD, MessageContext.Scope.APPLICATION);  //make it application scope

            } catch (SOAPException | IOException e) {
                e.printStackTrace();
                // handle exception if needed
                throw new WebServiceException(e);
            }

        }
        return true;
    }
 // Other method (no-op) omitted 
}


2. Get the payload

public class MathService implements MathServiceWS {
    @Resource
    private WebServiceContext context;

    @Override
    public Long add(AddBean add) {
        String payload = (String) context.getMessageContext().get(SOAP_MESSAGE_PAYLOAD);

        Long first = new Long(add.getFirst().intValue());
        Long second = new Long(add.getSecond().intValue());
        return Long.valueOf(Math.addExact(first.longValue(), second.longValue()));
    }
}

Hope it helps.

You can get full control of the document easily. First lets setup the bean:

@XmlRootElement(name="addRequest")
@XmlAccessorType(XmlAccessType.FIELD) //Probably you don't need this line. it is by default field accessible. 
public class AddBean implements Serializable {

private static final long serialVersionUID = -7727938355039425419L;

@XmlElement(name="first",required = true) //you don't need name attribute as field is already exactly the same as soap element
private Integer first;

@XmlElement(name="second",required = true) //you don't need name attribute as field is already exactly the same as soap element
private Integer second;

public AddBean() { }
//Getters and Setters
}

Now, I think this is the part you are looking for. To add custom namespace declarations and set prefix etc. If you are using org.springframework.ws.client.core.support.WebServiceGatewaySupport.getWebServiceTemplate to make SOAP request, then you can do the following:

public class WSCastorClient extends WebServiceGatewaySupport {

public CustomResponseObject callWebService(Addbean add) {
WebServiceTemplate wst = getWebServiceTemplate();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.afterPropertiesSet();
wst.setMarshaller(marshaller);
wst.setUnmarshaller(marshaller);
wst.afterPropertiesSet();
CustomResponseObject response = (CustomResponseObject) 
wst.marshallSendAndReceive(add, new 
WebServiceMessageCallback() {

    public void doWithMessage(WebServiceMessage message) { 
        SaajSoapMessage saajSoapMessage = (SaajSoapMessage) message;
        SOAPMesage soapMEssage = saajSoapMessage.getSaajMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
        SOAPHeader head = soapMessage.getSOAPHeader();
        SOAPBody soapBody = soapMessage.getSOAPBody();
        //Now you have full control of the soap header, body, envelope. You can add any namespace declaration, prefix, add header element, etc. You can add remove whatever you want. 
        soapEnvelope.removeNamespaceDeclaration(soapEnvelope.getPrefix()); //clear whatever namespace is there
        soapEnvelope.addNamespaceDeclaration("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
        soapEnvelope.addNamespaceDeclaration("myp", "http://mypackage.com/");
        soapEnvelope.setPrefix("soapenv");
        soapHeader.setPrefix("soapenv");
        soapBody.setPrefix("soapenv");

        Document doc = saajSoapMessage.getDocument();
        StringWriter sw = new StringWriter();

        Transformer transformer  = TransformerFactory.newInstance().newTransformer();
        transformer.transform(new DOMSource(doc), new StreamResult(sw));
    }       
    });
return response;
}
//close off other brackets if I forgot any. 
Related