The first data binding framework I will discuss is Castor, hosted online at http://castor.exolab.org. This framework has been around for quite a while, and the latest release as of this writing was Version 0.92. First, it should be made clear that Castor provides quite a bit more than just XML data binding. The package provides bindings in Java for more than just XML; you can also work with LDAP objects, OQL for mapping SQL queries to objects, as well as Java Data Objects (JDO), a fairly new specification from Sun dealing with Java-to-RDBMS (relational database management system) persistence. However, this is an XML book, so I'm only going to talk about the XML bindings.
To get ready to use Castor, you'll need to download a release from the download page: http://castor.exolab.org/download.html. That page has links to the Exolab FTP site (or you can FTP in manually, as I did), and lists the files available. I'd recommend getting the full release (in the event you want to play with OQL or JDO later), named castor-0.9.2.zip or castor-0.9.2.tgz. Extract the jar files from that archive, add them to your classpath, and you're ready to go.[27]
[27]Actually, there are two jar files within the distribution: castor-0.9.2.jar and castor-0.9.2-xml.jar. The first is a superset of the second, so you only need the first; or if you want a smaller archive, you can just use the second for XML binding.
NOTE: In this and subsequent examples, I've assumed that you still have a SAX-compliant XML parser, like Xerces, on your classpath in addition to the libraries discussed in this chapter. If you don't, add xerces.jar or your parser's jar file(s) to the classpath in addition to the data binding framework you are using.
Castor does provide for class generation, using an existing set of constraints to create Java classes. You will need to have an XML Schema that constrains your XML to use this. Example 15-2 provides just such a schema for the document I showed you earlier in Example 15-1.
<?xml version="1.0"?> <schema xmlns="http://www.w3.org/2000/10/XMLSchema" targetNamespace="http://www.homespuntapes.com"> <element name="catalog"> <complexType> <sequence> <element ref="item" minOccurs="1" maxOccurs="unbounded" /> </sequence> </complexType> </element> <element name="item"> <complexType> <sequence> <element name="title" type="string" /> <element name="teacher" type="string" /> <element name="guest" type="string" minOccurs="0" maxOccurs="unbounded" /> <element name="description" type="string" /> </sequence> <attribute name="id" type="string" /> <attribute name="level"> <simpleType> <restriction base="integer"> <enumeration value="1" /> <enumeration value="2" /> <enumeration value="3" /> <enumeration value="4" /> <enumeration value="5" /> </restriction> </simpleType> </attribute> </complexType> </element> </schema>
Obviously, you may have XML schemas of your own that you want to try out; as long as they conform to the XML Schema specification, they should work with any examples in this section.
WARNING: At least as of this writing, Castor supported only the October 2000 XML Schema Candidate Recommendation, as opposed to the final version of that specification. This could require you to make some small modifications in your existing schemas to conform to that earlier specification if you're using Castor. Hopefully that framework will have caught up by the time you are reading this; you can verify the current level of compliance for XML Schema at http://castor.exolab.org/xmlschema.html.
Once you have your XML Schema defined, you are ready to generate classes for the constraints. I've named my schema from Example 15-1 catalog.xsd, as you'll see reflected in the example instructions coming up.
Once you've got your schema, generating classes with Castor is a piece of cake. You'll need to use the org.exolab.castor.builder.SourceGenerator class, as shown here:
java org.exolab.castor.builder.SourceGenerator -i castor/catalog.xsd -package javaxml2.castor
In this example, I'm running the command with my schema in a subdirectory of the current directory, called castor/. I specified the schema with the "-i" flag, and the package to generate the files within through the "-package" flag. There's a whole slew of other options you can check out by simply entering the class without any options. The class will spit out the various flags and options you can supply.
Once the command executes (you'll get errors if your schema has problems), you will get a directory path correlating to the package you entered. In my example, I ended up with a javaxml2 directory, and a castor directory within that. Within that directory, I ended up with a Catalog.java and CatalogDescriptor.java source file, and an Item.java and ItemDescriptor.java source file. For most situations, you'll only need to worry about working with the first of each of these pairs.
You should also get a subdirectory called types, with some additional files within it. These are generated because of the user-defined type in the XML Schema for the "level" attribute. The result is a class called LevelType. Since there are only five allowed values, Castor must create custom classes for this type to handle it. These type classes are a pain to work with, as there is no way, for example, to do this:
// Create a new type with a value of "1" LevelType levelType = new LevelType(1);
Instead, you'll need to get the value you want to use and convert it to a String. You can then use the valueOf( ) method, which is static, to get an instance of LevelType with the correct value:
LevelType levelType = LevelType.valueOf("1");
Of course, once you get used to this, it's not such a big deal. If this seems a little fuzzy, you'll see how to use this class in a practical situation in the next section, so don't worry too much about it just yet. You can compile the type files, as well as the other Castor-generated sources, with this simple command:
javac -d . javaxml2/castor/*.java javaxml2/castor/types/*.java
At this point, you have classes that are ready to use. I won't show you the source for these files here, because it's quite long (and you can look at it yourself). I've listed the key methods for the Catalog class, though, so you'll get an idea of what to expect:
package javaxml2.castor; public class Catalog { // Add a new Item public void addItem( ); // Get the items as an Enumeration public Enumeration enumerateItem( ); // Get all items public Item[] getItem( ); // Get number of items public getItemCount( ); }
Notice that you can add items, as well as move through the items available. The names of two of these methods, enumerateItem( ) and getItem( ) , are a bit odd, so watch out for those. I did not expect getItem( ) to return an array, and looked for getItems( ) or getItemList( ) first, myself. Once you've got these generated classes, though, you're ready to use them in your application.
After you've compiled the classes Castor generates, add them to your classpath. You can then use them in your own applications. Example 15-3 shows a basic HTML form that allows a user to enter information about a new item.
<HTML> <HEAD><TITLE>Add New Item to Catalog</TITLE></HEAD> <BODY> <H2 ALIGN="CENTER">Add New Item</H2> <P ALIGN="CENTER"> <FORM ACTION="/javaxml2/servlet/javaxml2.AddItemServlet" METHOD="POST"> <TABLE WIDTH="80%" CELLSPACING="3" CELLPADDING="3" BORDER="3"> <TR> <TD WIDTH="50%" ALIGN="right"><B>Item ID:</B></TD> <TD><INPUT TYPE="text" NAME="id" /></TD> </TR> <TR> <TD WIDTH="50%" ALIGN="right"><B>Item Level:</B></TD> <TD><INPUT TYPE="text" NAME="level" SIZE="1" MAXLENGTH="1" /></TD> </TR> <TR> <TD WIDTH="50%" ALIGN="right"><B>Title:</B></TD> <TD><INPUT TYPE="text" NAME="title" SIZE="20" /></TD> </TR> <TR> <TD WIDTH="50%" ALIGN="right"><B>Teacher:</B></B></TD> <TD><INPUT TYPE="text" NAME="teacher" /></TD> </TR> <TR><TD COLSPAN="2" ALIGN="CENTER"><B>Guests:</B></TD></TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD> </TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD> </TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD> </TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD> </TR> <TR><TD COLSPAN="2" ALIGN="CENTER"><B>Description:</B></TD></TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"> <TEXTAREA NAME="description" COLS="30" ROWS="10"></TEXTAREA> </TD> </TR> <TR> <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="submit" value="Add Item" /></TD> </TR> </TABLE> </FORM> </P> </BODY> </HTML>
You should have Tomcat or another servlet engine running from some of the previous chapters, so I won't get into those details. Drop this form into one of your web applications, and then enter in and compile the servlet shown in Example 15-4.
package javaxml2; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; // Castor classes import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.Unmarshaller; // Castor generated classes import javaxml2.castor.Catalog; import javaxml2.castor.Item; import javaxml2.castor.types.LevelType; public class AddItemServlet extends HttpServlet { private static final String CATALOG_FILE = "c:\\java\\tomcat\\webapps\\javaxml2\\catalog.xml"; public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { PrintWriter out = res.getWriter( ); res.setContentType("text/html"); // Get input parameters String id = req.getParameterValues("id")[0]; String levelString = req.getParameterValues("level")[0]; String title = req.getParameterValues("title")[0]; String teacher = req.getParameterValues("teacher")[0]; String[] guests = req.getParameterValues("guest"); String description = req.getParameterValues("description")[0]; // Create new item Item item = new Item( ); item.setId(id); item.setLevel(LevelType.valueOf(levelString)); item.setTitle(title); item.setTeacher(teacher); if (guests != null) { for (int i=0; i<guests.length; i++) { if (!guests[i].trim( ).equals("")) { item.addGuest(guests[i]); } } } item.setDescription(description); try { // Load current catalog File catalogFile = new File(CATALOG_FILE); FileReader reader = new FileReader(catalogFile); Catalog catalog = (Catalog)Unmarshaller.unmarshal(Catalog.class, reader); // Add item catalog.addItem(item); // Write back out modified catalog FileWriter writer = new FileWriter(catalogFile); Marshaller.marshal(catalog, writer); out.println("Item added."); } catch (Exception e) { out.println("Error loading/writing catalog: " + e.getMessage( )); } finally { out.close( ); } } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } }
This servlet accepts the parameters from the form shown in Example 15-3. It first reads in the XML representing the current catalog (called catalog.xml and also in my servlet's web application folder). At this point, the servlet needs to access the current catalog; of course, I could write a bunch of SAX code here, but why? Castor does the job nicely. I use a FileReader to provide read access to the XML document, and a FileWriter to provide write access. The rest of the work is taken care of by Castor. Once the servlet gets the form values, it creates a new Item instance (using Castor's generated classes) and sets the various values on this class. You'll notice that because "level" is a custom type (remember the earlier discussion?), the servlet uses the static method LevelType.valueOf(String) to convert the String value for the item level into the correct instance of the LevelType class. This is one of Castor's minor drawbacks; the custom classes for user-defined types are a bit unwieldy until you get used to them.
Once the servlet has a ready-to-use instance of a new Item, it uses the org.exolab.castor.Unmarshaller class to get the current catalog. This couldn't be much simpler; the servlet passes in the class to unmarshal to, and access to the file (through the FileReader I just mentioned). The result is a Java Object, which can be cast to the class type supplied. At this point, adding the item is a piece of cake! You're working in Java, rather than in XML, and can simply invoke addItem( ) and pass in the newly created Item instance. Then, the process is reversed. Marshaller (in the same package as its sister Unmarshaller) is used via the static marshal( ) method to write the Catalog instance back to XML, using a FileWriter. Piece of cake, isn't it? Once this process completes, you get a new entry in the XML file (you may have to stop your servlet engine to get access to it), which should look something like this:
<item id="CD-KAU-PV99" level="1"> <title>Parking Lot Pickers, Vol. 3</title> <teacher>Steve Kaufman</teacher> <guest>Debbie Barbra</guest> <guest>Donnie Barbra</guest> <description>This video teaches you what to play when the singing stops, bluegrass style!</description> </item>
And that's really it. There are quite a few more options to use with the Marshaller and Unmarshaller, so check out the API documentation. And, just to make sure you can follow along, here are the pertinent files and directories in my web application that make this all work:
$TOCAT_HOME/lib/xerces.jar $TOMCAT_HOME/webapps/javaxml2/addItem.html $TOMCAT_HOME/webapps/javaxml2/catalog.xml $TOMCAT_HOME/webapps/javaxml2/WEB-INF/lib/castor-0.9.2.jar $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/AddItemServlet.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Catalog.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Catalog.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ CatalogDescriptor.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ CatalogDescriptor$1.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Item.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$1.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$2.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$3.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$4.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$5.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/ ItemDescriptor$6.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/types/ LevelType.class $TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/types/ LevelTypeDescriptor.class
There's a lot more to Castor, as there will be to each of the packages I talk about. This short introduction should get you started, and the documentation provided will help you through the rest. Of particular interest is the ability to define mappings that allow you, for example, to convert an XML element named "item" to a Java variable named "inventory". This allows for different representations of the same data within Java and XML, and can also really help you convert between legacy Java classes. Imagine converting an old Java class to a new XML format, and then unmarshalling that new XML back into new Java classes. In two easy steps, all of your old Java code is updated to a new format! Pretty slick, huh? On the other hand, Castor's lack of support for the latest schema recommendation and generation of concrete classes instead of interfaces is a drawback. Try it out for yourself, though, and see how you like it. Each of the frameworks in this chapter has pros and cons, so pick the one that's right for you.
Copyright © 2002 O'Reilly & Associates. All rights reserved.