It all started when I tried to find a solution for customizing an XStream instance for my ActiveMQ XStream message transformer that would be better than currently used (extending a base transformer and providing a custom factory method that will do things like alias, converter, annotation, etc. settings). I wanted an easy solution to configure XStream instance in a Spring xml configuration file used in my application and provide that instance to all beans that need it (message transformer among others).
XStream is a very nice library that I’ve used and written about before, but there is no Spring factory builder that could be used for this purpose. A little googling got me to the OXM framework, a part of Spring WS project.
The main purpose of this library is to provide a layer above various Object/XML mapping frameworks, such as Castor, Jaxb, XStream, etc. It contains two main abstractions,
Unmarshaller wrapping these basic two operations found in appropriate frameworks. For the context objects, this framework use standard classes located in the
javax.xml.transform package, so you can work with Stream, Sax and DOM sources and results. Also, it is distributed in two JARs, one that is fully JDK1.4 compatible and one that adds extra features for JDK5 where it is appropriate, which is very nice.
Now back to the XStream and some examples. First of all, in order to use this library you must have the appropriate version of
spring-oxm jar file in your classpath. You can achieve this, for example, by adding the following dependency in your pom.xml
<dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-oxm</artifactId> <version>1.0.2</version> </dependency>
Now, we can make our Spring definition of the OXM marshaller
<bean id="plainMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
After the application initialization we have a
plainMarshaller bean ready to use
XStreamMarshaller marshaller = (XStreamMarshaller)context.getBean("plainMarshaller"); JobDetails job = createJob(); StringResult result = new StringResult(); marshaller.marshal(job, result); System.out.println(result.toString());
which should marshall and print my
JobDetails class to standard output. So, the following output is expected
<org.sensatic.jqr.jobs.JobDetails> <group>testGroup</group> <name>testJob</name> <params> <map> <entry> <string>password</string> <string>test</string> </entry> <entry> <string>username</string> <string>dejanb</string> </entry> </map> </params> </org.sensatic.jqr.jobs.JobDetails>
Now let’s add some aliases and converters
<bean id="configuredMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> <property name="aliases"> <props> <prop key="job">org.sensatic.jqr.jobs.JobDetails</prop> </props> </property> <property name="converters"> <list> <bean class="org.sensatic.jqr.jobs.ParamsConverter"/> </list> </property> </bean>
Yes, it is really as easy as that. If we now use a marshaller set like this, it will produce the following result
<job> <group>testGroup</group> <name>testJob</name> <params><password>test</password><username>dejanb</username></params> </job>
As you probably know, XStream can use annotations to set aliases and converters for classes. To demonstrate this we will of course need the additional JAR that provides JDK5 (Tiger) funcionality to the XStream marshaller. For that just add the following dependency to your
pom.xml file (it will include the base file as well), or just provide this additional JAR to your classpath.
<dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-oxm-tiger</artifactId> <version>1.0.2</version> </dependency>
So, instead of defining the aliases and converters manually, we can just point to the classes that are properly annotated. In this case, we have to use
<bean id="annotatedMarshaller" class="org.springframework.oxm.xstream.AnnotationXStreamMarshaller"> <property name="annotatedClasses"> <list> <value>org.sensatic.jqr.jobs.JobDetails</value> </list> </property> </bean>
and the final result should be the same as the previously generated one (of course in case we annotated our class properly).
Now, the real beauty of this is that OXM’s
XStreamMarshaller is that it could be used as XStream bean factory (until some better solution comes along). For this to happen, we first have to define a factory bean like this.
<bean id="XStream" factory-bean="annotatedMarshaller" factory-method="getXStream"/>
Then we can use this to set properly configured XStream instance in any other bean (message transformer for example)
<bean id="messageTransformer" class="org.apache.activemq.util.xstream.XStreamMessageTransformer"> <property name="XStream"><ref bean="XStream"/></property> </bean>
For the end, here’re a couple of things that could be very helpful if happened in the future:
- XStream should get the proper spring bean factory (which could be used in OXM as well)
XStreamMarshallercurrently can not be set to use specified
HiearachicalStreamDriverto be used with streams. For example set the following property on marshaller
<property name="streamDriver"> <bean class="com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver"> </bean> </property>
to use JSON instead of XML. The patch is provided and I hope it will be availabe in future versions of OXM.
- Even with this patch, the XStream instance created by OXM does not have appropriate driver set (and will not be used by other beans where it is injected). Instead the (un)marshaller use it in appropriate methods. This is because of how XStream deals with this property (it could be set only in constructor) so I guess a bit of tweaking on both sides would be required (I guess it could be also sovled through additional constructor in the marshaller, but the universal Spring configuration mechanism would solve this perfectly)
As for the ActiveMQ and transforming messages between XML and Objects:
XStreamMessageTransformercould be modified to work with custom
HierarchicalStreamDriver, until we are able to provide fully configured XStream instance completely configured through Spring XML.
OXMMessageTransformerwould be great stuff to have, as it would provide more flexibility to people who needs it.
I hope I’ll have these two finished real soon.
Finally, take a look at the following two posts from Arjen on the topic
BTW we can also use Camel to transform messages now – though its definitely a bit easier to transform things inside the JMS API sometimes.
I’m definitely planning to use Camel for some of these tasks. This was only the first logical step forward as I’ve used transformers in the “pre-Camel” age. I’ll try to see if some of these XML mapping stuff could be applied to Camel too and provide some code for it. Cheers.