Last but not least, I want to cover JAXB, Sun's Java Architecture for Data Binding. It might surprise you that I'm dealing with JAXB last, or that I've even included coverage of other APIs. However, Sun's offering has been very slow in coming, and is still in early access as I write this book. It will probably not be in a final form when you are reading this chapter, and it has a very limited feature set. Certainly, future versions will add functionality, but for now Sun is just trying to get a stable offering out to the public. You will find features in Castor and Zeus that simply aren't available in JAXB right now; for all these reasons, I want to let you see more than just the JAXB framework. And of course, I'm generally of the opinion that open source software is just cool!
To get started, you need to download JAXB from Sun at http://java.sun.com/xml/jaxb/index.html. I also recommend you get the documentation and specification.
WARNING: At the time of this writing, JAXB is in early release. Sun could easily change things before a final version, although that's not really common in EA (early access) software. Still, be warned that you may have to tweak the code shown here to work by the time you are checking JAXB out. Additionally, there are many errors in the documentation (at least in the early release). I've tried to steer clear of those here, so the examples in this section can be used as correct syntax if you run into errors using the JAXB instructions (as I did).
Once you've got the release downloaded, simply put the two included jar files, jaxb-xjc-1.0-ea.jar and jaxb-rt-1.0-ea.jar, in your classpath. Additionally, JAXB uses a script, xjc, to perform schema compilation. I have no idea why this isn't a Java class, but that's life. You can invoke the Java class manually, though, if you want to dig into the shell script. However, the early access release neglects to supply a script capable of running on Windows, as many of you may be. Example 15-8 is a Windows-compatible script to match the supplied Unix version.
@echo off echo JAXB Schema Compiler echo -------------------- if "%JAVA_HOME%" == "" goto errorJVM if "%JAXB_HOME%" == "" goto errorJAXB set JAXB_LIB=%JAXB_HOME%\lib set JAXB_CLASSES=%JAXB_HOME%\classes echo %JAVA_HOME%\bin\java.exe -jar %JAXB_LIB%\jaxb-xjc-1.0-ea.jar %1 %2 %3 %4 %5 %JAVA_HOME%\bin\java.exe -jar %JAXB_LIB%\jaxb-xjc-1.0-ea.jar %1 %2 %3 %4 %5 goto end :errorJVM echo ERROR: JAVA_HOME not found in your environment. echo Please, set the JAVA_HOME variable in your environment to match the echo location of the Java Virtual Machine you want to use. echo For example: echo set JAVA_HOME=c:\java\jdk1.3.1 goto end :errorJAXB echo ERROR: JAXB_HOME not found in your environment. echo Please, set the JAXB_HOME variable in your environment to match the echo location of the JAXB installation directory. echo For example: echo set JAXB_HOME=c:\java\jaxb-1.0-ea :end
Place this in your JAXB installation's bin directory, along with xjc; as you can see, I called mine xjc.bat. Finally, add an environment entry for both JAVA_HOME and JAXB_HOME (you only need JAVA_HOME if you are on Unix platforms). On my Windows system, I did this:
set JAVA_HOME=c:\java\jdk1.3.1 set JAXB_HOME=c:\javaxml2\jaxb-1.0-ea
Then you are ready to proceed.
While Castor supports only XML Schema, JAXB supports only DTDs. So, I'll use the DTD from Example 15-5 for class generation here. However, JAXB also requires a binding schema for class generation. A binding schema tells JAXB the specifics of how to convert the constraints in a DTD to a Java class (or classes). At its simplest, you need to specify the root element in the DTD (or multiple root elements, if your DTD defines them). Example 15-9 is the simplest possible binding schema for the catalog.xml document used in this chapter.
<?xml version="1.0"?> <xml-java-binding-schema version="1.0ea"> <element name="catalog" type="class" root="true" /> </xml-java-binding-schema>
This specifies the document as a binding schema, and defines the root element. Save this file as catalog.xjc.
With a DTD (catalog.dtd from the last section on Zeus) and a binding schema, you're ready to generate Java classes. Execute the following command:
xjc c:\javaxml2\ch14\xml\jaxb\catalog.dtd c:\javaxml2\ch14\xml\jaxb\catalog.xjc
Or, on Unix or Cygwin:
xjc /javaxml2/ch14/xml/jaxb/catalog.dtd /javaxml2/ch14/xml/jaxb/catalog.xjc
Once that command completes, you get two classes in your current directory, Catalog.java and Item.java. These have the typical set of methods you would expect:
public class Item { public String getLevel( ); public void setLevel( ); public String getId( ); public void setId(String id); public List getGuest( ); public void deleteGuest( ); public void emptyGuest( ); // And so on... }
Note how JAXB deals with lists. It's a step up from Castor (in my opinion), as it does provide access to the List of guests. However (also my opinion), it lacks the convenience of an addGuest( ) method that Zeus provides. You'll need to handle all list manipulation by getting the list and working on it directly:
// The Zeus way item.addGuest(new Guest("Bela Bleck")); // The JAXB way List guests = item.getGuest( ); guests.add("Bela Fleck");
The generated classes are also all concrete classes, much like the model that Castor uses.
The binding schema does provide some nifty options (even if it's not XML). First, you can specify the package that generated classes should be in; this is done using the options element, with the package attribute. Add this line to your binding schema:
<?xml version="1.0"?> <xml-java-binding-schema version="1.0ea"> <options package="javaxml2.jaxb" /> <element name="catalog" type="class" root="true" /> </xml-java-binding-schema>
Now, generated source will be placed in the javaxml2/jaxb directory with the same structure as the package hierarchy. Next, let's specify that the level attribute on the item element should be a number (instead of the default, a String):
<?xml version="1.0"?> <xml-java-binding-schema version="1.0ea"> <options package="javaxml2.jaxb" /> <element name="catalog" type="class" root="true" /> <element name="item" type="class"> <attribute name="level" convert="int" /> </element> </xml-java-binding-schema>
As you can see, I first added an element declaration for the item element. This allows me to reference its level attribute using the attribute construct. To handle the datatype, I specified the type I wanted (int) with the convert attribute.
Continuing with the options that a binding schema supplies, here's a really nice feature. You can actually change the name of a property from what it is in the DTD. For example, I hate methods like getId( ). Instead, I really prefer getID( ), which looks much better. So, what I really want is to name the id property from my DTD as ID in Java. This turns out to be simple with JAXB:
<?xml version="1.0"?> <xml-java-binding-schema version="1.0ea"> <options package="javaxml2.jaxb" /> <element name="catalog" type="class" root="true" /> <element name="item" type="class"> <attribute name="level" convert="int" /> <attribute name="id" property="ID" /> </element> </xml-java-binding-schema>
Once you've made all of these various changes, run the schema compiler (xjc) again. You'll get the modified classes I've been talking about, and now can compile those:
javac -d . javaxml2/jaxb/*.java
If you have any problems, ensure that you still have jaxb-rt-1.0-ea.jar in your classpath.
There are quite a few more options for the binding schema than those discussed here; in fact, many of these were undocumented, and I found them by looking at the xjc.dtd included with JAXB. I suggest you do the same, in addition to reading the supplied documentation. Once you've got your classes generated, it's on to marshalling and unmarshalling.
The process of marshalling and unmarshalling turns out to be the same song, third verse, for this chapter. Since that is the case, I will get right to some code, shown in Example 15-10.
package javaxml2; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; // JAXB Classes import javax.xml.bind.UnmarshalException; // JAXB Generated Classes import javaxml2.jaxb.Catalog; import javaxml2.jaxb.Item; public class Categorizer { public void categorize(File catalogFile) throws IOException, UnmarshalException { // Convert from XML to Java FileInputStream fis = new FileInputStream(catalogFile); Catalog catalog = new Catalog( ); try { catalog = Catalog.unmarshal(fis); } finally { fis.close( ); } // Create new catalogs for the different categories Catalog fingerpickingCatalog = new Catalog( ); Catalog flatpickingCatalog = new Catalog( ); Catalog mandolinCatalog = new Catalog( ); List items = catalog.getItem( ); for (Iterator i = items.iterator(); i.hasNext( ); ) { Item item = (Item)i.next( ); String teacher = item.getTeacher( ); if ((teacher.equals("Doc Watson")) || (teacher.equals("Steve Kaufman"))) { flatpickingCatalog.getItem( ).add(item); } else if (teacher.equals("David Wilcox")) { fingerpickingCatalog.getItem( ).add(item); } else if ((teacher.equals("Sam Bush")) || (teacher.equals("Chris Thile"))) { mandolinCatalog.getItem( ).add(item); } } // Write back out to XML FileOutputStream fingerOutput = new FileOutputStream(new File("fingerpickingCatalog.xml")); FileOutputStream flatpickOutput = new FileOutputStream(new File("flatpickingCatalog.xml")); FileOutputStream mandolinOutput = new FileOutputStream(new File("mandolinCatalog.xml")); try { // Validate the catalogs fingerpickingCatalog.validate( ); flatpickingCatalog.validate( ); mandolinCatalog.validate( ); // Output the catalogs fingerpickingCatalog.marshal(fingerOutput); flatpickingCatalog.marshal(flatpickOutput); mandolinCatalog.marshal(mandolinOutput); } finally { fingerOutput.close( ); flatpickOutput.close( ); mandolinOutput.close( ); } } public static void main(String[] args) { try { if (args.length != 1) { System.out.println("Usage: java javaxml2.Categorizer " + "[XML Catalog Filename]"); return; } // Get access to XML catalog File catalogFile = new File(args[0]); Categorizer categorizer = new Categorizer( ); categorizer.categorize(catalogFile); } catch (Exception e) { e.printStackTrace( ); } } }
There is not a whole lot of interest here; you've seen this several times. However, JAXB does do a few things differently. First, the marshal( ) and unmarshal( ) methods are on the generated classes themselves, rather than on static Marshaller and Unmarshaller classes:
// Convert from XML to Java FileInputStream fis = new FileInputStream(catalogFile); Catalog catalog = new Catalog( ); try { catalog = Catalog.unmarshal(fis); } finally { fis.close( ); }
The generated classes provide static methods that allow for marshalling and unmarshalling. These static methods return an instance of the class with the data from the supplied file filled in. However, you must be sure to assign this return value to an instance variable! An extremely frustrating mistake to make is this:
// Convert from XML to Java FileInputStream fis = new FileInputStream(catalogFile); Catalog catalog = new Catalog( ); try { catalog.unmarshal(fis); } finally { fis.close( ); }
Notice the bolded line: if you try to access the instance variables of the catalog instance after this code snippet, you will get no data, regardless of what's in the supplied XML file. That's because the unmarshal( ) method is static, and returns a live instance of the Catalog class; since that value is never assigned here, it's lost. This can be quite annoying, so watch out! That very issue is actually a case for the external Marshaller and Unmarshaller classes, as used in Castor and Zeus.
In the example, once I have an instance of the XML catalog, I iterate through it. Depending on the teacher, the code adds the item to one of three new catalogs: a flat picking one, a finger picking one, or a mandolin one. Then, each of these new catalogs is marshalled back out to a new XML document. As an example, here's what I got for my mandolinCatalog.xml document:
<?xml version="1.0" encoding="UTF-8"?> <catalog> <item level="3" id="VD-THI-MN01"> <title>Essential Techniques for Mandolin</title> <teacher>Chris Thile</teacher> <description>Here's a lesson that will thrill and inspire mandolin players at all levels.</description></item> <item level="4" id="CDZ-SM01"> <title>Sam Bush Teaches Mandolin Repertoire and Techniques</title> <teacher>Sam Bush</teacher> <description>Learn complete solos to eight traditional and orignal tunes, each one jam-packed with licks, runs, and musical variations.</description> </item></catalog>
The spacing is, as always, added in production, so your line feeds may not match up. However, JAXB's marshalling and unmarshalling is that simple to use. Once you get past the static method issue I just mentioned, it's almost identical to the other two frameworks.
While I encourage you to check out JAXB today, be leery of using it in production until a final release becomes available. There are several undocumented features still floating around that could easily be changed before a final release. Additionally, JAXB does not yet support taking arbitrary objects (ones not generated by JAXB) and marshalling them; this is something the other frameworks do allow for, and might be a drawback for your applications. JAXB also, as mentioned, has no XML Schema support, as well as no namespace support. On the upside, Sun will obviously put a lot of resources into JAXB, so expect it to shape up in the coming months.
In any case, you should have a pretty good taste of what data binding provides by now. The ins and outs of a particular framework are left for you to discover, but with these basics you could pick up any one (or all) of the projects mentioned here and put them to work. Be sure to let whichever project you choose to work with know about what works and what doesn't; it can really affect future development, particularly the open source ones.
Copyright © 2002 O'Reilly & Associates. All rights reserved.