
I am playing around with XStream and spring-ws using java 1.5 generics.
Once again Spring has done a good job of simplifying athe integration of a framework’s API into the real-world enterprise workspace. I am in need of an extensible XML binding api. I have looked into Castor, XMLBeans by apache, JibX by sourceforge and of course JaxB, all worthy candidates, each with different advantages and disadvantages. I chose XStream for it’s simplicity and extensibility and because it was most suitable for the task I was performing at the time.
Spring’s Marshaller and Unmarshaler interfaces come from the generalized OXM domain support Spring provides. The speciailized XStreamMarshaller implementation provides a good template like approach to use of the underlying XStream implementation within the container.
<bean id="marshaller" class="org.springframework.oxm.xstream.XStreamMarshaller" />
As with all binding frameworks you need to tweak around the configuration a bit to get the desired results, and one of the ways to go about doing that in the experimentation stage is to load a bunch of data from somewhere and just dump it into XStream marshaller.
My Domain Objects are:
public class Box<T> {
Dimensions size;
String serialNumber;
T contents;
public Box(Dimensions size, String serialNumber, T contents) {
this.size = size;
this.serialNumber = serialNumber;
this.contents = contents;
}
public T getContents() {
return contents;
}
public String getSerialNumber() {
return serialNumber;
}
public Dimensions getSize() {
return size;
}
}
public class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
}
public interface Widget {
}
public class WidgetA implements Widget {
String name;
public WidgetA(String name) {
super();
this.name = name;
}
}
public class WidgetB implements Widget ...
public class Warehouse {
List<Box<Widget>> boxes;
public Warehouse(List<Box<Widget>> boxes) {
super();
this.boxes = boxes;
}
}
Here is a little domain object test:
List<Box<Widget>> boxes = new LinkedList<Box<Widget>>();
Box<Widget> box1 = new Box<Widget>(new Dimensions(3, 3), "A001",
new WidgetA("test-widget-a"));
Box<Widget> box2 = new Box<Widget>(new Dimensions(3, 4), "B001",
new WidgetB("test-widget-b"));
boxes.add(box1);
boxes.add(box2);
Warehouse warehouse = new Warehouse(boxes);
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
marshaller.marshal(warehouse, result);
The result of this out of the box is
<org.echotech.xstream.Warehouse> <boxes class="linked-list"> <org.echotech.xstream.Box> <size> <width>3</width> <height>3</height> </size> <serialNumber>A001</serialNumber> <contents class="org.echotech.xstream.WidgetA"> <name>test-widget-a</name> </contents> </org.echotech.xstream.Box> <org.echotech.xstream.Box> <size> <width>3</width> <height>4</height> </size> <serialNumber>B001</serialNumber> <contents class="org.echotech.xstream.WidgetB"> <name>test-widget-b</name> </contents> </org.echotech.xstream.Box> </boxes> </org.echotech.xstream.Warehouse>
Not very nifty, will do for most bot-works but not very pleasant for the human eye. We can quickly sort this out using some aliases and short cuts
<bean id="marshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> <property name="aliases"> <map> <entry key="warehouse" value="org.echotech.xstream.Warehouse" /> <entry key="box" value="org.echotech.xstream.Box" /> <entry key="a" value="org.echotech.xstream.WidgetA" /> <entry key="b" value="org.echotech.xstream.WidgetB" /> </map> </property> <property name="implicitCollections"> <map> <entry key="org.echotech.xstream.Warehouse" value="boxes" /> </map> </property> </bean>
Look at the result, much more readable.
<warehouse> <box> <size> <width>3</width> <height>3</height> </size> <serialNumber>A001</serialNumber> <contents class="a"> <name>test-widget-a</name> </contents> </box> <box> <size> <width>3</width> <height>4</height> </size> <serialNumber>B001</serialNumber> <contents class="b"> <name>test-widget-b</name> </contents> </box> </warehouse>
Notice how the contents element still has a class attribute. If we want to be able to change that we have to dig deeper since generics are involved. I will essentialy write a generalized converter for the box class in order to do that, giving me complete control over how the class is marshalled and unmarshalled.
Here is the converter implementation:
public class BoxConverter<T> implements Converter {
private final Map<String, Class<T>> aliasMap = new HashMap<String, Class<T>>();
private final Map<Class<T>, String> classMap = new HashMap<Class<T>, String>();
public BoxConverter(List<Class<T>> classes) {
for (Class<T> clazz : classes) {
String dashCase = toDashCase(clazz.getSimpleName());
aliasMap.put(dashCase, clazz);
classMap.put(clazz, dashCase);
}
}
@Override
public void marshal(Object object, HierarchicalStreamWriter writer,
MarshallingContext context) {
@SuppressWarnings("unchecked")
Box<T> box = (Box<T>) object;
writer.addAttribute("serial-number", box.getSerialNumber());
writer.startNode("size");
context.convertAnother(box.getSize());
writer.endNode();
writer.startNode(classMap.get(box.getContents().getClass()));
context.convertAnother(box.getContents());
writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
String serialNumber = reader.getAttribute("serial-number");
reader.moveDown();
Dimensions size = (Dimensions) context.convertAnother(
reader.getValue(), Dimensions.class);
reader.moveUp();
reader.moveDown();
@SuppressWarnings("unchecked")
T contents = (T) context.convertAnother(reader.getValue(), aliasMap
.get(reader.getNodeName()));
reader.moveUp();
return new Box<T>(size, serialNumber, contents);
}
@Override
@SuppressWarnings("unchecked")
public boolean canConvert(Class clazz) {
return Box.class.equals(clazz);
}
}
And here is how to it’s defined in spring:
<bean id="marshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> <property name="mode" value="1001" /> <property name="aliases"> <map> <entry key="warehouse" value="org.echotech.xstream.Warehouse" /> <entry key="box" value="org.echotech.xstream.Box" /> </map> </property> <property name="implicitCollections"> <map> <entry key="org.echotech.xstream.Warehouse" value="boxes" /> </map> </property> <property name="converters"> <list> <bean class="org.echotech.xstream.BoxConverter"> <constructor-arg> <list> <value>org.echotech.xstream.WidgetA</value> <value>org.echotech.xstream.WidgetB</value> </list> </constructor-arg> </bean> </list> </property> </bean>
This produces a nice clean xml we can work with:
<warehouse> <box serial-number="A001"> <size> <width>3</width> <height>3</height> </size> <widget-a> <name>test-widget-a</name> </widget-a> </box> <box serial-number="B001"> <size> <width>3</width> <height>4</height> </size> <widget-b> <name>test-widget-b</name> </widget-b> </box> </warehouse>
Here is the source code.
Note: XStream has a better method for controling the way marshaled classes are mapped look at FieldPrefixStrippingMapper the way it’s shown here. This will only work with Spring 3.0 and later due to SPR-6073.
1 Trackbacks
You can leave a trackback using this URL: http://techo-ecco.com/blog/xstream-spring-ws-oxm-and-generics/trackback/
[...] a previous post I showed how to combine spring-ws and XStream to produce customizable XML format from a Java object [...]
One Comment
Thx