Enhydra Zeus User's Guide

This document describes the basics of the Enhydra Zeus data binding framework. It includes details on installing and setting up the Zeus framework, instructions for class generation from a set of XML constraints, and information on how to convert from XML to Java, and from Java to XML.


Many of the example commands shown in this guide are only shown on one platform, either Windows, Unix, Cygwin, or Mac OS X. To avoid undue length, commands are not repeated for each platform variant. You will need to convert the commands from the platform shown to the one you yourself use.

Contents

Installation and Setup

There are two common ways to obtain the software required for using Zeus. The first is by downloading the software directly from the Zeus website, http://zeus.enhydra.org. You can download binaries for your specific platform, in either Unix or Windows format. If you download the source code, you will have to compile the tools first.This preliminary step is described in the last section.

Generating Java Source Files with Zeus

The first step in using the Zeus data binding framework is to develop a set of constraints for your XML data. Currently, Zeus only fully supports DTDs as defined in the XML 1.0 specification. Once you have a DTD, you can use that as input for the Zeus source code generation tools. The result of this source generation will be a set of Java source files that can then be compiled into Java classes. These classes will be used to represent your XML structure within Java programs, and to allow conversion between Java and XML.

Once you have your DTD ready, you will need to supply this DTD to the Zeus framework, along with any options needed for generating these source files, and let class generation take place. While there are programmatic ways to accomplish this task, the simplest means of accomplishing this is by using the Zeus command-line tool. zeus.sh and zeus.bat are shells which encapsulate org.enhydra.zeus.util.ZeusCommand, a class generating Java source code. If you've followed the steps outlined in the Installation and Setup section, this shell should be in your bin directory. You can execute the Zeus source generator like this:

C:\dev\Zeus>bin\zeus.bat
Usage: zeus
      -constraints=
     [-outputDir=]
     [-collapseSimpleElements=]
     [-ignoreIDAttributes=]
     [-javaPackage=]
     [-root=]

You can see the various options available to the source generator in this manner. The first argument, specified through the constraints parameter, allows you to specify the DTD file to use for input to class generation, and is required for any operation. The other options are specified and detailed in the following table.

parameter required default value description
constraints yes N/A The path to the constraint file to generate source code from.
outputDir no . (The current working directory) The directory to use as the base directory for generated source files.
collapseSimpleElements no false Whether "simple" elements should be collapsed. A simple element is one which has no attributes, and only textual content (#PCDATA). If the element is collapsed, the text value can be obtained through invoking get[ElementName]() instead of invoking get[ElementName]().getContent(). For example, instead of invoking getFirstName().getContent(), you can simply code getFirstName() and get the textual value of the firstName element.
ignoreIDAttributes no false This specifies if the id attribute is considered when determining if an element is simple. Normally, this (along with any other attribute) will cause an element to be considered non-simple. However, if this parameter is set to true, it is ignored. This is helpful if all your elements have id attributes (such as for use in IDREFs, XLink, and XPointer), but you still want textual elements to be collapsed.
javaPackage no "" (The default package) This allows specification of the Java package to use for the generated source files. For example, specifying com.foo.bar would result in all classes being in the com.foo.bar package upon generation.
root no [determined by the DTD parser] This allows specification of the root element to use. This root element becomes the top-level element in the generated source files. This is generally found through introspection of the DTD by the DTD parser. However, in cases where a DTD has multiple root elements (such as one used for multiple XML document formats), this is required for correct class generation.

So, consider the following simple DTD:

<!ELEMENT songs (song+)>

<!ELEMENT song (title, artist+, instrument+)>

<!ELEMENT title (#PCDATA)>

<!ELEMENT artist (#PCDATA)>
<!ATTLIST artist
          type CDATA #REQUIRED
>

<!ELEMENT instrument EMPTY>
<!ATTLIST instrument 
          type CDATA #REQUIRED
>

You can save this file as song.dtd in the ./samples/dtd directory. To generate source files from this file, you could use the following command:

C:\dev\Zeus>cd samples

C:\dev\Zeus\samples>mkdir classes

C:\dev\Zeus\samples>..\bin\zeus.bat -constraints=dtd/song.dtd -outputDir=classes -javaPackage=samples.dtd -collapseSimpleElements=true

The result of this operation is a set of source files in the newly created classes directory:

./classes/samples/dtd/Artist.java
./classes/samples/dtd/ArtistImpl.java
./classes/samples/dtd/Instrument.java
./classes/samples/dtd/InstrumentImpl.java
./classes/samples/dtd/Song.java
./classes/samples/dtd/SongImpl.java
./classes/samples/dtd/Songs.java
./classes/samples/dtd/SongsImpl.java
./classes/samples/dtd/SongsUnmarshaller.java
./classes/samples/dtd/Title.java
./classes/samples/dtd/TitleImpl.java
./classes/samples/dtd/Unmarshallable.java

As you can see, each element in the DTD results in an interface and implementation source file. You will also see two other files: SongsUnmarshaller.java and Unmarshallable.java. The first of these will always be associated with the root element (songs), and allows the unmarshalling process to start (see the Unmarshalling with Zeus section). The second is generated with each class generation process, and is used to provide a common interface for all classes that can be unmarshalled.

You can compile these classes as shown here

c:\dev\Zeus\samples>cd classes
c:\dev\Zeus\samples\classes>javac -classpath ../../lib/xerces.jar samples/dtd/*.java

At this point, you are ready to use these classes in your own applications.
First, though, you should create a samples XML file that conforms to this DTD:
<?xml version="1.0"?>

<!DOCTYPE songs SYSTEM "samples/DTD/song.dtd">
<songs>
  <song>
    <title>The Finishing Touch</title>
    <artist type="Band">Sound Doctrine</artist>
  </song>

  <song>
    <title>Change Your World</title>
    <artist type="Solo">Eric Clapton</artist>
    <artist type="Solo">Babyface</artist>
  </song>

  <song>
    <title>The Chasing Song</title>
    <artist type="Band">Andy Peterson</artist>
  </song>
</songs>

Unmarshalling with Zeus

One you have your generated classes compiled, it is very simple to convert an XML document into its Java representation. This process is called unmarshalling, and is obviously critical to any data binding implementation. Unmarshalling in Zeus is accomplished through use of the [RootElementName]Unmarshaller class. In the songs example shown above, this is the SongsUnmarshaller class. The method you want to use is simply called unmarshal(); it takes as input a Java File, Writer, or InputStream. This input should wrap the XML to convert into Java objects. The returned value of this method is an object, of the root element's type (in this case, Songs). Here's an example code fragment that demonstrates unmarshalling using Zeus:

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java samples.TestSongsDTD " +
                "[songs.xml location]");
            return;
        }

        try {
            // Unmarshalling
            Songs songs = SongsUnmarshaller.unmarshal(new File(args[0]), false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Once you have taken this step, you can use the resultant Java object(s) like any other Java objects. For example, here is a simple class that reads in an XML document (through unmarshalling), and then iterates through and prints out the contents of the XML. Note that no XML knowledge or expertise is required for this operation.

package samples;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.List;

// Generated classes
import samples.dtd.Artist;
import samples.dtd.Song;
import samples.dtd.Songs;
import samples.dtd.SongsUnmarshaller;

public class TestSongsDTD {

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java samples.TestSongsDTD " +
                "[songs.xml location]");
            return;
        }

        try {
            // Unmarshalling
            Songs songs = SongsUnmarshaller.unmarshal(new File(args[0]), false);

            List songList = songs.getSongList();
            for (Iterator i = songList.iterator(); i.hasNext(); ) {
                Song song = (Song)i.next();
                System.out.println("Song title: '" + 
                    song.getTitle() + "'");
                List artists = song.getArtistList();
                for (Iterator j = artists.iterator(); j.hasNext(); ) {
                    Artist artist = (Artist)j.next();
                    System.out.println("Artist: '" + artist.getValue() + "'");
                    System.out.println("  Type: '" + artist.getType() + "'");
                }
                System.out.println("-------------------------");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If you use the sample XML document shown above, compile the TestSongsDTD.java source file, and run it, you will get the following output:

C:\dev\Zeus\samples\classes>javac -d . -classpath .\;..\lib\xerces.jar ..\TestSongsDTD.java


C:\dev\Zeus\samples\classes>java -classpath ".;..\..\lib\xerces.jar" TestSongsDTD ..\song.xml


Song title: 'The Finishing Touch'
Artist: 'Sound Doctrine'
  Type: 'Band'
-------------------------
Song title: 'Change Your World'
Artist: 'Eric Clapton'
  Type: 'Solo'
Artist: 'Babyface'
  Type: 'Solo'
-------------------------
Song title: 'The Chasing Song'
Artist: 'Andy Peterson'
  Type: 'Band'
-------------------------
Turning on Validation

By default, Zeus will not parse the input XML document as it unmarshals that document. This results in faster processing time in the conversion process. However, you may want to ensure that the input document is validated to ensure that untrusted documents are valid. You can accomplish this through the use of the org.enhydra.zeus.validation system property. Turning on validation can be done like this:

        // Turn on validation
        System.setProperty("org.enhydra.zeus.validation", "true");

Alternatively, you may find that it is easier to set a system property for system-wide unmarshalling. This could be done in a startup program, or through the -D switch on the Java interpreter. In these cases, though, you may find the occasional need to validate a document when the system-wide property is turned off, or ignore validation when normally validation is on. In these cases, you can invoke the unmarshal() method with an additional parameter, a boolean flag, indicating the state of validation. This will affect validation only for the single invocation, and leave the system-wide setting in place for furture unmarshalling processes.

        // Regardless of the system property, unmarshal this once with validation
        Songs songs = SongsUnmarshaller.unmarshal(myInputStream, true);

By default, Zeus will provide a default (albeit simple) SAX ErrorHandler that will report any validation errors, assuming that validation is turned on. However, you are free to provide your own customized ErrorHandler; this should be an instance of a class that implements the org.xml.sax.ErrorHandler interface. If you aren't familiar with SAX, you can visit the Resources section for relevant links.

        // Set a customized error handler
        SongsUnmarshaller.setErrorHandler(myCustomErrorHandler);
        
        // Unmarshal with validation
        Songs songs = SongsUnmarshaller.unmarshal(myInputReader, true);
Changing the Parser

If you have a need to use a parser other than the default that comes with Zeus (Apache Xerces), you can easily accomplish this using Java system properties. The org.xml.sax.driver system property should reference the class to use for Zeus parsing. The Zeus classes will pick up this property on unmarshalling, and use it automatically. So, to change the parser used in the sample program, add the following lines before unmarshal() is invoked:

        // Set the XML parser to use
        System.setProperty("org.xml.sax.driver", 
                           "org.apache.crimson.parser.Parser2");

Marshalling with Zeus

Once you have read in your XML document, and made modifications and changes, you will want to convert the object map in Java back into an XML document. This process is referred to as marshalling, and results in an XML document that mirrors the data in your Java objects. Marshalling in Zeus is a much simpler process than unmarshalling, as there are less options available.

Each unmarshalled object offers the marshal() method for this purpose. There are three versions of this method; the first takes a java.io.File as input, the second a java.io.Writer, and the third a java.io.OutputStream. In all three cases, the output should result in a writable sink for XML data. You would use this in your programs as shown here:

        try {
            // Unmarshalling
            Songs songs = SongsUnmarshaller.unmarshal(new File(args[0]), false);

            List songList = songs.getSongList();
            for (Iterator i = songList.iterator(); i.hasNext(); ) {
                Song song = (Song)i.next();
                
                // Rename songs named 'The Finishing Touch' to 'Call It A Day'
                if (song.getTitle().equals("The Finishing Touch")) {
                    song.setTitle("Call It A Day");
                }
            }
            
            // Marshal back to XML
            songs.marshal(new FileOutputStream(new File("output.xml")));
        } catch (Exception e) {
            e.printStackTrace();
        }

The result of this would be a new file, output.xml, which looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<songs>
  <song>
    <title>Call It A Day</title>
    <artist type="Band">Sound Doctrine</artist>
  </song>
  <song>
    <title>Change Your World</title>
    <artist type="Solo">Eric Clapton</artist>
    <artist type="Solo">Babyface</artist>
  </song>
  <song>
    <title>The Chasing Song</title>
    <artist type="Band">Andy Peterson</artist>
  </song>
</songs>
Setting the encoding

There are a few simple features that you may need to make use of in your marshalling processes. The first is the ability to set the encoding of output document. This is useful in the event where you may use non-standard Java characters, and want to ensure that your resultant XML supports these characters. The most common encoding used in this case is the ISO-8859-1. You may also use a less common variant, like EU-JP. You can ensure that your output document has one of these by using the setOutputEncoding() method on any unmarshalled object:

            // Set encoding
            songs.setOutputEncoding("EU-JP");

            // Marshal back to XML
            songs.marshal(new FileOutputStream(new File("output.xml")));
Setting the DOCTYPE

Another commonly used feature is the ability to set the DOCTYPE declaration on the output document. This is common in two situations:

  1. When XML has been created from scratch using Zeus objects.
  2. When an input document has no DTD, but the output document should.

In both cases, it is simple to handle this case. All generated classes offer the setDoctype() method, which takes three parameters: name, the root element's name; publicID, the public identifier of the DTD; and systemID, the system identifier of the DTD. Using this method will result in the output document having the relevant DOCTYPE declaration inserted at marshal-time.

            // Set encoding
            songs.setOutputEncoding("EU-JP");
            
            // Set DOCTYPE, with no public ID
            songs.setDoctype("songs", null, "DTDs/songs.dtd");

            // Marshal back to XML
            songs.marshal(new FileOutputStream(new File("output.xml")));

The Zeus Task Definition

Zeus provides the org.enhydra.zeus.util.ZeusTask for generating source code from constraints, using Zeus, directly from Ant build files. Please refer to the Ant website for more information. The supported tags are as follows:

The zeus element is the top-level element used to initiate data binding using Zeus. It specifies global options for all constraint generation processes initiated within it (each using the constraint element, see below).

    The <zeus> Tag

attribute required default value description
srcDir no [Current Working Directory] The base directory for constraint files
destDir yes N/A The directory in which generated Java source files should be placed. Note that this will be the base directory, and directories equivalent to the package-level of the generated source code will be nested within this base.
defaultJavaPackage no "" The Java package to use on all constraint generation processes where an explicit Java package is not supplied (using the javaPackage attribute on the constraint element, see below).

The constraint element can be nested inside the zeus element, one or more times. This is used to specify a single constraint generation process (the conversion from one constraint file to one or more Java source files). All options specified here override the default options specified on the zeus element.

    The <constraint> Tag

attribute required default value description
type yes N/A The type of constraint being supplied. Currently, only DTD is supported, with XSD (XML Schema) in an alpha state.
constraintFile yes N/A The path (working from the sourceDir path, specified in the zeus element) to the constraint file to generate source code from.
javaPackage no "" The Java package in which to place generated source code. This overrides any value supplied in the defaultJavaPackage attribute on the zeus element.)
collapseSimpleElements no false Whether or not to collapse simple elements. A simple element is one in which there is only character content; there are no attributes, and no nested elements. Collapsing these elements results in properties that access the simple element's content as get[ElementName]() instead of get[ElementName]().getValue().
ignoreIDAttributes no false Whether or not to ignore ID attributes in deterimining if an element is simple. When IDREFs are used, most elements have ID attributes that have no functional meaning. This allows elements with just an ID attribute to still be collapsed as simple elements.

Example

<zeus sourceDir="${basedir}/doc/dtds" destDir="${basedir}/src">
  <constraint type="DTD"
              constraintFile="web-app_2_2.dtd"
              javaPackage="com.lutris.eas.facility.web.deployment.xml.web_app_2_2"
              collapseSimpleElements="true"
              ignoreIDAttributes="true"
  />
  <constraint type="DTD"
              constraintFile="eas-web_1_0.dtd"
              javaPackage="com.lutris.eas.facility.web.deployment.xml.eas_web_1_0"
  />
</zeus>

Building from Source

More advanced developers may want to try out the latest (and sometimes experimental) features of Zeus, or work with the Zeus code directly. To get the Zeus source code, you should use CVS (the Concurrent Versioning System). Details on obtaining the Zeus source code via CVS are outlined on the Zeus website at zeus.enhydra.org. Once you have obtained the source code, you will need to build Zeus from source, using the Zeus build system.

The Zeus build system is based on Jakarta Ant, which is a Java building tool originally developed for the Jakarta Tomcat project but now used in many other Apache projects and extended by many developers.

Ant is a little but very handy tool that uses a build file written in XML (build.xml) as building instructions. For more information refer to http://jakarta.apache.org/ant/.

The only thing that you have to make sure of is that the JAVA_HOME environment property is set to match the top level directory containing the JVM you want to use. For example:

Building instructions

Ok, let's build the code. First, make sure your current working directory is where the build.xml file is located. Then type:

if everything is right and all the required packages are visible, this action will generate a file called zeus.jar in the ./build/lib directory. Note, that if you do further development, compilation time is reduced since Ant is able to detect which files have changed an to recompile them at need.

Also, you'll note that reusing a single JVM instance for each task, increases tremendously the performance of the whole build system, compared to other tools (i.e. make or shell scripts) where a new JVM is started for each task.

Build targets

The build system is not only responsible for compiling Zeus into a jar file, but is also responsible for creating the HTML documentation in the form of javadocs.

These are the meaningful targets for this build file:

For example, to build and run the samples, type:

To learn the details of what each target does, read the build.xml file. It is quite understandable.

Additional Resources

Here are some relevant links to other data binding resources.

Authors

Brett McLaughlin brett.mclaughlin@enhydra.org
Christophe Ney cney@batisseurs.com

Copyright Lutris Technologies Inc - 2001
Copyright ObjectWeb Consortium - 2002