SOAP Transformation With Apache Camel Java DSL
Got a bunch of SOAP services staring you in the face? Fear not!
Join the DZone community and get the full member experience.
Join For FreeOne of the most common issues that we usually tackle with customers when modernizing legacy integrations is when they have a collection of legacy SOAP web services that don't integrate well enough with the modern RESTful-based clients.
In this article, we are going to use Apache Camel to build a REST endpoint that will translate REST calls to a SOAP envelope, get the response, and send it back to the client.
Apache Camel is a popular open-source integration framework with lots of features and a diverse ecosystem of plugins and libraries. That said, this implementation is a case of Message Transformation. To be more specific, this is a use case of the Normalizer pattern.
The code for this implementation is available on GitHub.
First Things First
If we're going to translate a SOAP service, first we need a SOAP service. For this example, we're going to use Learn Web Services' Temp Converter Service available at this link.
Maven Is Your Friend
Now, we don't actually need to write all the code that is needed to interact with a SOAP web service. We can use the Apache CXF CodeGen Plugin to create the source code from the WSDL. To do that, you just need to add the plugin to the pom.xml
x
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/TempConverter.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
And then run mvn generate-sources
. This will generate all the necessary boilerplate code under the target folder for Camel to interact with the SOAP endpoints.
To make use of autocompletion resources in IDEs, you can, in Eclipse, right-click -> Build Path -> Use as Source Folder.
Talk Is Cheap, Show Me the Code
We're going to use Spring Boot Starter to make life easier for us, so we just need to annotate to create our beans. The first bean that we will create is the Apache CXF Soap Endpoint config. This bean is going to be responsible for making the request and gathering the response.
x
"${endpoint.wsdl}") (
private String SOAP_URL;
(name = "cxfConvertTemp")
public CxfEndpoint buildCxfEndpoint() {
CxfEndpoint cxf = new CxfEndpoint();
cxf.setAddress(SOAP_URL);
cxf.setServiceClass(TempConverterEndpoint.class);
return cxf;
}
With this, we're ready to finally go into some Camel code.
Let's build a route that will consume a parameter exposed by a REST service.
xxxxxxxxxx
from("direct:celsius-to-fahrenheit")
.removeHeaders("CamelHttp*")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
CelsiusToFahrenheitRequest c = new CelsiusToFahrenheitRequest();
c.setTemperatureInCelsius(Double.valueOf(exchange.getIn().getHeader("num").toString()));
exchange.getIn().setBody(c);
}
})
.setHeader(CxfConstants.OPERATION_NAME, constant("{{endpoint.operation.celsius.to.fahrenheit}}"))
.setHeader(CxfConstants.OPERATION_NAMESPACE, constant("{{endpoint.namespace}}"))
.to("cxf:bean:cxfConvertTemp")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
MessageContentsList response = (MessageContentsList) exchange.getIn().getBody();
CelsiusToFahrenheitResponse r = (CelsiusToFahrenheitResponse) response.get(0);
exchange.getIn().setBody("Temp in Farenheit: "+r.getTemperatureInFahrenheit());
}
})
.to("mock:output");
Let's break down the code above.
We have two processors, one to create the complex object that will be sent as a SOAP envelope, and one to get the response envelope to an object.
On these two lines below, we get the temperature in Celsius to convert to Fahrenheit.
xxxxxxxxxx
CelsiusToFahrenheitRequest c = new CelsiusToFahrenheitRequest();
c.setTemperatureInCelsius(Double.valueOf(exchange.getIn().getHeader("num").toString()));
Now we set two headers — the namespace and the operation name. For more information about SOAP and its building blocks, I recommend this link from W3Schools.
xxxxxxxxxx
.setHeader(CxfConstants.OPERATION_NAME, constant("{{endpoint.operation.celsius.to.fahrenheit}}"))
.setHeader(CxfConstants.OPERATION_NAMESPACE, constant("{{endpoint.address}}"))
All these values are configured in the application.properties file.
xxxxxxxxxx
endpoint.operation.fahrenheit.to.celsius=FahrenheitToCelsius
endpoint.operation.celsius.to.fahrenheit=CelsiusToFahrenheit
logging.level.root=INFO
endpoint.namespace=http://learnwebservices.com/services/tempconverter
endpoint.wsdl=http://www.learnwebservices.com/services/tempconverter?wsdl
Now we send our request and gather the response:
x
to("cxf:bean:cxfConvertTemp")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
MessageContentsList response = (MessageContentsList) exchange.getIn().getBody();
CelsiusToFahrenheitResponse r = (CelsiusToFahrenheitResponse) response.get(0);
exchange.getIn().setBody("Temp in Farenheit: "+r.getTemperatureInFahrenheit());
}
})
.to("mock:output");
And it's....
...Not Done
You may be asking yourself "Ok, we have the Camel route, but how are we going to send the Celsius temperature that we want to convert?" Well, now enters the REST endpoint configuration.
x
restConfiguration()
.component("undertow").host("0.0.0.0").port(9090).bindingMode(RestBindingMode.auto).scheme("http")
.dataFormatProperty("prettyPrint", "true")
.contextPath("/")
.apiContextPath("/api-doc")
.apiProperty("api.title", "Camel2Soap")
.apiProperty("api.version", "1.0")
.apiProperty("host","")
.enableCORS(true);
rest("/convert")
.get("/celsius/to/fahrenheit/{num}")
.consumes("text/plain").produces("text/plain")
.description("Convert a temperature in Celsius to Fahrenheit")
.param().name("num").type(RestParamType.path).description("Temperature in Celsius").dataType("int").endParam()
.to("direct:celsius-to-fahrenheit")
.get("/fahrenheit/to/celsius/{num}")
.consumes("text/plain").produces("text/plain")
.description("Convert a temperature in Fahrenheit to Celsius")
.param().name("num").type(RestParamType.path).description("Temperature in Fahrenheit").dataType("int").endParam()
.to("direct:fahrenheit-to-celsius");
We add some Swagger documentation to our API as a best practice. And if you don't want to test using the command line, you can use the Swagger Inspector.
xxxxxxxxxx
[rmendes@rmendes ~]$ curl localhost:9090/convert/celsius/to/fahrenheit/50
Temp in Farenheit: 122.0
And we're finally done. With this article, I hope that some light can be shed on how to reuse some old SOAP-based web services without rewriting them. And with this Apache Camel approach, we can always apply more patterns to maybe enrich or send the information to multiple destinations, for example.
Opinions expressed by DZone contributors are their own.
Comments