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.
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.
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
With this interface, we can specify the type of the Map’s value property. The first implementation of this interface will be
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
value will be used. This could be overwritten by specifying the name attribute in the
@XmlElement annotation like
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.
StringResponse class will be used by JAXB when unmarshalling a Map of String values into a List of Objects.
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.
StringResponse class properly annotated, we can use it in a Spring Integration service activator like this.
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
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
ArrayDataItem is used to merely unmarshal a Map of
StringDataItem entries. Like
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
@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.
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.
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.
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.
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.