Java Architecture for XML Binding (JAXB) is an API for access of XML documents from applications written in the Java programming language. It’s convenient to use as it usually follows this pattern:
- Create JAXBContext
- Create Marshaller/Unmarshaller
- Marshal/Unmarshal the code
The following are examples that marshal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Application { public static void main(String[] args) throws JAXBException, IOException { Project project = new Project(); project.getTasks().add(new Task("tstamp")); project.getTasks().add(new Task("mkdir")); project.getTasks().add(new Task("rmdir")); Marshaller marshaller = JAXBContext.newInstance(Project.class).createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); try (StringWriter writer = new StringWriter()) { marshaller.marshal(project, writer); System.out.println(writer.toString()); } } } |
and unmarshal XML document to object:
1 2 3 4 5 6 7 |
public class Application { public static void main(String[] args) throws JAXBException { Unmarshaller unmarshaller = JAXBContext.newInstance(Project.class).createUnmarshaller(); Project project = (Project) unmarshaller.unmarshal(new File("project.xml")); } } |
The marshalled XML looks like this:
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <project> <task name="tstamp"/> <task name="mkdir"/> <task name="rmdir"/> </project> |
The actual model is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
@XmlRootElement (name = "tasks" @XmlAccessorType(XmlAccessType.FIELD) public class Project { @XmlElement(name = "task") private Collection<Task> tasks = new ArrayList<>(); public Collection<Task> getTasks() { return tasks; } public void setTasks(Collection<Task> tasks) { this.tasks = tasks; } } public class Task { private String name; public Task() { } public Task(String name) { this.name = name; } @XmlAttribute public String getName() { return name; } public void setName(String name) { this.name = name; } } |
As you can see the JAXBContext is initialized with the classes that take part in the marshall/unmarshall process (in this case the Project class has a reference to the Task class and that’s why there is no need to pass all classes to the JAXBContext). This means that those classes should be known beforehand. However often this is not the case. There are cases when the contained elements are known only at runtime. One such example is a Task Runner – an application that can run tasks and the tasks with their execution order and parameters are contained in single XML file (sounds familiar? Yep, Ant is what comes to mind). Consider the following example:
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <tasks> <tstamp/> <mkdir dir="foo"/> <delete dir="foo"/> </tasks> |
It contains several elements that have different names – tstamp, mkdir, delete – however they are all of the same type. They are all Tasks. And because such an application is extensible (users can add arbitrary Tasks) by plugins the Tasks are not known beforehand.
Let’s start with the model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@XmlRootElement(name = "tasks") public class Project { private Collection<Task> tasks = new ArrayList<>(); @XmlElementRef(name = "task") public Collection<Task> getTasks() { return tasks; } public void setTasks(Collection<Task> tasks) { this.tasks = tasks; } } public abstract class Task {} |
Minor changes can be spotted. First of all Task is an abstract class so the actual implementors are not referenced. The other thing is the @XmlElementRef annotation. This annotation dynamically associates an XML element name with the JavaBean property. When used the XML element name is derived from the instance of the type of the JavaBean property at runtime.
This is the Java code defining the Tasks
1 2 3 4 5 6 7 8 |
@XmlRootElement("tstamp") public class TstampTask extends Task {} @XmlRootElement("mkdir") public class MkdirTask extends Task {} @XmlRootElement("delete") public class DeleteTask extends Task {} |
There are two ways to marshal those objects. First add jaxb.index file to the package containing the JAXB annotated classes and list them. The second way is to provide ObjectFactory in the package containing the JAXB annotated classes.
1 2 |
//the contents of jaxb.index Project |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@XmlRegistry class ObjectFactory { @XmlElementDecl(name = "tstamp") JAXBElement<Timestamp> createTimestamp(Timestamp task) { return new JAXBElement<Timestamp>(new QName("", "aws"), Timestamp.class, task); } @XmlElementDecl(name = "mkdir") JAXBElement<CreateDir> createMkdir(CreateDir taks) { return new JAXBElement<CreateDir>(new QName("", "mkdir"), CreateDir.class, taks); } @XmlElementDecl(name = "rmdir") JAXBElement<RemoveDir> createRmdir(RemoveDir task) { return new JAXBElement<RemoveDir>(new QName("", "rmdir"), RemoveDir.class, task); } } |
Source: ObjectFactory.java
Here is an app that instantiates custom tasks and marshals them
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static void main(String[] args) throws JAXBException, IOException { Project project = new Project(); project.getTasks().add(new Timestamp()); project.getTasks().add(new CreateDir()); project.getTasks().add(new RemoveDir()); Marshaller marshaller = JAXBContext.newInstance("io.techgarage.jaxb.factory.model:io.techgarage.jaxb.factory.tasks").createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); try (StringWriter writer = new StringWriter()) { marshaller.marshal(project, writer); System.out.println(writer.toString()); } } |
Source: Application.java
As you can see in this case the JAXBContext is created by specifying the package names containing the JAXB annotated classes.
Resources:
https://jaxb.java.net/nonav/2.2.4/docs/api/javax/xml/bind/annotation/XmlElementRef.html
https://jaxb.java.net/nonav/2.2.4/docs/api/javax/xml/bind/annotation/XmlElementDecl.html