Complex SOAP payloads with JAXB
Our introductory guides for Quarkus SOAP client and SOAP service dealt with services having only primitive parameters and return values such as integers and strings. Let’s have a look at passing and receiving more complex objects.
As an example, let’s create an application for managing fruits.
The sample code snippets used in this section come from the server integration test in the source tree of Quarkus CXF |
Because our representation of fruit is supposed to be a complex, let’s model it as a Java bean with a couple of attributes:
package io.quarkiverse.cxf.it.server;
import java.util.Objects;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@XmlType(name = "Fruit")
@XmlRootElement
public class Fruit {
private String name;
private String description;
public Fruit() {
}
public Fruit(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
@XmlElement
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
@XmlElement
public void setDescription(String description) {
this.description = description;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Fruit)) {
return false;
}
Fruit other = (Fruit) obj;
return Objects.equals(other.getName(), this.getName());
}
@Override
public int hashCode() {
return Objects.hash(this.getName());
}
}
As you may have noticed, we have used some JAXB annotations,
such as @XmlElement
, @XmlRootElement
and @XmlType
.
This is to control the serialization and deserialization of the bean from and to XML.
Automatic registration for reflection
JAXB is a reflection based serialization framework.
When learning about GraalVM native images, one of the first things you typically hear is
that you have to register classes, fields and methods for reflection at build time.
With plain GraalVM you’d have to do this through reflection-config.json
manually.
Well, at least for the classes you have written yourself.
Not so with Quarkus. quarkus-jaxb
extension (which quarkus-cxf
depends on)
is able to scan your application’s class path for classes annotated with JAXB annotations
and register them for reflection automatically.
Hence working with complex payloads on Quarkus is not different from stock CXF. The JAXB serialization and deserialization will work out of the box without any additional configuration.
SEI and implementation
The Service Endpoint Interface (SEI) for managing fruits might look like the following:
package io.quarkiverse.cxf.it.server;
import java.util.Set;
import jakarta.jws.WebMethod;
import jakarta.jws.WebService;
@WebService
public interface FruitService {
@WebMethod
Set<Fruit> list();
@WebMethod
Set<Fruit> add(Fruit fruit);
@WebMethod
Set<Fruit> delete(Fruit fruit);
}
We can implement the SEI as simply as possible:
package io.quarkiverse.cxf.it.server;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import jakarta.jws.WebService;
@WebService(serviceName = "FruitService")
public class FruitServiceImpl implements FruitService {
private Set<Fruit> fruits = Collections.synchronizedSet(new LinkedHashSet<>());
public FruitServiceImpl() {
fruits.add(new Fruit("Apple", "Winter fruit"));
fruits.add(new Fruit("Pineapple", "Tropical fruit"));
}
@Override
public Set<Fruit> list() {
return fruits;
}
@Override
public Set<Fruit> add(Fruit fruit) {
fruits.add(fruit);
return fruits;
}
@Override
public Set<Fruit> delete(Fruit fruit) {
fruits.remove(fruit);
return fruits;
}
}
application.properties
The implementation is pretty straightforward and you just need to define your endpoints using the application.properties
.
quarkus.cxf.endpoint."/fruits".implementor = io.quarkiverse.cxf.it.server.FruitServiceImpl
quarkus.cxf.endpoint."/fruits".features = org.apache.cxf.ext.logging.LoggingFeature
Test with Quarkus dev
mode and curl
Having the above files in place, you can start Quarkus tooling in dev
mode:
$ mvn quarkus:dev
...
INFO [io.quarkus] (Quarkus Main Thread) ... Listening on: http://localhost:8080
and then check whether the service is working by invoking its list
operation:
$ curl -v -X POST -H "Content-Type: text/xml;charset=UTF-8" \
-d \
'<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
<soapenv:Body>
<ns1:list/>
</soapenv:Body>
</soapenv:Envelope>' \
http://localhost:8080/soap/fruits
...
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:listResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
<return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<description>Winter fruit</description>
<name>Apple</name>
</return>
<return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<description>Tropical fruit</description>
<name>Pineapple</name>
</return>
</ns1:listResponse>
</soap:Body>
</soap:Envelope>
As you can see, the endpoint has returned the two fruits Apple
and Pineapple
available by default.
Now let’s add another fruit, say an Orange
:
$ curl -v -X POST -H "Content-Type: text/xml;charset=UTF-8" \
-d \
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:add xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<arg0>
<description>Mediterranean fruit</description>
<name>Orange</name>
</arg0>
</ns2:add>
</soap:Body></soap:Envelope>' \
http://localhost:8080/soap/fruits
...
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:addResponse xmlns:ns1="http://server.it.cxf.quarkiverse.io/">
<return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<description>Winter fruit</description>
<name>Apple</name>
</return>
<return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<description>Tropical fruit</description>
<name>Pineapple</name>
</return>
<return xmlns:ns2="http://server.it.cxf.quarkiverse.io/">
<description>Mediterranean fruit</description>
<name>Orange</name>
</return>
</ns1:addResponse>
</soap:Body>
</soap:Envelope>
We can see Orange
having been added in the returned list as expected.
Further steps
You may want to proceed with packaging your application for running on a JVM or natively.