Back in August, I posted about invoking web services with Spring Integration using XStream marshalling/unmarshalling. That post was based on a project I had just finished at the time. Recently, I had to make some changes to that project in order to implement another web service offered by the same provider. This particular web service was returning a SOAP Map like before, however instead of a simple String key and String value, I had String keys and Map values. With XStream, this seemed to be not so simple to implement. After a bit of looking around, I decided to try and swap out XStream for JAXB. I could have simply added JAXB and used it strictly on the new web service, but it sounded like more fun getting JAXB working across the application with a more sophisticated implementation than what I had done with XStream. The other thing I wanted to do was unmarshal the Map as a List of Objects because for the most part, the key itself didn’t matter too much and there was a finite list of those keys. Here’s a sample excerpt of XML being returned by the new web service.
<item>
<key xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">mainkey</key>
<value xsi:type="ns1:Map">
<item>
<key xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">key1</key>
<value xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">value1</value>
</item>
<item>
<key xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">key2</key>
<value xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">value2</value>
</item>
</value>
</item>
Register your classes with the JAXB marshaller
In Spring, there’s the convenient OXM namespace for registering various types of XML marshallers. Here’s how we register the JAXB annotated classes with the JAXB marshaller.
<oxm:jaxb2-marshaller id="jaxbMarshaller">
<oxm:class-to-be-bound name="com.patrickgrimard.ws.Request"/>
<oxm:class-to-be-bound name="com.patrickgrimard.ws.StringResponse"/>
<oxm:class-to-be-bound name="com.patrickgrimard.ws.MapResponse"/>
<oxm:class-to-be-bound name="com.patrickgrimard.util.StringDataItem"/>
<oxm:class-to-be-bound name="com.patrickgrimard.util.ArrayDataItem"/>
</oxm:jaxb2-marshaller>
The first thing I did was start writing an API for dealing with Maps that had String values, and then adapted it to handle Map values.
Unmarshalling simple Maps of String values into a List of Objects
This starts with a simple interface I created using Java Generics called DataItem<T>. Now if anybody’s used JAXB, they know that when unmarshalling XML with JAXB, it can’t handle interfaces, so the objects and their properties need to be concrete implementations that can be instantiated. Here is the DataItem<T> interface.
public interface DataItem<T> {
public String getKey();
public void setKey(String key);
public T getValue();
public void setValue(T value);
}
With this interface, we can specify the type of the Map’s value property. The first implementation of this interface will be StringDataItem.
public class StringDataItem implements DataItem<String> {
private String key;
private String value;
public StringDataItem() {}
public StringDataItem(String key, String value) {
this.key = key;
this.value = value;
}
@Override
@XmlElement
public String getValue() {
return value;
}
@Override
public void setValue(String value) {
this.value = value;
}
@Override
@XmlElement
public String getKey() {
return key;
}
@Override
public void setKey(String key) {
this.key = key;
}
}
So a very simple implementation of DataItem<String>. There’s only 3 things here that matter for JAXB unmarshalling. The default no-arg constructor, the @XmlElement annotation on getKey() and the @XmlElement annotation on getValue(). By default, @XmlElement will derive the node name from the property name. So in this case “key” and “value” will be used. This could be overwritten by specifying the name attribute in the @XmlElement annotation like @XmlElement(name=”name-of-node”).
I won’t go into detail about setting up the outbound gateways for invoking the web services. You can see that in my original post using XStream. Adapting that to JAXB is trivial. What I’ll focus on is unmarshalling the response from the web service.
The following StringResponse class will be used by JAXB when unmarshalling a Map of String values into a List of Objects.
@XmlRootElement(name="responseRoot")
public class StringResponse {
private List<StringDataItem> items;
@XmlElementWrapper(name="items")
@XmlElement(name="item")
public List<StringDataItem> getItems() {
return items;
}
public void setItems(List<StringDataItem> items) {
this.items = items;
}
}
So again this is a simple class who’s only property right now is “items” which is a List<StringDataItem> type. The @XmlRootElement annotation is needed by JAXB for unmarshalling XML, otherwise JAXB will throw an exception telling you there’s no @XmlRootElement annotation. Similar to the @XmlElement annotation, you can specify a name for the root element. Next we have the @XmlElementWrapper annotation on getItems(). This is a useful annotation when your List needs to be wrapped by a particular node name. Where this got tricky for me was the additional @XmlElement annotation. At first when I tried to implement JAXB, I didn’t know about @XmlElementWrapper and I thought @XmlElement would wrap the whole list. As it turned out, annotating the getItems() method with @XmlElement determines what the node name of each List item will be. I thought I had to use @XmlRootElement on the StringDataItem class, but I was wrong. It needs to be in the containing class. So that threw me for a spin at first.
With this StringResponse class properly annotated, we can use it in a Spring Integration service activator like this.
@Component("responseServiceActivator")
public class ResponseServiceActivator {
@ServiceActivator
public void handleResponse(StringResponse response, @Headers Map<String, Object> headers) {
List<StringDataItem> items = response.getItems();
for(StringDataItem item : items) {
if(log.isDebugEnabled())
log.debug(item.getKey() + "=" + item.getValue());
/*
* Do something with the items here
*/
}
}
}
So this takes care of unmarshalling SOAP Maps of String values using JAXB. Let’s look at unmarshalling SOAP Maps of Map values.
Unmarshalling Maps of Map values into a List of Objects
The first thing we need to do is implement DataItem<T> once again. This is the ArrayDataItem implementation.
public class ArrayDataItem implements DataItem<List<StringDataItem>> {
private String key;
private List<StringDataItem> value;
@Override
@XmlElement
public String getKey() {
return key;
}
@Override
public void setKey(String key) {
this.key = key;
}
@Override
@XmlElementWrapper
@XmlElement(name="item")
public List<StringDataItem> getValue() {
return value;
}
@Override
public void setValue(List<StringDataItem> value) {
this.value = value;
}
}
The first thing you’ll notice is that we’re implementing DataItem<List<StringDataItem>>. This is precisely why I started showing you how we implemented StringDataItem first. ArrayDataItem is used to merely unmarshal a Map of StringDataItem entries. Like StringDataItem, ArrayDataItem has a key and value, but the unique feature is how we’ve annotated the getValue() method. Instead of a simple @XmlElement annotation, we’re using @XmlElementWrapper and @XmlElement(name=”item”). As I described earlier, @XmlElementWrapper is used to wrap a List. In this case, it will derive the node name from the property, so it will be called “value” in this case. The @XmlElement(name=”item”) will be the node name of each entry in the Map we unmarshal.
Let’s look at the MapResponse class which will be used as the Object the XML is unmarshalled into.
@XmlRootElement(name="mapResponse")
public class MapResponse {
private List<ArrayDataItem> items;
@XmlElementWrapper(name="items")
@XmlElement(name="item")
public List<ArrayDataItem> getItems() {
return items;
}
public void setItems(List<ArrayDataItem> items) {
this.items = items;
}
}
The MapResponse class has a single property of type List<ArrayDataItem>. It’s very similar to the StringResponse class from earlier. Let’s look at how we can use this kind of response in a Spring Integration service activator.
@Component("mapResponseServiceActivator")
public class MapResponseServiceActivator {
@ServiceActivator
public void handleResponse(MapResponse response, @Headers Map<String, Object> headers) {
List<ArrayDataItem> items = response.getItems();
if(items.size() > 0) {
for(ArrayDataItem arrayItem : items) {
MyEntity entity = new MyEntity();
entity.setId(arrayItem.getKey());
for(StringDataItem item : arrayItem.getValue()) {
switch(Enum.valueOf(MyKeyEnum.class, item.getKey())) {
case KEYA: entity.setPropertyOne((item.getValue()); break;
case KEYB: entity.setPropertyTwo((item.getValue())); break;
case KEYC: entity.setPropertyThree(item.getValue()); break;
}
}
entity.persist();
}
}
}
}
In this service activator, I’m iterating over each ArrayDataItem and creating an instance of MyEntity. Then I’m iterating over each instance of StringDataItem within the ArrayDataItem. Earlier I mentioned there was a finite number of keys in the Map, so I created an Enum with all those possible values. This is an example of what that Enum might look like.
public enum MyKeyEnum {
KEYA, KEYB, KEYC
}
With this Enum class, I can attempt to parse the key value of the StringDataItem and see if it’s defined in my Enum class. I can then use a switch/case statement to perform different operations depending on what the Enum type is. In this case, I’m setting different property values on my entity instance. Finally I’m persisting that entity. One additional step you could take is handling scenarios when a new key is introduced and you didn’t know about it until your application fails. So you could wrap the switch/case statement in a try/catch block. When the Enum.valueOf throws an exception because the new key isn’t defined in the Enum class, you can at least continue iterating over the remaining entries. How you handle the exception is up to you, but you could send yourself some kind of notification.
Conclusion
To conclude, it took a bit of work to replace XStream with JAXB, mainly to get the Map of Map values to unmarshal correctly. I feel it was worth it though. The result is a codebase I’m much happier with.
I hope this helps you out in some way.
Merry Christmas!