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. |
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.
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=
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>
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");
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:
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")));
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. |
<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>
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:
C:\> set JAVA_HOME=C:\jdk1.3.1
% setenv JAVA_HOME /usr/local/java
(csh)> JAVA_HOME=/usr/java; export JAVA_HOME
(ksh, bash)Ok, let's build the code. First, make sure your current working directory is
where the build.xml
file is located. Then type:
.\build.bat
(Windows)./build.sh
(Unix/Cygwin)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 targetsThe 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:
bin\build samples
(Windows)bin/build.sh samples
(Unix/Cygwin)To learn the details of what each target does, read the build.xml file. It is quite understandable.
Here are some relevant links to other data binding resources.
Brett McLaughlin brett.mclaughlin@enhydra.org
Christophe Ney cney@batisseurs.com
Copyright Lutris Technologies Inc - 2001
Copyright ObjectWeb Consortium - 2002