You may have noticed a couple of drawbacks to using the DOM to process XML. Both have to do with the fact that XML files are meant to be exchanged with others, so both parties need to be in sync with respect to the file layout.
First, tag names in XML are case sensitive. As such, it’s easy to make a mistake in a tag name simply by capitalizing, or failing to capitalize, some or all of a tag name.
Second, by constructing your XML “by hand,” it’s possible that when writing it, the resulting tag structure may mistakenly not be what a supplier furnished or what a recipient expects.
Both these difficulties can be overcome by the combination of JAXB (Java Architecture for XML Binding) and XML templates. When an XML data provider and consumer share an XSD template and use it with JAXB, compatibility problems resolve themselves.
JAXB is an application programming interface (API) for transforming XML to and from Java objects. In our examples, it means transforming our <broadway> structure into a List of ShowPOJO instances and vice versa.
So let’s get started on an example.
Download these Lesson-20-XSD and Lesson-20-Example-02 and add them to your workspace. (It will help to close all other projects except Libs.)
If not already there, add these JARs from Libs to the build classpath of Lesson-20-Example-02:
- commons-lang3-…jar
- jakarta.activation-1.2.2.jar
- jakarta-xml.bind-api-2.3.3.jar
- jaxb-impl-2.3.7.jar
And if not already there, add these JARs from Libs to the build classpath of Lesson-20-XSD:
- jakarta.activation-1.2.2.jar
- jaxb-api.2.3.0.jar
- jaxb-core-2.3.0.1.jar
- jaxb-xjc-2.3.0.jar
And by now you’ll have noticed that you still have errors in project Lesson-20-Example-02. That’s because the build path is missing JAXB classes that represent our XML structure. We removed these classes before uploading the project (but retained their import statements) so we can get the experience of generating them.
Generating JAXB Classes From a Schema
The JAXB classes that we want to generate describe tags in our XML as Java objects, and uses annotations to tell the JAXB framework about their relationship of the XML to the corresponding Java. Many developers code these Java objects by hand. The good news is, you don’t have to. JAXB generators are available which provide the Java code corresponding to an XML template (an XSD file), and Eclipse Enterprise Edition provides such a generator. Let’s use it now.
- Right-click on file xsd/broadway.xsd in project Lesson-20-XSD, and select Generate/JAXB Classes….
- As shown above, select Lesson-20-XSD and click Next.
- Click the Browse button…
… and select org.hardknockjava.lesson20.example02.jaxb.broadway from the list. (This folder has already been defined in project Lesson-20-XSD specifically so you can pick it from this list). It’s also the package under which the classes in Lesson-20-Example-02 expect to find them based on the import statements.) - Click Ok.
… and click Finish. You’ll see a warning that existing files will be overwritten; click Yes to allow the generation to proceed. - Now, copy or move the generated files to project Lesson-20-Example-02 from package org.hardknockjava.lesson20.example02.jaxb.broadway in Lesson-20-XSD by clicking on them in the Package Explorer and dragging them to the corresponding package in Lesson-20-Example-02, or by using copy/paste. And your compilation errors should now be resolved.
Try running class BroadwayReaderJAXB as a Java application and see what you get!
Parsing an XML File With JAXB: Unmarshalling
Class BroadwayReaderJAXB contains these instance variables:
private JAXBContext jaxbContext;
private Unmarshaller jaxbUnmarshaller;
And it’s constructor initializes them:
jaxbContext = JAXBContext.newInstance(Broadway.class);
jaxbUnmarshaller = jaxbContext.createUnmarshaller();
(The variables could have been static, since they won’t change.)
A JAXBContext provides entree to the JAXB framework. The values passed to newInstance() are one or more classes which, along with classes to which they refer, will be recognized by the framework. And these classes are those with the necessary JAXB annotations that describe the XML layout–in our example, the classes generated from the schema broadway.xsd and found in package org.hardknockjava.lesson20.example02.jaxb.broadway.
The class or classes passed to newInstance() correspond to the outermost XML tags which will be unmarshalled and/or marshalled (more on this later). In our example, we’ll be parsing the <broadway> tag, so we pass Broadway.class.
And Unmarshaller converts XML into Java objects. Which leads us to our read() method.
private void read(String fileName) throws Exception {
File showFile = new File(fileName);
InputStream xmlSource = new FileInputStream(showFile);
Broadway broadway = (Broadway) jaxbUnmarshaller.unmarshal(xmlSource);
The unmarshal() method brings our XML into memory, into the classes produced by the JAXB generator. We happen to have passed it an InputStream, but unmarshal() also accepts objects of type File, URL, StringBuffer, or DOM Node.
Notice that we need to cast the output of unmarshal(), as its method definition returns Object.
Now begins the task of copying the unmarshalled XML into ShowPOJOs.
for (Show show : broadway.getShow()) {
String title = show.getTitle();
String theatre = show.getTheatre();
The variable broadway represents the <broadway> tag, and within that tag are zero or more <show> tags. The JAXB generator created class Show to represent these tags, and Broadway contains a List of Show objects created by the process of unmarshalling. broadway.getShow() returns this List.
The methods getTitle() and getTheatre() are straightforward, but consider that the former returns the value of an attribute while the latter returns the text within an element. The method names created by the JAXB generator don’t distinguish between attributes and element values.
List<String> playwrights = show.getPlaywrights().getPlaywright();
List<String> composers = new ArrayList<String>();
if (show.getComposers() != null) {
composers = show.getComposers().getComposer();
}
We create playwrights as a List<String> set to the value of show.getPlaywright(). Again, it’s counterintuitive but getPlaywright() returns a List and not an individual String.
We initialize composers as an empty List<String>, and if there is no <composers> tag, we leave it at that.
Integer previews = show.getPreviews();
Integer performances = show.getPerformances();
ShowPOJO instance = new ShowPOJO(title, theatre, playwrights, composers, lyricists, opening, closing,
previews, performances);
showList.add(instance);
We set previews and performances to the Integer values returned from the unmarshalled <show> tag. Notice how JAXB has used the type specifications in the schema to convert the string input in the XML to Integer values for us!
Finally, we create a ShowPOJO from the accumulated values and store it in our List. Mission accomplished.
Creating XML With JAXB: Marshalling
Not surprisingly, the opposite of unmarshalling is marshalling: the process of converting Java objects into XML.
After creating an Unmarshaller, our constructor creates a Marshaller:
jaxbMarshaller = jaxbContext.createMarshaller();
And just as we unmarshalled into a Broadway instance and then stored its contents in POJOs, we do the reverse to obtain the XML from our POJOs: build a Broadway instance, populate it from our POJOs, and marshal it. So here we go:
private String write() throws Exception {
Broadway broadwayOut = new Broadway();
for (int index = showList.size() - 1; index >= 0; index--) {
SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yyyy");
ShowPOJO production = showList.get(index);
Show showXml = new Show();
if (production.getClosing() != null) {
showXml.setClosing(fmt.format(production.getClosing()));
}
First, we create the Broadway instance, representing the outer XML tag. Next, we loop through the ShowPOJOs in reverse order (just because we feel like it). For each ShowPOJO, we create a Show instance. It then becomes a simple matter to set individual Strings (like closing) or Integers (like previews).
Initializing a List in Show is a little trickier:
if (!production.getComposerList().isEmpty()) {
Composers composers = new Composers();
composers.getComposer().addAll(production.getComposerList());
showXml.setComposers(composers);
}
This is complicated, so follow carefully.
- Show has a field, composers, which is a Composers object and represents the <composers> tag.
- Composers has a field, composer, which is a List<String> and represents all the <composer> tags.
- What’s happening here is that we copy the list of composers from the ShowPOJO to composer within a Composers instance (furnishing the <composer> tags within <composer>).
- Then we set composers within the Show instance (adding the <composers> tag to the <show> tag).
We continue initializing the rest of the Show instance, and then add it to the Broadway instance–essentially, adding the <show> tag to the <broadway> tag:
broadwayOut.getShow().add(showXml);
Finally, when all the ShowPOJOs have been processed, we can marshal the XML:
OutputStream output = new ByteArrayOutputStream();
// This statement ensures the output is in "pretty print" format.
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(broadwayOut, output);
return output.toString();
And now we have a String of XML that we can dispose of as we please.
Exercise
Now it’s time to try to parse, and to generate, XML for yourself, using both the DOM and JAXB.
- Download Lesson-20-Exercise-01 and import it to your workspace.
- This project has a package, org.hardknockjava.lesson20.example01.jaxb.murder. This package exists to hold JAXB files you need to generate. Project Lesson-20-XSD already has a schema file, murder.xsd. Generate JAXB classes from this file org.hardknockjava.lesson20.example01.jaxb.murder and copy them from project Lesson20-Lesson20-XSD to Exercise-01.
- Modify class MurderReaderDOM to use the Document Object Model to parse the XML in file data/murder.xml into instances of BookPOJO, and then to create XML from those instances and write them to a file.
- Modify class MurderReaderJAXB to accomplish the same tasks but using JAXB.
- Run MurderReaderDOM and MurderReaderJAXB to see how well you did. To view the resulting files from within Eclipse, you’ll need to refresh the Package Explorer tree by right-clicking on Lesson20-Exercise-01/data and selecting Refresh from the popup menu.
If you get stuck, you can download and import a solution from Lesson20-Exercise-01-Solution.
What You Need to Know
- It’s important that the producer and the consumer of XML data agree on the format of that data.
- Use of XML schemas is a means of ensuring this agreement.
- JAXB classes should be coded to match the XML schema. There are generators for producing JAXB classes from an XML schema, and Eclipse Enterprise Edition provides one.
- Using JAXB classes generated from a schema makes it impossible to code XML tags with the names, sequences, or formats that don’t conform to the schema.