Introduction
One of the things we had to tackle this week was some marshalling issue with a zillion small xml files. The feast of legacy. It was a very depressing thought to create separate marshalling objects for each of them. That Idea popped faster out of my head then in. After some thinking we came up with a component that could do all of the xml files
The solution
The idea I came up with is very simple it is base on Jaxb marshalling which is quit powerfull in the way that the code you need to write is very small. Also the speed of marshalling and unmarshalling is quit fast.
To get this to work you need the following dependencies:
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
//This one you only need when you want to use xpath queries
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
The one and only class for marshalling and unmarshalling:
package nl.lostlemon.Marshal;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/
/The Type is to make it possible to instanciate this class with different marshalling beans.
public class Marshalling<T> {
// Notice that T is the return typed bean. The xm is an inputStream. Jaxb can deal with that. It improves performance
public T unMarshall(final InputStream xmlStream, final Class clazz) {
T value = null;
try {
//create an unmarshaller
Unmarshaller unmarshal = JAXBContext.newInstance(clazz).createUnmarshaller();
//read the xml input stream into Jaxb
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream);
//marshall the xml file into a JavaBean
value = (T)unmarshal.unmarshal(reader, clazz).getValue();
} catch (JAXBException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
return value;
}
//Notice that the parameter is also The T of type.
public ByteArrayOutputStream marshal(final T type) {
//Create the return type for this method.
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
//Create a marshaller
Marshaller marshaller = JAXBContext.newInstance(type.getClass()).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create the xml in the form of an outputstream to boost performance
marshaller.marshal(type, outputStream);
} catch (PropertyException e1) {
e1.printStackTrace();
} catch (JAXBException e1) {
e1.printStackTrace();
}
return outputStream;
}
}
Running the following tests:
//This test uses the BookBean with an xml containing a single book.
@Test
public void testUnmarshallingXpath() {
InputStream inputStream = this.getClass().getResourceAsStream("testSingle.xml");
Marshalling<SingleBookBean> marshalling = new Marshalling<SingleBookBean>();
SingleBookBean bookBean = marshalling.unMarshall(inputStream,SingleBookBean.class);
Assert.assertTrue(bookBean.getTitle() != null);
}
// The BookBean the @Getter and @Setter is from Lombok.
// Take care of the
@XmlRootElement(name = "book")
@XmlAccessorType(XmlAccessType.FIELD) These are the identifiers to tell JaxB what to do. The XmlRootElement refers to the xml rootelement in the xml file.
@Getter
@Setter
@XmlRootElement(name = "book")
@XmlAccessorType(XmlAccessType.FIELD)
public class BookBean {
// These are the exact same names as the xml Elements
private String author;
private String title;
}
//The single xml file:
<book>
<title>witches abroad</title>
<author>Terry Pratchet</author>
</book>
//This test is using the books bean with an xml containing multiple books.
@Test
public void testUnmarshallingList() {
long start = Calendar.getInstance().getTimeInMillis();
InputStream inputStream = this.getClass().getResourceAsStream("test.xml");
Marshalling<Books> marshalling = new Marshalling();
Books books = marshalling.unMarshall(inputStream,Books.class);
//Assert.assertTrue(books.getBookBeans().size() == 3);
long stop = Calendar.getInstance().getTimeInMillis();
long elapsedTime = (stop-start);
System.out.println("number of elements " + books.getBookBeans().size() + " elapsed time " + elapsedTime + " ms");
}
// The Books bean based on the same principle as the BookBean.
@XmlRootElement(name = "catalog")
@XmlAccessorType(XmlAccessType.FIELD)
public class Books {
@Getter
@Setter
@XmlElement(name = "book", type = BookBean.class)
private List<BookBean> bookBeans;
}
//the multiple books xml file:
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
</catalog>
//The marshalling test with the books bean:
@Test
public void testMarshallingList() {
Books books = new Books();
List<BookBean> bookBeans = new ArrayList<BookBean>();
for(int i=0;i<100000;i++) {
BookBean bookBean = new BookBean();
bookBean.setAuthor("J.R.R Tolkien");
bookBean.setTitle("The hobbit");
bookBeans.add(bookBean);
}
books.setBookBeans(bookBeans);
long start = Calendar.getInstance().getTimeInMillis();
Marshalling<Books> marshalling = new Marshalling<Books>();
}
// The marshalling test with the BookBean.
@Test
public void testMarshalling() {
BookBean bookBean = new BookBean();
bookBean.setAuthor("Terry Pratchet");
bookBean.setTitle("Witches abroad");
Marshalling<BookBean> marshalling = new Marshalling<BookBean>();
String xml = marshalling.marshal(bookBean).toString();
}
Conclusion:
Using Java typing in combination with a usefull library in the open closed principal, saves you a lot of code writing and maintainance. I had real pleassure to write in such a small piece of code so much power.
Have fun!