Filed under: Java, TDD, — Tags: JUnit, POJO, dom, jaxb, xml, xpath — Thomas Sundberg — 2010-01-19
We want to convert a POJO, Plain Old Java Object, to xml and we don’t want to alter the POJO in any significant way. How can this be done?
One solution is to annotate the POJO and use jaxb to transform it to xml. Let’s start with a simple POJO, Car.java.
The non annotated version looks like this:
package com.agical.educational.model; public class Car { private String registration; private String brand; private String description; public Car() { } public Car(String registration, String brand, String description) { this.registration = registration; this.brand = brand; this.description = description; } public String getRegistration() { return registration; } public void setRegistration(String registration) { this.registration = registration; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
And our goal is to create a xml document that may look like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <car registration="abc123"> <brand>Volvo</brand> <description>Sedan</description> </car>
And we want to do it test driven and build it using Maven.
Let’s start with a pom.xml that will solve our need for tools:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.agical.educational</groupId> <version>1.0</version> <artifactId>jaxb</artifactId> <name>jaxb lab</name> <packaging>jar</packaging> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.1</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-idea-plugin</artifactId> <version>2.2</version> <configuration> <downloadJavadocs>true</downloadJavadocs> <downloadSources>true</downloadSources> <jdkLevel>1.6</jdkLevel> <jdkName>1.6</jdkName> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2</version> </dependency> </dependencies> </project>
Let’s continue with a small test that will verify that we get a proper xml for a car.
package com.agical.educational.model; import com.agical.educational.model.Car; import com.agical.educational.util.XmlUtil; import org.junit.Test; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class CarTest { @Test public void getCarAsXml() { String registration = "abc123"; String brand = "Volvo"; String description = "Sedan"; Car car = new Car(registration, brand, description); XmlUtil xmlUtil = new XmlUtil(); String xml = xmlUtil.convertToXml(car, car.getClass()); String xpathExpression = "/car/@registration"; String actual = xmlUtil.extractValue(xml, xpathExpression); assertThat(actual, is(registration)); xpathExpression = "/car/brand"; actual = xmlUtil.extractValue(xml, xpathExpression); assertThat(actual, is(brand)); xpathExpression = "/car/description"; actual = xmlUtil.extractValue(xml, xpathExpression); assertThat(actual, is(description)); } }
An annotated version of Car looks like this:
package com.agical.educational.model; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement @XmlType(propOrder = {"brand", "description"}) public class Car { private String registration; private String brand; private String description; public Car() { } public Car(String registration, String brand, String description) { this.registration = registration; this.brand = brand; this.description = description; } @XmlAttribute public String getRegistration() { return registration; } public void setRegistration(String registration) { this.registration = registration; } @XmlElement public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } @XmlElement public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
A utility class is needed to convert an annotated class to xml. It may look like this:
package com.agical.educational.util; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.StringWriter; public class XmlUtil { public String convertToXml(Object source, Class... type) { String result; StringWriter sw = new StringWriter(); try { JAXBContext carContext = JAXBContext.newInstance(type); Marshaller carMarshaller = carContext.createMarshaller(); carMarshaller.marshal(source, sw); result = sw.toString(); } catch (JAXBException e) { throw new RuntimeException(e); } return result; } }
This is all that is needed to be able to annotate a class and convert it to xml using jaxb.
I also created a utility to read a value from an xml document and the final version of XmlUtil ended up like this:
package com.agical.educational.util; import org.w3c.dom.Document; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringWriter; public class XmlUtil { public String convertToXml(Object source, Class... type) { String result; StringWriter sw = new StringWriter(); try { JAXBContext carContext = JAXBContext.newInstance(type); Marshaller carMarshaller = carContext.createMarshaller(); carMarshaller.marshal(source, sw); result = sw.toString(); } catch (JAXBException e) { throw new RuntimeException(e); } return result; } public String extractValue(String xml, String xpathExpression) { String actual; try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setIgnoringElementContentWhitespace(true); DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); byte[] bytes = xml.getBytes("UTF-8"); InputStream inputStream = new ByteArrayInputStream(bytes); Document doc = docBuilder.parse(inputStream); XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); actual = xpath.evaluate(xpathExpression, doc, XPathConstants.STRING).toString(); } catch (Exception e) { throw new RuntimeException(e); } return actual; } }
As an exercise for myself, I decided to create a collection of cars and annotate it as well. A Car Data Access Object stub may look like this:
package com.agical.educational.model; import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.LinkedList; import java.util.List; @XmlRootElement(name = "cars") public class CarDAO { @XmlAnyElement public List<Car> cars; public CarDAO() { cars = new LinkedList<Car>(); } public void addCar(Car car) { cars.add(car); } }
A test class that adds two cars and reads the xml back may look like this
package com.agical.educational.model; import com.agical.educational.model.Car; import com.agical.educational.model.CarDAO; import com.agical.educational.util.XmlUtil; import org.junit.Test; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class CarDAOTest { @Test public void getCarsAsXml() { String firstCarRegistration = "abc123"; String secondCarRegistration = "123abc"; Car car = new Car(firstCarRegistration, "Volvo", "Sedan"); CarDAO carDAO = new CarDAO(); carDAO.addCar(car); car = new Car(secondCarRegistration, "Opel", "Truck"); carDAO.addCar(car); XmlUtil xmlUtil = new XmlUtil(); String xml = xmlUtil.convertToXml(carDAO, carDAO.getClass(), car.getClass()); String xpathExpression = "/cars/car/@registration"; String actual = xmlUtil.extractValue(xml, xpathExpression); assertThat(actual, is(firstCarRegistration)); } }