SOAP Service Consumption made easy

Martien van den Akker
virtualsciences
Published in
7 min readJan 30, 2021

My previous story was about creating a SOAP Service using CXF in Spring Boot. The idea was to try to create a service using Spring Boot, Camel and CXF and have it as declarative as possible. Providing a service as a SOAP web service is one thing, but similarly you often would need to consume a SOAP service. Let’s investigate what you would need to do for that.

Start off

Decorative Soaps
Decorative Soaps (from: https://en.wikipedia.org/wiki/File:Decorative_Soaps.jpg)

To get you started, I created a SOAP 2 SOAP service and published it to my fusedemos project on GitHub. The project uses the same WSDL to expose a web service, as the AnimalOrderSOAP Service, that I described in the previous story. The declaration of the SOAP interface as an Exposed Service, is identical. In fact, the AnimalOrderSOAP service is a stripped-down version of the SOAPtoSOAP service. I created it because, in the first implementation, I couldn’t get the service working as intended. I had to process the SOAP Envelope in a Java way for the response and requests when calling services. When I got that right, I adapted the SOAPtoSOAP service according to my insights.

Functional Overview

The SOAP-to-SOAP service takes in an AnimalOrder request, to buy one or more animals from a zoo. When I think about it, maybe not so much of a politically correct example. The Order looks like this:

An order has one or more order lines, each of which is about a number of Animals. For each order line, the service will call an AnimalOrderQuote service. An example request looks like this:

You see here that the articleId in the order line (bad idea to describe an animal as an article; the company has probably implemented a standard ERP system) is copied to the quote request as an animalId, and the quantity is copied.

The quote service will return a response with a price quote and the number of animals available:

In the SOAP-to-SOAP service this response is transformed to a AnimalOrderResponse order line, using AnimalQuoteResponseToAnimalOrderResponse.xsl. That XSL checks if the number of available animals is higher than the requested number. Based on this condition the requested or available number is returned and the total price is calculated accordingly.

Each result is aggregated into the response. So, it contains an example of the split-aggregate integration pattern, based on JAXB unmarshalling and marshaling. Might be interesting to write some lines about that, but I leave that for a potential other article.

Implement a consumer for the quote service

As with the AnimalOrder WSDL, I created the AnimalQuoteService.wsdl with JDeveloper and provided it in the project on GitHub.

You will need to add a cxfEndpoint based on the WSDL. This can be done in the camel-context.xml, but as explained in my previous story, I prefer to use a separate file, that I named camel-cxf.xml. It’s all similar to the AnimalOrderSOAP service:

In this example, I used a standard namespace declaration with tns as namespace reference. Since the scope is just this snippet, you don’t have to use a globally unique reference. Having something like tns is handy because you can just copy and paste the snippet when you need to add other services.
The only attribute that references a property is the address. For referenced services, this is especially useful, since for different target environments you would obviously have separate service implementations. You can find the actual value in the application-${environment}.yaml file, specific to the target environment. Like application-dev.yml:

Notice, by the way, the entry element as a cxf:properties child, where the dataFormat is declared as a PAYLOAD. Together with all the instructions in the previous story, this will allow you to provide just the AnimalQuoteRequest XML message as a value of the $body variable. CXF will create a SOAP Message for you.

AnimalOrderQuote Service implementation

For each service you implement or consume with CXF you will need to create a web service Java class. And, thus we have an AnimalQuoteServiceProvider.java, class. The class is not very complicated and seemingly unneccesary. However, it’s important to have a method with the exact same name as the operation you want to use. In this case the method is named “quote”. It can return null, because to the best of my knowledge, nothing is actually done with it. But, it apparently needs it.

Calling the service is easy now

The following snippet shows the route that is responsible for the calling of the AnimalQuote service:

ProcessOrderLine route

The actual invocation is done in line 24, which is a pretty simple to, right? For the rest, there is little more to it than making sure that the previously described AnimalQuoteRequest is in the $body variable. In line 13, you see the call to the AnimalOrderRequestToAnimalQuoteRequest.xsl that transforms the AnimalOrder request to the Quote request. To do so, it needs to “know” for which animalId the AnimalQuote request is to be made. The Camel XSLT component can work with parameters supplied as message headers. And so, in lines 4–9, the header variables articleId and quantity are set (sharp readers will notice that the quantity header isn’t really needed).

In lines 10–12 the body is reset to the AnimalOrder Request as an input to the XSLT. Notice that here, the body is set from an exchange property. I’ll get to that in a later remark below.

In the lines 14–22, you see that three headers are set: operationName, operationNameSpace, SOAPAction. The first of those you could set in the to-endpoint-uri as well:

To Advanced Producer Options

The SOAP-to-SOAP service is initiated through a SOAP-CXF endpoint. Then, upon invocation, these header variables are already set, so you would have to override those initial values. The operationName is quote in this case, the operationNamespace is just the target namespace from the WSDL.

In some cases, the SOAPAction can be emptied, but you have to explicitly do so. What I used to do, is to import the WSDL in SoapUI, create a mock service out of it, and then call that with a default request. Take a look at the RAW tab of you’re request in the mock message or SOAP Request:

SOAPAction

The SOAPAction can also be found in the WSDL as well, under the binding:

After the service invocation in line 24, the Quote-response is transformed to an AnimalOrderResponse (line 26), unmarshalled (line 28-30) and the particular order line set into an exchange property (line 31–33) to be processed in the AggregationStrategy.

Run and test

You can build and run the service using the maven goal spring-boot:run, supplying the target environment as a spring.profiles.act parameter:

Maven goal spring-boot:run

Then in the same SoapUI project, I have a test case you can use to execute the service, providing 2 mock response test steps:

SoapUI AnimalOrder-OK TestCase

You can run it using the play button in the button bar of the test case. Double click on the test steps to see the actual messages getting to and fro.

Exchange properties or message headers?

Above I explained that in lines 4–9 I set the quantity and articleId values as a message header variable, where as for the AnimalOrderRequest (line 10–12) and the AnimalOrder response order line (line 31–33) I used properties.

This is quite important because CXF uses every message header to send them as HTTP headers:

HTTP Headers from Message headers

For these particular attributes, it’s no big deal. But, if I would use message headers to backup message objects then these too would be passed as an HTTP header. And that would flood the HTTP session, causing weird exceptions. I ran into that before, and one way to solve it was to unmarshal the message. That way the message was a Java object and all that was put in the header was the object-id. However, I found that not so neat either.

Besides that, you might unintentionally send saved sensitive values via the HTTP header. As shown, the quantity and articleId are in plain sight in the HTTP-message. These are maybe not so much privacy-sensitive values, but you could also reveal something about the working of your service or maybe even the back-end system and technology you used to implement your functionality. And thus, you should always check what is send over the line and make sure you remove message headers as soon as they’re not needed anymore. And use exchange properties where ever possible when you need to save/backup intermediate values or messages.

Conclusion

SOAP is really easy with CXF, and 100% declarative, besides the simple web service implementation class. There are two aspects I want to talk about:

  • How to set SOAP Headers?
  • How to do Exception handling, and get the exception as well as the runtime URI used to try to invoke the service?

But, because I have to build up writer’s muscles and keep my articles digestable, I leave those for a follow-up story.

--

--

Martien van den Akker
virtualsciences

Technology Architect at Oracle Netherlands. The views expressed on this blog are my own and do not necessarily reflect the views of Oracle