Java XML Mapping (JXM)

JXM

Contents

Installation

This distribution is available as a ZIP archive. After you unzip the distribution file, you will have a jxm directory containing a lib directory of JAR files, a docs directory containing documentation, an Ant build.xml file, and a src directory containing JXM source code. To compile the source, you will need to install the Jakarta Ant build tool.

Use

JXM is used to write Java objects to XML and read them back again, which is called marshalling and unmarshalling. The simplest case when using JXM is to accept the default object to XML mapping. To marshal a Java object using the default mapping follow this example (assuming you are marshalling a "Person" object):

   import com.lifecde.jxm.*;
   import java.io.*;

   public class Test
   {
      public static void main(String [] args) throws Exception
      {
         Object obj = new Person("Ilya");
         Writer out = new OutputStreamWriter(System.out);
         ObjectXMLWriter objectWriter  = new ObjectXMLWriter(out);
         objectWriter.write(obj);
         out.flush();
      }
   }
To run the program, the Java classpath will need to contain the jxm.jar file, the commons-logging.jar file, and the path to your Person (or other class) class file.

Unmarshalling is a bit more complicated, because by default element names do not have enough information to map them to fully qualified (with package names) Java class names. We use the XMLMap object to pass this information to the XML reader (the example assumes the unmarshalled object's class is in the com.lifecde.jxm pacakge):

   import com.lifecde.jxm.*;
   import java.io.*;

   public class Test
   {
      public static void main(String [] args) throws Exception
      {
         Reader in = new InputStreamReader(System.in);
         ObjectXMLReader objectReader  = new ObjectXMLReader();
         XMLMap map = new XMLMap();
         map.setDefaultPackage("com.lifecde.jxm");
         objectReader.setXMLMap(map);
         Object obj = objectReader.parse(in);
      }
   }
To run the program, the Java classpath will need to contain the jxm.jar file, the commons-logging.jar file, the commons-cli-1.0.jar file, XML parser classes, and the path to your Person (or other class) class file.


Default Marshalling

Attributes and Relationships

JXM uses Java Bean conventions to determine what members of an object should be marshalled. The return value of a get<X> accessor method whose return type can easily be represented as a String is written as an XML attribute named "x". Types which are more complex are written as content elements inside a content element named "x", where the element "x" represents the relationship between the marshalled object and its related objects. To understand the default marshalling, let's look at the Person test class in the test/utest/com/lifecde/jxm directory. The Person class has a getName method that returns a String, so the return value becomes an attribute. A person object named "Ilya" is marshalled as

  <person name="Ilya"/>
The Person class also has a accessors named getFriends and getBestFriend which return Person objects. If we use Person objects to represent that Uric's best friend is Juan, and his friends are Clara, Yumiko, and Ilya, the Uric object marshalls as
  <person name="Uric">
    <best_friend>
      <person name="Juan"/>
    </best_friend>
    <friends>
      <person name="Clara"/>
      <person name="Yumiko"/>
      <person name="Ilya"/>
    </friends>
  </person>

Default marshalling is handled by the Marshaller class and the ClassToXMLMap class. Default unmarshalling is handled by the Unmarshaller class and the XMLToClassMap class.

Naming

XML names are formed by changing Java-style capitalization separated words such as "BestFriend" into underscore separated words such as "best_friend". Class names are mapped to element names by removing the package names and changing to underscore separated words. This logic is implemented in XMLNaming. Mapping XML names back to Java names is implemented in JavaNaming.


Customization

You can customize the default behavior by creating sub-classes of Marshaller and Unmarshaller, and registering them with an ObjectXMLReader and ObjectXMLWriter using an XMLMap. The function of the XMLMap is to associate classes with Marshallers, and XML elements with Unmarshallers. An XMLMap can be initialized programmatically, or by reading an XML specification. The XMLMap class defines a static method for unmarshalling maps from XML files, and, for convenience, ObjectXMLReader and ObjectXMLWriter objects can parse XMLMap specifications when given the file name.

Example marshaller and unmarshaller specification in test/data/jxm.xml:

    <jxm>
      <default_package>com.lifecde.jxm</default_package>
      <element name="node"
               unmarshaller="com.lifecde.jxm.NodeUnmarshaller"/>
      <element name="connection" 
               unmarshaller="com.lifecde.jxm.ConnectionUnmarshaller"/>
      <class name="com.lifecde.jxm.Connection" 
             marshaller="com.lifecde.jxm.ConnectionMarshaller"/>
      <class name="com.lifecde.jxm.Graph"
             marshaller="com.lifecde.jxm.GraphMarshaller"/>
    </jxm>

Note that the specification can also declare default packages. If no Unmarshaller is associated with an element, the default Unmarshaller will form fully-qualified class names using the default package names and the XML element name. If a class with the generated name is found, that class is instantiated for unmarshalling.

You write a custom Marshaller by creating a sub-class of Marshaller. The Marshaller defines default writeAttributes and writeContent methods for writing the XML attributes and XML content associated with an Object. An XMLWriter is passed into these methods to handle XML formatting and escape characters.

Writing a custom Unmarshaller is much like implementing a SAX ContentHandler. The Unmarshaller has a readAttributes method which is passed a SAX Attributes object containing the XML attributes for the element. The Unmarshaller add method is called to add objects that were unmarshalled from the element's content.

Finally, you may want to incorporate the marshalling and unmarshalling setup code into Java objects that might be mapped to the root elements of XML documents. It's also easier for users of the classes if any necessary JXM specification files are packaged in the associated JAR file. When this is done, users never need to be aware of the existence of JXM or the specification files. Here is a pattern to follow:


    protected static XMLMap xmlMap;
    
    // load XMLMap during class loading.
    static 
    {
        try
        {
            Reader in =
                new InputStreamReader(Person.class.getResourceAsStream(
                    "jxm.xml"));
            xmlMap = XMLMap.unmarshal(in);  
            in.close();
        }
        // an exception here will prevent the class from loading.  This is     
        // acceptable, because it indicates that the JAR file 
        // was built incorrectly.
        //
        catch (IOException e)
        {
            throw new ExceptionInInitializerError(
                "Unable to read XML marshalling specification.");
        }
        catch (SAXException e)
        {
            throw new ExceptionInInitializerError(
                "Unable to parse XML marshalling specification..");
        }
    }

    public void marshal(Writer out) 
        throws IOException, TranslationException, SAXException
    {
        ObjectXMLWriter wr = new ObjectXMLWriter(out);
        wr.setXMLMap(xmlMap);
        wr.write(this);
    }

    public static Person unmarshal(Reader in) 
        throws IOException, SAXException
    {
        ObjectXMLReader xr = new ObjectXMLReader();
        xr.setXMLMap(xmlMap);
        return (Person) xr.parse(in);
    }

Copyright © 2003, Life Code, Inc.