Section 24.8. JAXB and Code Generation


24.8. JAXB and Code Generation

We've said that the ultimate goal of XML is automated binding of XML to Java classes. With automated binding, no fragile parsing code is created by the developer and just one schema is used for both the document and the object models. While many tools do just this, none is really ideal in terms of capabilities and ease of use. This is not entirely the fault of the tool vendors. Many aspects of mapping XML to Java constructs and back are tricky. For example, if an XML document can contain one of several types of elements at some point, what is the best way to represent that in Java?

Several tools available today bridge XML and Java. The one we'll discuss here is the standard Java API for XML Binding (JAXB, not to be confused with JAXP, the parser API). JAXB is a standard extension that is provided as part of the Java Web Services Developer Pack (JWSDP), though it is also useful outside of the web services arena. You can download JAXB from http://java.sun.com/xml/jaxb. Compiling the examples in this section requires several of the JAR files from the JAXB lib and JWSDP "shared" lib directories to be in your classpath. To simplify this, we've provided an Ant build file along with the example code for these examples. You can find this and all of the source code on the CD accompanying this book.

JAXB generates Java source code. It can work with either DTDs or XML Schema and generates Java classes that match the structure of the XML. At runtime, JAXB-generated classes can read an XML document and parse it into the object model it has defined or you can go the other way, populating your object model and then writing it out to XML. In both cases, JAXB can validate the data to make sure it matches the schema. This may sound like the DOM interface, but, in this case, we're not using generic classeswe're using classes custom-generated for our model. For example, in the case of our zooinventory.xml file, we'll have classes like Inventory, Animal, and FoodRecipe to work with, each having the expected setter and getter methods. This means that our workflow centers more around writing the XML Schema, with the job of creating the classes done for us.

As you might guess, the question of how to customize these generated classes or map them to your own classes if you already have them is a big topic. JAXB defines its own additional "binding" language that allows a great deal of flexibility in these areas as well as in naming of methods, etc. We'll just use the default behavior here. Things don't line up exactly with our previous model, but they are close enough for our purposes.

24.8.1. Generating the Model

Everything in JAXB begins with the schema. We'll be using our W3C XML Schema, but other schema languages may be supported in the future. After installing JAXB, run the JAXB schema compiler, xjc, found in JWSDP_HOME/jaxb/bin on our schema file.

         % xjc.sh -p com.learningjava zooinventory.xsd 

The -p option tells xjc the name of a Java package into which to place the generated classes. Alternately, you can use the Ant build file to do this:

         % ant jaxb 

We use the package name com.learningjava so that our generated files end up in a com/learningjava/ directory.

         com/learningjava/Animal.java         com/learningjava/AnimalType.java         com/learningjava/FoodRecipe.java         com/learningjava/FoodRecipeType.java         com/learningjava/Inventory.java         com/learningjava/InventoryType.java         com/learningjava/Name.java         com/learningjava/ObjectFactory.java         com/learningjava/impl/... 

The first thing to note is that JAXB has generated interfaces for us in com.learningjava and a separate subpackage, impl, which holds the implementation classes. There is an interface for each of the "top-level" elements in our schema. Providing interfaces in this way makes for a clean separation of the API relating to our schema from the particular generated code that JAXB produces to fulfill that API. You only have to work with the interfaces in your code. The objects will be produced by a factory pattern, either created for us when reading a document or by us using the generated ObjectFactory.

The next thing to note is that each of our elements has two interfaces: an element type and content type interface (for example, Animal and AnimalType). These correspond to the difference between the elements and their complex types as we described earlier in this chapter in "XML Schema." Although our schema uses types "anonymously" in only one location, JAXB has automatically created type interfaces for them and named them by appending the suffix Type to the interfaces. AnimalType holds the actual interface of our Animal element (for example, the getName( ) and setName( ) methods).

         public interface AnimalType {             java.lang.String getFood(  );             void setFood(java.lang.String value);             double getWeight(  );             void setWeight(double value);             java.lang.String getHabitat(  );             void setHabitat(java.lang.String value);             java.lang.String getSpecies(  );             void setSpecies(java.lang.String value);             java.lang.String getAnimalClass(  );             void setAnimalClass(java.lang.String value);             java.lang.String getTemperament(  );             void setTemperament(java.lang.String value);             java.lang.String getName(  );             void setName(java.lang.String value);             com.learningjava.FoodRecipeType getFoodRecipe(  );             void setFoodRecipe(com.learningjava.FoodRecipeType value);         } 

The Animal interface extends AnimalType and also the javax.xml.bind.Element flag interface, which indicates that it is an element type. As we described earlier, more than one element may have the same content type. The get methods of the generated classes generally return the "Type" content interface. You can see this in the getFoodRecipe( ) method above. If we had two elements in our example that used a "food recipe" schema type, they could have different element class names but return the same FoodRecipeType. When you are constructing your model to write out XML, you must create the correct element types, using the object factory, at the correct locations. You'll see this in the following examples.

24.8.1.1 Unmarshaling from XML

Taking an object model and writing it out to XML is called marshaling the objects to XML. Doing the converse and parsing an XML file into an object model is called unmarshaling the document. The following example unmarshals our zooinventory.xml file and prints the same bits of information as our previous examples:

         import javax.xml.bind.*;         import com.learningjava.*;         import java.io.File;         import java.util.*;                   public class TestJAXBUnmarshall         {            public static void main( String [] args ) throws JAXBException            {               JAXBContext jbcontext = JAXBContext.newInstance("com.learningjava");               Unmarshaller unmarshaller = jbcontext.createUnmarshaller(  );                         Inventory inventory =                  (Inventory)unmarshaller.unmarshal( new File("zooinventory.xml") );                         System.out.println( "Animals = " );               List<AnimalType> animals = inventory.getAnimal(  );               for( AnimalType animal : animals )                  System.out.println( "\t"+animal.getName(  ) );               AnimalType cocoa = (AnimalType)(inventory.getAnimal(  ).get(1));               FoodRecipeType recipe = cocoa.getFoodRecipe(  );               System.out.println( "Recipe = " + recipe.getName(  ) );               List<String> ingredients = recipe.getIngredient(  );               for( String ingredient : ingredients )                  System.out.println( "\t"+ingredient );            }                   } 

Note how similar this code is to the hand-written code we created for our SAXModelBuilder example. The main difference is the naming of the classes. Here we refer to the content typese.g., AnimalTypeto read the data. The elements that hold collections return Lists, as in our own model. JAXB does not pluralize the setter and getter method names for us, so we have a getAnimal( ) method on our Inventory, which returns the collection of AnimalType objects. The list is mutable and this is how we add and remove Animals.

The process for unmarshaling is simple. We create a JAXBContext object representing our generated classes' package, in this case com.learningjava. We then ask it for an unmarshaler and parse the file. The unmarshaler returns the object corresponding to the root element of our document, Inventory. That's really about all there is to it. If we'd wanted to validate the document while parsing it, we could turn on validation in the unmarshaler:

          unmarshaller.setValidating(true); 

To compile and run the TestJAXBUnmarshall program and our generated classes requires all of the jars of the JWSDP_HOME/jaxb/lib directory, as well as the directory containing our examples and generated code to be in the classpath. The Ant build file also has a compile target that will compile the source for you.

The output of our program is as we'd expect:

         Animals =                 Song Fang                 Cocoa         Recipe = Gorilla Chow                 Fruit                 Shoots                 Leaves 

24.8.1.2 Marshaling to XML

Let's go the other way. In this example, we'll start by building our object model and then marshal it out to XML.

         import javax.xml.bind.*;         import com.learningjava.*;         import java.io.File;         import java.util.*;                   public class TestJAXBMarshall         {                      public static void main( String [] args ) throws JAXBException            {               ObjectFactory factory = new ObjectFactory(  );                         Inventory inventory = factory.createInventory(  );               Animal fang = factory.createAnimal(  );               fang.setName("Song Fang");               fang.setSpecies("Giant Panda");               fang.setHabitat("China");               fang.setFood("Bamboo");               fang.setTemperament("Friendly");               inventory.getAnimal(  ).add( fang );                         JAXBContext jbcontext = JAXBContext.newInstance("com.learningjava");               Marshaller marshaller = jbcontext.createMarshaller(  );               marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);               marshaller.marshal( inventory, System.out );            }         } 

To create our objects, we use the generated ObjectFactory that was created for us in the com.learningjava package. It has a set of methods for creating each of our complex element types. Here, we created an Inventory (it uses its own implementation) and one Animal to populate it. When we've got our model, we use the JAXBContext to get a marshaler object and write it out. We've set a property on the marshaler, JAXB_FORMATTED_OUTPUT, to indicate that we want nicely indented, human-readable XML.

Again, to compile and run the TestJAXBMarshall program and our generated classes requires all of the JARs of the JWSDP_HOME/jaxb/lib directory as well as the directory containing our examples and generated code to be in the classpath. The Ant build file can compile the classes for you with the compile target.

The output of the marshaler looks as you'd expect:

         <?xml version="1.0" encoding="UTF-8" standalone="yes"?>         <Inventory>             <Animal>                 <Name>Song Fang</Name>                 <Species>Giant Panda</Species>                 <Habitat>China</Habitat>                 <Food>Bamboo</Food>                 <Temperament>Friendly</Temperament>             </Animal>         </Inventory> 

Again, that's about all there is to it for this simple case. Of course, to do real work, you will probably want to customize the generated classes in some ways. This can be done in JAXB through the use of special binding descriptor files or through special annotations placed in the XML Schema.

JAXB does, as promised, achieve the goal of binding XML to Java objects without requiring you to write low-level XML code. Although taking the tool beyond these simple examples is not as easy as we'd like, the benefits for a large project can be enormous. There are also other XML binding tools to consider. A popular alternative is Castor (http://www.castor.org), which works in much the same way with XML Schema.

Having worked our way through the options for bridging XML to Java, we'll turn our attention to transformations on XML itself with XSL, the styling language for XML.



    Learning Java
    Learning Java
    ISBN: 0596008732
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net