XStream, Spring-WS OXM and Generics

xml XStream, Spring WS OXM and Generics

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/

  1. [...] a previous post I showed how to combine spring-ws and XStream to produce customizable XML format from a Java object [...]

One Comment

  1. Jinbo

    Thx

    Posted March 18, 2011 at 12:08 PM | Permalink | Reply

Post a Comment

Your email is never shared. Required fields are marked *

*
*