 | Level: Intermediate Brett McLaughlin (brett@newInstance.com), Author and Editor, O'Reilly Media Inc.
01 Aug 2002 Quick is an open source data binding framework with an emphasis on runtime transformations. This instructional article shows you how to use this framework to quickly and painlessly turn your Java data into XML documents, without the class generation semantics required by other data binding frameworks. Extensive code samples are included. XML has certainly taken the programming world by storm over the last several years. However, the complexity of XML applications, which started out high, has not diminished much in the recent past. Developers still have to spend weeks, if not months, learning the complicated semantics of XML, as well as APIs to manipulate XML, such as SAX and DOM. However, in just the last six to 12 months a new class of XML API, called Quick, has become increasingly popular as a simpler alternative to these more complex APIs.
Data binding allows you to directly map between the Java objects and XML, without having to deal with XML attributes and elements. Additionally, it allows Java developers to work with XML without having to spend hours boning up on XML specifications. Quick is one such data binding API -- a project that's geared toward business use in Java applications. Installation and setup
Before diving into the details of using Quick, you'll need to download and install the project.
Visit Quick's Web site (see Resources ), and select Download.
You can then download a .zip file for the project; as of this writing, the latest version available was
Quick 4.3.1, accessed through the Quick4.3.1.zip file.
Expand the .zip file to create the Quick distribution. The directory hierarchy is shown in
Listing 1:
Quick4
|
+-- JARs
+-- BATs
+-- Doc
+-- dtdParserSrc
+-- DTDs
+-- examples
+-- JARs
+-- QDMLs
+-- QJMLs
+-- quickSrc
+-- UTILs
+-- utilSrc
+-- XSLs
|
The two directories of most interest to developers are Quick4/BATs, which should be
added to your PATH environment variable, and Quick4/JARs, which contains jar files that should be added to your CLASSPATH environment variable. Specifically, you need to add dtdparser115.jar, Quick4rt.jar, and Quick4util.jar to your current class path. You'll also need a SAX parser implementation, such as the Apache project's Xerces-J (see Resources ). Add xerces.jar, or your own favorite parser, to your class path as well.
Java classes and XML documents
Data binding centers around XML and Java, so let's take a look at how these XML documents and Java
classes relate to Quick. To illustrate these points, I look at several simple Java classes,
and a simple XML document. A simple XML document
First, Listing 2 shows a small XML document. I've kept things simple so that you
don't miss out on the concepts by wading through 10 or 15 Java classes.
<?xml version="1.0"?>
<!DOCTYPE person SYSTEM "person.dtd">
<person>
<firstName>Gary</firstName>
<lastName>Greathouse</lastName>
<address type="home">
<street>10012 Townhouse Drive</street>
<city>Waco</city>
<state>TX</state>
<zipCode>76713</zipCode>
</address>
<phoneNumber>
<type>home</type>
<number>2545550287</number>
</phoneNumber>
<phoneNumber>
<type>work</type>
<number>2545556127</number>
</phoneNumber>
</person>
|
Listing 2, while not a prime example of how to write XML, brings out several Quick points that are worth noting. You'll also want to take a look at the DTD for the document, shown in Listing 3.
<!ELEMENT person (firstName, lastName, address+, phoneNumber+)>
<!ELEMENT firstName (#PCDATA)>
<!ELEMENT lastName (#PCDATA)>
<!ELEMENT address (street, city, state, zipCode)>
<!ATTLIST address
type (home | work | other) "home"
>
<!ELEMENT street (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zipCode (#PCDATA)>
<!ELEMENT phoneNumber (type, number)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT number (#PCDATA)>
|
The Java classes
In many data binding implementations, you would now need to generate Java source files to
represent this type of XML document. That sort of implementation assumes, generally falsely, that you
lack the Java business objects you want to use. More commonly, you have a set of Java classes that you want to
begin persisting to XML. This use case is exactly what Quick helps to solve. In light of that, then,
Listings 4, 5, and 6 are three Java source files that represent a person.
import java.util.LinkedList;
import java.util.List;
public class Person {
/** The first name of the person */
private String firstName;
/** The last name of the person */
private String lastName;
/** The addresses of the person */
private List addressList;
/** The phone numbers of the person */
private List phoneNumberList;
public Person() {
addressList = new LinkedList();
phoneNumberList = new LinkedList();
}
public Person(String firstName, String lastName,
List addressList, List phoneNumberList) {
this.firstName = firstName;
this.lastName = lastName;
this.addressList = addressList;
this.phoneNumberList = phoneNumberList;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List getAddressList() {
return addressList;
}
public void setAddressList(List addressList) {
this.addressList = addressList;
}
public void addAddress(Address address) {
addressList.add(address);
}
public List getPhoneNumberList() {
return phoneNumberList;
}
public void setPhoneNumberList(List phoneNumberList) {
this.phoneNumberList = phoneNumberList;
}
public void addPhoneNumber(PhoneNumber phoneNumber) {
phoneNumberList.add(phoneNumber);
}
}
|
public class Address {
/** The type of address */
private String type;
/** The street address */
private String street;
/** The city */
private String city;
/** The state */
private String state;
/** The zip code */
private String zipCode;
public Address() { }
public Address(String type, String street, String city,
String state, String zipCode) {
this.type = type;
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
|
public class PhoneNumber {
/** The number itself */
private String number;
/** The type of number */
private String type;
public PhoneNumber() { }
public PhoneNumber(String type, String number) {
this.type = type;
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
|
These classes should be fairly self-explanatory. They are the analogs of the XML structure you
saw back in Listing 2. Presumably, you will want to convert XML documents to instances of these Java objects, and back again. Quick makes these conversions a piece of cake.
Initialization
Once you have your XML document (or documents), your DTD, and your Java classes, you need
to tell the Quick framework how to map data from one format to the other. This is a multi-step
process:
- Create a QDML file from the XML document's DTD
- Create a QJML file from the QDML document
- Create a QIML file from the QJML document
- Use the QIML to map Java to XML and back again
I'll show you how to accomplish each step in turn in the next several sections. While this may seem like a lot of steps to get Quick going, you should note that each of these only needs to occur one time so this is an up-front cost for using the Quick framework. Once you have taken care of these steps, you can use Quick in your applications as many times as you want, without this process overhead.
Creating QDML
The first thing you need to do in getting Quick ready to roll is create a QDML file. QDML (Quick Document Markup Language) is essentially Quick's version of a DTD, and defines
the XML document's structure for the Quick framework. At this point, you aren't providing any mapping
information; you're just defining your document in a format that Quick can understand. Of course, this
is accomplished through a Quick tool, which makes life easier for developers.
First, make sure that your class path is set up as indicated in Installation and setup. You can then use the
cfgDtd2Qdml script, located in the Quick distribution's
BATs directory. For Windows users, you would use cfgDtd2Qdml.bat; for Unix users,
use cfgDtd2Qdml.sh. (The examples in this article are all in Unix format, but you can easily
accomplish the tasks on Windows.) Issue the following command: sh cfgDtd2Qdml.sh -in=person.dtd -out=person.qdml |
You won't see much exciting in terms of output, but you should get a new file,
called person.qdml. With your DTD in a format that's more easily understood by
Quick, you're almost ready to move to the next step. Before moving on, you need to let Quick (and the QDML file it uses) know the root element of your XML document. In this case, it's the person element. To do this, use another Quick utility: sh cfgSetQdmlRoot.sh -in=person.qdml -out=person.qdml -root=person |
Creating QJML
You now need to create a QJML file for Quick to use. QJML is the
Quick Java Markup Language, and is the equivalent of a binding schema, for
those of you familiar with JAXB or other data binding implementations. Quick uses QJML to convert the constructs in your XML file to their Java counterparts, and vice versa.
It's possible to create a QJML file from scratch; however, Quick supplies a
tool for generating one automatically, and you generally only need to make minimal
changes. For that reason, this is the recommended approach. Use either the
cfgQdml2Qjml.bat or cfgQdml2Qjml.sh script to accomplish this,
supplying your newly-generated QDML file as input (Quick reads the QDML to determine
the constructs to map; see why you needed that file now?): sh cfgQdml2Qjml.sh -in=person.qdml -out=person.qjml |
You should now have a new file, person.qjml. As I've said, you will need to make some changes to this file, since
Quick makes some problematic assumptions about Java variable names. Open
up your file, and add the bold coding so it resembles Listing 7.
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE qjml SYSTEM "classpath:///qjml.dtd">
<qjml root="person">
<bean tag="person">
<targetClass>Person</targetClass>
<elements>
<item coin="firstName">
<property name="firstName"/>
</item>
<item coin="lastName">
<property name="lastName"/>
</item>
<item coin="address" repeating="True">
<property kind="list" name="addressList"/>
</item>
<item coin="phoneNumber" repeating="True">
<property kind="list" name="phoneNumberList"/>
</item>
</elements>
</bean>
<text tag="firstName"/>
<text tag="lastName"/>
<bean tag="address">
<targetClass>Address</targetClass>
<attributes>
<item coin="address.type" optional="True" value="home">
<property initializer="home" name="type"/>
</item>
</attributes>
<elements>
<item coin="street">
<property name="street"/>
</item>
<item coin="city">
<property name="city"/>
</item>
<item coin="state">
<property name="state"/>
</item>
<item coin="zipCode">
<property name="zipCode"/>
</item>
</elements>
</bean>
<text label="address.type" tag="type">
<enum value="home"/>
<enum value="work"/>
<enum value="other"/>
</text>
<text tag="street"/>
<text tag="city"/>
<text tag="state"/>
<text tag="zipCode"/>
<bean tag="phoneNumber">
<targetClass>PhoneNumber</targetClass>
<elements>
<item coin="type">
<property name="type"/>
</item>
<item coin="number">
<property name="number"/>
</item>
</elements>
</bean>
<text tag="type"/>
<text tag="number"/>
</qjml>
|
These changes should be pretty obvious when you compare the original file
to the one shown in Listing 7; they all involve mapping the XML property to the
correct Java property name, as defined in the source code (see Listings 4, 5, and 6 for a refresher on these). Creating QIML
You're almost done with the setup work; you still need to perform one final step. Quick performs much better with a QIML file -- the Quick Internal Markup Language. As the name implies, Quick uses this internal format to avoid runtime compilation, conversion, and processing of QJML files. Perform the following simple step to put your QJML into a format that Quick can use more handily: sh cfgQjml2Qiml.sh -in=person.qjml -out=person.qiml |
Finally, to improve performance even further, turn this text-based format into
a Java source file that you can compile (binary objects are always better to work
with than non-compiled textual file formats): sh cfgQiml2Java.sh -in=person.qiml -out=PersonSchema.java
-class=PersonSchema -key=person.qjml |
You really don't need to worry too much about what is happening here; it's all
Quick-specific, and the utilities take care of things. You should now compile
this created source (and the rest of your Java source if you haven't already),
and make sure everything is added to your classpath. With all these steps
complete, you're ready to do some data binding.
Data binding
With all the setup work done, it is now trivial to use the Quick framework to
convert your XML document into Java bytecode and then back again. I'm going to keep
the example for this simple; once you've got the basics down,
the uses of data binding become limitless, and rather than assume I know your
use-case, I'll just let you use the information you need from the sample code and take
off! Take a look at Listing 8, and then I'll explain the main concepts.
import java.util.Iterator;
// Quick imports
import com.jxml.quick.QDoc;
import com.jxml.quick.Quick;
public class PersonTest {
public static void main(String[] args) {
try {
if (args.length != 2) {
System.err.println("Usage: java PersonTest [input file] [output file]");
return;
}
// Initialize Quick
QDoc schema = PersonSchema.createSchema();
// Convert input XML to Java
QDoc doc = Quick.parse(schema, args[0]);
// Get the result
Person person = (Person)Quick.getRoot(doc);
// Output block
System.out.println(" --------------- Person ------------------ ");
System.out.println(" First Name: " + person.getFirstName());
System.out.println(" Last Name : " + person.getLastName());
for (Iterator i = person.getAddressList().iterator(); i.hasNext(); ) {
Address address = (Address)i.next();
System.out.println(" Address (" + address.getType() + "):");
System.out.println(" " + address.getStreet());
System.out.println(" " + address.getCity() + ", " + address.getState() +
" " + address.getZipCode());
}
for (Iterator i = person.getPhoneNumberList().iterator(); i.hasNext(); ) {
PhoneNumber number = (PhoneNumber)i.next();
System.out.println(" Phone Number (" + number.getType() + "):");
System.out.println(" " + number.getNumber());
}
// Add a new address
Address address =
new Address("work", "357 West Magnolia Lane", "Waco", "TX", "76710");
person.getAddressList().add(address);
// Change a phone number
PhoneNumber number = (PhoneNumber)person.getPhoneNumberList().get(1);
number.setNumber("2547176547");
// Write out modified XML
Quick.express(doc, args[1]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
The portion of this code that deals with Quick turns out to be only about
four lines! You first must load the QIML (compiled as Java bytecode) so
Quick knows which XML elements and attributes become which Java classes
and properties. Do this through the createSchema()
method (which is static) of the generated PersonSchema
class. Once that schema is loaded, it and the input file are handed off to the
Quick.parse() method, which does the conversion. From
there, you simply have to grab the root element of the resulting
QDoc and work with it as you would any other Java
object. Quick enters the picture again in the last bit of the code where the express() method outputs a modified version of the XML. Pretty easy, isn't it? Note: Do not supply the same filename for both input and
output, as you will overwrite your original data, and can cause all sorts
of unexpected things to happen.
Conclusion
Ideally, you've seen some really intriguing functionality here. First, data binding
in general can greatly simplify programming tasks, especially when you need to
persist data to some type of static storage (like a file, as shown in this article).
Additionally, Quick provides a fast, simple way to achieve this in your own projects.
Take a spin around the block with Quick, and see how you like it. Enjoy, and I'll
see you online!
Download | Name | Size | Download method |
|---|
| x-quick/quick.zip | | HTTP |
Resources
About the author  | 
|  |
Brett McLaughlin has been working in computers since the Logo days (Remember the little triangle?). He currently specializes in building application infrastructure using Java and 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. |
Rate this page
|  |