Did you know that when you put the @XmlRootElement annotation on any class you can have an XML representation of that class?
When I first saw Java 6 I didn’t get very excited by it, but I have found out some nice things (scripting and Derby for example). This article will also describe a new feature of Java 6. I believe it was also possible with JWSDP, but I don’t like to search external libraries (or at least not standard in a Maven 2 repository 😉 )
I wanted to put an get an XML document in and from a database. With the new JDBC 4.0 you can save and load the XmlType data type from your database. But this article is not about JDBC, it started as an article about it, but then I discovered some pretty cool featues with XML only. The JDBC 4.0 has to wait a little longer.
Creating the XML document
When I was looking how to create an XmlType object I found out it was possible to marshal (the conversion from Object to XML representation) an object to an XML document. Let’s start with a simple class called Movie:
public class Movie {
private String title;
private String genre;
private Integer year;
}
Now create the getters and setters for these fields. Note that without getters and setters the marshalling doens’t work as expected! An empty constructor is also needed when you override the default constructor.
The final step is putting an @XmlRootElement annotation on your class.
@XmlRootElement
public class Movie {
...
}
I created a simple unit test to print an XML document to the System.out.
public class XmlTest extends TestCase {
public void testXml() throws Exception {
Movie movie = new Movie("Pulp Fiction", "Action", 1994);
JAXBContext context = JAXBContext.newInstance(Movie.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); //pretty print XML
marshaller.marshal(movie, System.out);
}
}
I created a constructor for my Movie object, so I didn’t have to set 3 fields. The next line creates a JAXBContext, this is the entry point of all your JAXB operations. You can give an array of classes (or just use the Java 5 varargs as parameters) or a list of packages. For now it is enough to pass the Movie class. With our context we can create a Marshaller, the marshaller will perform the actual conversion to XML.
The property jaxb.formatted.output is used for debugging purposes only, normally all XML tags will be on one line.
Finally we invoke the marshal method and you’ll see the following document appear on the console:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
<genre>Action</genre>
<title>Pulp Fiction</title>
<year>1994</year>
</movie>
You can also route the output to other streams or objects. The javadocs of the Marshaller explain this pretty well.
To get a String create a StringWriter object and ivoke the getBuffer() get a StringBuffer, which can be toString()-ed to get a String.
Creating the schema
Whilst browsing the API I found a method called generateSchema. That sounds cool and easy. The easy part appeared to be a little more difficult, but it works.
The generateSchema method on JAXBContext takes a SchemaOutputResolver as parameter and changes that object (which is quite evil in my opinion). So let’s find an implementation of the SchemaOutputResolver. When I opened the Javadoc I saw this interesting line of comment : “This is a class, not an interface so as to allow future versions to evolve without breaking the compatibility” I don’t get this, an interface breaks compatibility? I looked up the source of SchemaOutputResolver and found no non-abstract methods, so that’s an interface to me, but you can’t extend from another class and extending from another class doesn’t break compatibility or does it?
But enough of that, I couldn’t find an implementation of the class. Fortunately google code search helped me out. In the source of Xfire I found an implementation of the clas:
final List<DOMResult> results = new ArrayList<DOMResult>();
context.generateSchema(
new SchemaOutputResolver() {
@Override
public Result createOutput(String ns, String file)
throws IOException {
DOMResult result = new DOMResult();
result.setSystemId(file);
results.add(result);
return result;
}
});
I won’t get into the details of this code, it isn’t important right now, we just want a schema. The context object is the JAXBContext object we created earlier.
The final step is writing the schema to System.out. This is a little bit more work than the XML file:
DOMResult domResult = results.get(0);
Document doc = (Document) domResult.getNode();
OutputFormat format = new OutputFormat(doc);
format.setIndenting(true);
XMLSerializer serializer = new XMLSerializer(System.out, format);
serializer.serialize(doc);
We have to cast node from the result to a Document to let the serializer understand it. The setIndenting(true) not only indents, but also puts every tag on a new line.
The result:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="movie" type="movie"/>
<xs:complexType name="movie">
<xs:sequence>
<xs:element minOccurs="0" name="genre" type="xs:string"/>
<xs:element minOccurs="0" name="title" type="xs:string"/>
<xs:element minOccurs="0" name="year" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Advanced
This schema isn’t right I hear you think. A movie needs at least a title, so minOccurs should not appear.
@XmlElement(required = true)
public String getTitle() {
return title;
}
Remember to put the annotation on the getter and not on the field, otherwise you’ll get the following error:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name “title”
When you run your test again you’ll notice the minOccurs=0 has dissapeared. The order of the elements has also changed, this is not wat we want. The title must be the first element and today I think year as the second element is nice.
We have to use the @XmlType annotation for this:
@XmlRootElement
@XmlType(propOrder = {"title", "year", "genre"})
public class Movie { ... }
Quite straightforward, only a pity that we have to ‘hardcode’ the element names. It would’ve been nice if the schema generator took the order as we put it in the class (but of course there is no order in the class, it’s just what we see). The good thing however is that you’ll get an error on the JAXBContext.newInstance(Movie.class); when you do something wrong with the propOrder.
Multiple genres
Pulp Fiction is an action movie, but it’s also crime and drama. What we usually do in Java is create a Set of genres. Unfortunately it doen’st work when you replace the String genre with Set genres, you need to put an annotation on a getter and you can’t just change the Set interface. So I created a GenreSet class:
public class GenreSet {
private Set<String> genre;
public GenreSet() {
genre = new HashSet<String>();
}
public void add(String s) {
genre.add(s);
}
@XmlElement(name = “genre”)
public Set<String> getGenre() {
return genre;
}
}
I replaced the String genre in the Movie class with GenreSet genres and when we run the test again we’ll get a set of genres:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<movie>
<title>Pulp Fiction</title>
<year>1994</year>
<genres>
<genre>Drama</genre>
<genre>Crime</genre>
<genre>Action</genre>
</genres>
</movie>
Omit the year
Suppose we don’t want to show the year in XML, but we do want it to be in the Java object. We can use the @XmlTransient annotation to get this behaviour.
Year as attribute
It’s also possible to use a field as an attribute, use the @XmlAttribute for this.
Conclusion
What started out as an article to show the XmlType in JDBC turned out to be an article about XML only. After the @XmlAttribute I thought it was time to stop. Every time I started exploring a new feature I found 2 new other cool features. The java.xml.bind package is quite extensive with many possibilities.
A good thing is that everything is in the JDK, when you had a library problem in the past it probably was due to an XML libary.
Sources
http://forum.java.sun.com/thread.jspa?threadID=633093&messageID=3666378
Xfire source for SchemaOutputResolver
Really good one
Thanks for this, just what I was looking for to generate a schema from my model.
The advantage of SchemaOutputResolver being a class rather than an interface is if they want to add another method to it. If it is an interface adding a method breaks all existing code and stops it from compiling. As a class this isn’t necessarily the case if they SchemaOutputResolve could provide a default implementation of the method and subclassers would keep working fine.
XStream works better; more types out of the box, fewer bugs
Cool stuff!