It's no secret that Sun's JAXB, the Java API for XML, has had its fair share of criticism. In its earliest iteration (beta something-or-other), it was based on DTDs, and didn't support W3C XML Schema at all. It sat in that state for over a year, and then suddenly a 1.0 version was released. While that version addressed the schema support, it dropped DTDs completely, and all the code that developers had written (admittedly to beta software) suddenly stopped working. In more recent days, JAXB has picked up some support, but it still remains a very closed environment, with plenty of bugginess in the darker corners of the implementation.
In this article, I'll introduce the JaxMe project, which keeps most of JAXB's good features, and dumps many of its problems. First and foremost, though, JaxMe is open source (and under the Apache umbrella), which means that even if it disappeared today, developers could freely use and even modify its source code -- ensuring that code depending on it will happily go on running. If this alone isn't enough, JaxMe offers database interaction, support for Enterprise JavaBeans, and all sorts of other goodies.
To get you started, I'll walk you through class generation in JaxMe. While this is pretty basic stuff, it should get you familiar enough with JaxMe to look at some of its more interesting features.
Setting up JaxMe is a piece of cake. Visit the JaxMe project site (see Resources for links) and download the binaries from one of the Apache mirrors. As of this writing, the file I downloaded was incubated-jaxme-0.2-bin.tar.gz. (The 0.3 version became available shortly before this went to press; the instructions are the same, but the filename is incubated-jaxme-0.3-bin.tar.gz). Extract this to your development machine. While you can work with JaxMe on the command line, it's a pain (lots of JAR files) -- this article uses Ant to handle JaxMe tasks. You're strongly encouraged to do the same, and all the relevant Ant files are included here, easily modifiable for your own use.
As with JAXB, you'll need some XML before you can do much of anything with JaxMe. Listing 1 shows a very simple XML schema that defines a student. Obviously, this leaves a lot to be desired, but I've kept things simple so you can focus on JaxMe rather than on schema semantics.
Listing 1. Simple student schema
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
xml:lang="EN"
targetNamespace="http://dw.ibm.com/jaxme/student"
xmlns:stu="http://dw.ibm.com/jaxme/student"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
>
<element name="Student">
<complexType>
<sequence>
<element name="firstName" type="string" />
<element name="lastName" type="string" />
<element name="address" type="stu:Address" />
</sequence>
</complexType>
</element>
<complexType name="Address">
<sequence>
<element name="name" type="string"/>
<element name="street" type="string"/>
<element name="city" type="string"/>
<element name="state" type="string"/>
<element name="zip" type="positiveInteger"/>
</sequence>
</complexType>
<complexType name="College">
<sequence>
<element name="name" type="string" />
<element name="address" type="stu:Address" />
</sequence>
</complexType>
</schema>
|
If you've got your Ant build process set up correctly (described in detail at the end of this article), you can just type ant generate to create classes from this schema. I've left these details for the end of the article so that you can focus on JaxMe and its semantics throughout the article, and then look at all the Ant specifics later on. I'd actually recommend you read through the article once, and then work through the code, piece by piece. That way, you'll have the concepts down by the time you're actually entering code, and can probably troubleshoot any problems much more quickly.
All of your generated classes will be placed in the package specified in the targetNamespace attribute. This convention is unique to JaxMe, so you'd do well to understand how it works. Look at the URI provided as an argument to this attribute: http://dw.ibm.com/jaxme/student. This is turned into a package name by the JaxMe schema compiler. First, "http://" is dropped. Then, the host name portion of the URI (in this case, "dw.ibm.com") is actually reversed -- resulting in "com.ibm.dw". That may seem a little odd, but it's actually the typical mechanism for packaging, and should make sense to those of you who are used to developing site-specific classes or beans, particularly tag libraries.
Finally, the remainder of the URI is split on the slash character (/) and appended to the base package that's derived from the host name. So the complete package for the schema in Listing 1 turns out to be com.ibm.dw.jaxme.student. All classes generated from this schema are dropped into this package.
In addition to providing information to JaxMe, the targetNamespace attribute has
some XML-specific implications. It tells the schema processor to put all constructs created (like the Address complex type) in that namespace. This means that you'll need to refer to those constructs as being in that namespace; the long and short of this is that you should define a prefix that maps to that namespace.
Note: If that last sentence didn't make any sense to you, you may need to brush up on your XML, and particularly on how XML Schema works with XML. Consult the Resources at the end of this article (as well as the other articles on the developerWorks XML zone) for more information. For now, you can continue reading, and just trust me -- but you should definitely take the time to fully understand namespaces before considering yourself a JaxMe expert!
In the schema in Listing 1, this was done with the stu prefix. With this prefix, it's easy to define types and then refer to them (using the namespaced prefix) throughout the schema.
You also should be aware that JaxMe works great with the include directive (which is not used in this example). For particularly large schemas, you can segment these definitions into multiple files. Then, in your top-level schema, just reference them as follows:
<!-- Include definitions from another XML Schema --> <include schemaLocation="file:///dev/jaxme/supplemental/datatypes.xsd"/> |
The XML schema processor hands off this information to JaxMe without any distinction of files, so you're advised to use multiple schemas as much as needed.
Once you have generated the classes, take time to familiarize yourself with what's been created. While these are similar to the constructs created by JAXB, you'll find a few subtle differences.
This represents the one XML element (from Listing 1) and is the basic structure for the class hierarchy. It's actually just a very simple interface that extends StudentType (created by JaxMe) and Element, which is part of the JaxMe runtime API. Of course, this class (as well as all other generated classes) is in the com.ibm.dw.jaxme.student package.
Anything named XXXType in JaxMe (where XXX is a name like "Student" or "Address") is the definition derived from a schema. Listing 2 shows this class's source, which turns out to be pretty self-explanatory.
Listing 2. StudentType generated code
package com.ibm.dw.jaxme.student;
public interface StudentType {
public String getFirstName();
public void setFirstName(String pFirstName);
public String getLastName();
public void setLastName(String pLastName);
public AddressType getAddress();
public void setAddress(AddressType pAddress);
}
|
This has accessor (getXXX()) and mutator (setXXX()) methods for all its properties, whether they are simple string types or more complex types like Address. Of course, those types are all represented by classes that end in "Type," which is why you see references to AddressType in the generated listings.
AddressType.java and CollegeType.java
Now that you've seen Listing 2 and StudentType.java, these should be pretty obvious. I'll leave you to examine the source for yourself.
This file is in many ways the bridge between JaxMe and your domain-specific classes. Most important are the methods:
-
newInstance()creates a new element in the domain-specific context and returns it to you. -
createStudentType()creates a new top-levelStudentelement.
Oddly enough, this file isn't mentioned in any of JaxMe's documentation, and isn't used in any of their examples. Personally, I've found it sort of useful, but I wouldn't recommend relying on it. You can perform all the tasks you need without it, and as it's conspicuously missing in documentation, it could easily disappear in a later release.
This XML file handles the mapping from XML to Java code (and back again). It relates elements to classes, fields to properties, and so on. Listing 3 shows the relatively simple mapping file for the sample.
Listing 3. Configuration.xml for example code
<Configuration xmlns="http://ws.apache.org/jaxme/namespaces/jaxme2/configuration">
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.StudentTypeValidator"
qName="{http://dw.ibm.com/jaxme/student}Student"
marshallerClass="com.ibm.dw.jaxme.student.impl.StudentTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.StudentHandler"
elementInterface="com.ibm.dw.jaxme.student.Student"
elementClass="com.ibm.dw.jaxme.student.impl.StudentImpl"/>
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.AddressTypeValidator"
marshallerClass="com.ibm.dw.jaxme.student.impl.AddressTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.AddressTypeHandler"
elementInterface="com.ibm.dw.jaxme.student.AddressType"
elementClass="com.ibm.dw.jaxme.student.impl.AddressTypeImpl"/>
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.CollegeTypeValidator"
marshallerClass="com.ibm.dw.jaxme.student.impl.CollegeTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.CollegeTypeHandler"
elementInterface="com.ibm.dw.jaxme.student.CollegeType"
elementClass="com.ibm.dw.jaxme.student.impl.CollegeTypeImpl"/>
</Configuration>
|
Currently, JaxMe requires you to directly modify this file to change the mapping. The most common change you might want to make is to substitute your own classes for the generated classes. Of course, you'll have to (at least in current versions of JaxMe) generate the default classes and then modify this file. It's also worth noting that this isn't really supported behavior -- the mapping supports it, but it's not particularly well tested or well documented.
Note: In future articles, I may explore this behavior in more detail. If this is of interest to you, please e-mail me or post feedback to this article, and let me know! That's the best way to request coverage of a specific topic.
This is the standard JAXB properties file, of course modified for JaxMe. It is just a single line, and tells the JAXB factory classes to use the JaxMe implementation classes, as follows:
javax.xml.bind.context.factory=org.apache.ws.jaxme.impl.JAXBContextImpl |
For those of you who are familiar with JAXP, this is exactly the way that implementations like Xerces instruct the JAXP architecture to load their implementations.
The various source files in the impl sub-directory are all concrete implementations of the interfaces created by JaxMe. Generally, you don't have to worry about these -- they handle the XML processing of your documents, and conversion to Java equivalents.
For those of you who like to look under the hood, these classes are SAX classes, as JaxMe uses SAX to parse XML. In fact, the Type classes implement (indirectly) SAX's org.xml.sax.ContentHandler interface. You'll see methods like startDocument() and characters() in the source code (which is too long to show here).
Although you probably won't have to mess around with these classes much, it's good to have some basic familiarity with them. You'll use them in your code (which I'll show you shortly), and you'll also find an understanding of them quite helpful in troubleshooting and debugging.
As a final step in looking at these classes, compile them. This may seem obvious, but you wouldn't believe the questions I get that relate to lack of compilation, or class path issues (also covered, a little later). So before you try to work with your classes, be sure to compile them. As always, I find this is easiest to do with Ant, and I just use ant compile to do the trick.
For those of you doing things the hard way (or who just have a passion for typing javac, be sure to include jaxme-api.jar and jaxme.jar in your class path. jaxme-api.jar contains the JAXB API, while jaxme.jar has the JaxMe implementation classes. As output, you should get everything in both the base and impl directories compiled. Finally, you'll want to copy over the support files used: Configuration.xml and jaxb.properties. Those will be important at run time for marshalling and unmarshalling.
You'll find no substitute for a good build tool. It saves you the hassle of dealing with classpath issues over and over again, as well as remembering specific command-line options. This article deals with several tasks, each of which can be handled automatically by Ant. I want to take a little time to let you see my Ant files, so you can incorporate Ant into your own build environment. This also allows me to largely skip this detail in later articles (and cover more meat -- always good, right?).
First, you'll want to use JaxMe's schema compiler/class generation tool, represented by the org.apache.ws.jaxme.generator.Generator interface. So you could conceivably use Ant's java target to fire up a specific instance of this interface. However, that's a bit messy -- you are hard-coding in an implementation, and you have to mess with your build file to change that implementation. You could define the implementation class as a property, but that's still messing with fairly low-level coding issues. Fortunately for those of you using Ant, JaxMe includes an Ant taskdef (task definition) for inserting class generation into your build file, and it takes care of all of these details for you. Just tell Ant you've got a custom task definition, as shown in Listing 4.
Listing 4. Ant taskdef for JaxMe
<path id="classpath.schema-generator">
<pathelement location="${lib}/jaxme2.jar" />
<pathelement location="${lib}/jaxmejs.jar" />
<pathelement location="${lib}/jaxmexs.jar" />
<pathelement location="${lib}/jaxmeapi.jar" />
</path>
<taskdef name="xjc"
classname="org.apache.ws.jaxme.generator.XJCTask"
classpathref="classpath.schema-generator"
/>
|
By including this fragment in an Ant build file, it's trivial to generate classes. You can use the XML shown in Listing 5 to do just that.
Listing 5. Generating classes
<target name="generate" depends="init">
<xjc schema="student.xsd"
target="${dir.generated}">
<produces includes="com/ibm/dw/jaxme/student/*.java" />
</xjc>
</target>
|
With the classes generated, you now need to compile them and copy over your support files. Listing 6 takes care of this task, and even deals with classpath issues.
Listing 6. Compiling classes
<path id="classpath.schema-compiler">
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
<pathelement location="${lib.jaxme}/jaxme2.jar" />
</path>
<target name="compile" depends="generate">
<javac srcdir="${dir.generated}"
destdir="${dir.build}">
<classpath refid="classpath.schema-compiler" />
</javac>
<!-- Copy over support files -->
<copy todir="${dir.build}">
<fileset dir="${dir.generated}">
<include name="**/jaxb.properties" />
<include name="**/Configuration.xml" />
</fileset>
</copy>
</target>
|
As obvious as this should be, it's often helpful to be able to clean up what you've done and start from scratch. While this isn't anything JaxMe-specific, it is worth looking at. The typical way to handle this is through a target called clean, as shown in Listing 7.
Listing 7. Cleaning up
<target name="clean">
<delete dir="${dir.generated}" />
<delete dir="${dir.build}" />
</target>
|
Listing 8 is a larger Ant file that puts all these elements together. It's actually the file I've used throughout, so it works great for everything described here.
Listing 8. Ant taskdef for JaxMe
<?xml version="1.0" encoding="UTF-8"?>
<project basedir=".">
<property name="lib.jaxme" value="/Users/bmclaugh/dev/lib" />
<property name="dir.build" value="build" />
<property name="dir.generated" value="generated" />
<path id="classpath.schema-generator">
<pathelement location="${lib.jaxme}/jaxme2.jar" />
<pathelement location="${lib.jaxme}/jaxmejs.jar" />
<pathelement location="${lib.jaxme}/jaxmexs.jar" />
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
</path>
<path id="classpath.schema-compiler">
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
<pathelement location="${lib.jaxme}/jaxme2.jar" />
</path>
<taskdef name="xjc"
classname="org.apache.ws.jaxme.generator.XJCTask"
classpathref="classpath.schema-generator"
/>
<target name="init">
<mkdir dir="${dir.generated}" />
<mkdir dir="${dir.build}" />
</target>
<target name="generate" depends="init">
<xjc schema="student.xsd"
target="${dir.generated}">
<produces includes="com/ibm/dw/jaxme/student/*.java" />
</xjc>
</target>
<target name="compile" depends="generate">
<javac srcdir="${dir.generated}"
destdir="${dir.build}">
<classpath refid="classpath.schema-compiler" />
</javac>
<!-- Copy over support files -->
<copy todir="${dir.build}">
<fileset dir="${dir.generated}">
<include name="**/jaxb.properties" />
<include name="**/Configuration.xml" />
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="${dir.generated}" />
<delete dir="${dir.build}" />
</target>
</project>
|
You need to change the value of the lib.jaxme property, and then you're ready to go. In this case, you could simply type ant generate to generate the classes from your schema. You'll want to keep utilities like this around (as well as Ant), as it makes compiling and handling tricky class paths a piece of cake.
With a solid understanding of how JaxMe handles class generation, you can easily get started converting to and from XML. I'll tackle that in the next article, and then move on to some JaxMe-specific features like working with databases. Until then, try messing around with mapping and Configuration.xml -- you'll have a good time (probably breaking things once or twice) and really get a handle on how JaxMe works. While you're doing that, I'll be working on the next installment -- see you then!
| Name | Size | Download method |
|---|---|---|
| x-pracdb4-code.zip | 2KB | HTTP |
Information about download methods
- Participate in the discussion forum.
-
Download the code for student.xsd and build.xml.
- Visit the JaxMe Web site to learn more about this new API.
- Visit the Java Architecture for XML Binding (JAXB) page.
- Check out the Apache Incubator, where new and ingenious projects like JaxMe are coming online all the time.
-
Browse for books on these and other technical topics.
- Look at several XML data binding approaches using code generation from W3C XML Schema or DTD grammars for XML documents in Dennis Sosnoski's article "Data binding, Part 1: Code generation approaches -- JAXB and more" (developerWorks, January 2003).
- Obtain text parsing utilities from the Jakarta Commons package.
- Get the scoop on Sun's XML APIs from their Web site.
- Learn how to use JAXB to develop enterprise applications with WebSphere Studio Application Developer V5.1 in this article by Tilak Mitra (developerWorks, February 2004).
- Discover more data binding resources on the developerWorks
XML and Java technology zones.
- Find out how you can become an IBM Certified Developer in XML and related technologies.
Brett McLaughlin has been working in computers since the Logo days. (Remember the little triangle?) He currently specializes in building application infrastructure using Java-related technologies. He has spent the last several years implementing these infrastructures at Nextel Communications and Allegiance Telecom, Inc. Brett is one of the co-founders of the Java Apache project Turbine, which builds a reusable component architecture for Web application development using Java servlets. He is also a contributor to the EJBoss project, an open source EJB application server, and Cocoon, an open source XML Web-publishing engine.




