Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 4 of 6
I'm a teacher, so I always like to include programming exercises. As an exercise, run validate on socks.xml, change socks.xml to make it invalid, and run validate again. Xerces should produce helpful error messages.
Now we'll use JAXB and Castor to generate Java classes based on DTDs and XML Schemas. Again, schema-based code generation enables us to represent conformant XML documents as objects in our programs. JAXB and Castor perform essentially the same task, but we'll see that Castor is a more mature and full-featured package. Two other similar, young frameworks worth noting are Enhydra Zeus and Arborealis from Beautiful Code BV, but we won't examine them here (see Resources for more information).
Let's first look at the Sun framework for XML data binding, JAXB. The JAXB API automates the mapping between XML documents and Java objects. It is currently an early access release, meaning that we can download and use a bare-bones working version. JAXB now supports only Java class creation from DTDs; future releases will add XML Schema support.
Let's begin our tour of generating classes with JAXB based on socks.dtd with the following summary of steps:
socks.dtd, and define a JAXB binding schema
Step 1. Start with our DTD, socks.dtd, and define a JAXB binding schema
To begin, we'll need to write one more thing: a JAXB binding schema -- that is, a JAXB-specific document that helps JAXB convert the DTD to Java classes. It makes up for the DTD data typing
deficiencies and separates the programming-specific information from the schema information. For our example, it looks like
this:
Listing 5. socks.xjs
<?xml version="1.0"?>
<xml-java-binding-schema version="1.0ea">
<!-- Register a type. This specifies that we want to use this type instead of String somewhere in our document. -->
<conversion name="BigDecimal" type="java.math.BigDecimal" />
<element name="socks" type="class" root="true" />
<element name="price" type="value" convert="BigDecimal"/>
<!-- To restrict the sock color to white or black we create an enumeration
with the allowed values and make the color attribute the new enumeration type -->
<element name="color" type="class">
<attribute name="value" convert="SockColor"/>
</element>
<enumeration name="SockColor" members="white black"/>
</xml-java-binding-schema>
As noted in bold font, we ask JAXB to make the price element a BigDecimal, which is like a Java double. Our binding schema also restricts the color values to white or black. Yeah, I know, it's tough to learn yet another syntax for this binding schema, but Sun specifications have a lot of staying
power, so the spec should be around for a while. Besides, maybe Sun will make a GUI to help in creating the JAXB binding schema
(fingers crossed). For more information about JAXB binding schemas, see Resources.
Step 2. Invoke the JAXB schema compiler
Now we invoke the JAXB schema compiler. First, we need to download JAXB. We surf to http://java.sun.com/xml/jaxb/ and download the JAXB Implementation 1.0, Early Access Release.
We will end up with the following jar files:
jaxb-rt-1.0-ea.jar: Runtime library (the binding framework)
jaxb-xjc-1.0-ea.jar: Schema compiler
Next, we invoke the JAXB schema compiler:
java -jar %JAXB_HOME%\lib\jaxb-xjc-1.0-ea.jar socks.dtd socks.xjs -d destination_directory
So, the DTD and the schema binding file go in, and Java classes come out. The Java files produced are Socks.java, SockColor.java, Sock.java, and Color.java.
Examine the files and take pleasure in knowing that you won't have to maintain this code yourself. When your DTD or schema
binding changes, you simply regenerate the classes. Notice in particular the validate(), marshal(), and unmarshal() methods. If you're thinking about adding custom code, you'd better subclass these generated classes. That way you will end
up regenerating the superclasses, which won't overwrite your custom code. I'll leave that as another exercise for you.
Step 3. Compile our newly generated classes
We ensure the JAXB runtime jar (jaxb-rt-1.0-ea.jar) is in our CLASSPATH, then we compile the generated classes.
Step 4. Examine a test program using these classes
Okay, deep breath. Now we'll examine a test program that uses our newly generated classes.
I recently moved from America to Europe with a wardrobe that included only white socks. I quickly discovered that Europeans only wear dark-colored socks. My program turns all my white socks black and leaves the smell alone because you don't need to wash black socks:
Listing 6. JAXBTestSocks.java
import java.io.*;
import java.util.*;
import java.math.BigDecimal;
public class JAXBSocksTest {
public static void main(String[] args) {
// Building the content trees (Unmarshal the socks.xml file) Socks socks = new Socks();
try {
// Pass in socks.xml as a command-line argument
File socksFile = new File(args[0]);
InputStream fin = new FileInputStream(socksFile);
socks = socks.unmarshal(fin);
List sockList = socks.getSock();
// Print in memory document
printSocks(sockList);
fin.close();
// Turn all socks black
Sock sock;
Color black; // My Color class generated by JAXB
System.out.println("Turn all socks black for my new European look!"); for (Iterator i = sockList.iterator(); i.hasNext();) {
sock = (Sock)i.next();
if (sock.getColor().getValue().equals(SockColor.WHITE)) {
// if you don't make a new Color object every time JAXB
// gets confused!
black = new Color();
black.setValue(SockColor.BLACK);
sock.setColor(black);
}
}
// Create a new sock (I only buy black ones now!) sock = new Sock();
sock.setNumber("4");
sock.setName("new sock");
sock.setImage("newsock.jpg");
Color c = new Color();
c.setValue(SockColor.BLACK);
sock.setColor(c);
sock.setSmell("0");
sock.setPrice(new BigDecimal("5.55")); sockList.add(sock);
// Print in memory document
printSocks(sockList);
// Validate. You must do this before marshaling socks.validate();
//Marshal
System.out.println("Marshaling socks to file blackSocks.xml");
File socks_new = new File("blackSocks.xml"); FileOutputStream fout = new FileOutputStream(socks_new);
socks.marshal(fout);
fout.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//Helper method to print out the socks
static void printSocks(List sockList) {
System.out.println("Printing in memory socks...");
Sock currentSock;
for (Iterator i = sockList.iterator(); i.hasNext();) {
currentSock = (Sock)i.next();
System.out.println("Sock number " + currentSock.getNumber() +
" | Name: " + currentSock.getName() +
" | Color: " + currentSock.getColor() +
" | Price: " + currentSock.getPrice() +
" | Smell: " + currentSock.getSmell());
}
System.out.println("");
}
}
Steps 4a and 4b. Unmarshal an existing XML document and change the content tree
There are two ways to build a content tree -- that is, an in-memory representation of your XML document as objects. One approach is to unmarshal an XML document, which
I do using the unmarshal() method. I can then extract the Sock objects as a List and modify any attributes I'd like. Here I'm turning all my white socks black. The other way to add content is to simply
use the JAXB-generated classes to construct new Sock objects. I insert a new Sock object into my SockList, and I have a new Sock in my in-memory content tree.