Many existing mechanisms can handle XML in Java code, including runtime APIs such as DOM and SAX, and data-binding approaches such as JAXB. In all of these approaches, XML support is grafted onto the Java programming language -- the compiler does not understand which portions of a program are processing XML data. As a result, XML processing applications tend to be less robust and efficient than they might be otherwise.
In the XML Enhancements for Java (XJ) project (the initial release is available on IBM alphaWorks), we take a different approach. XJ integrates XML as a first-class construct in Java technology. Programmers can import declarations in W3C XML Schemas as if they were Java classes, they can write inline XPath expressions to navigate XML data, and can construct XML data by writing inline XML (if you are familiar with ECMAScript for XML, the construction aspect of XJ is similar to that technology; see Resources for more on E4X). Since knowledge of XML is embedded in the language, the compiler can check XML processing programs for correctness with respect to XML Schema declarations and perform optimizations so that applications run more efficiently. Compared to XML-based languages such as XSLT, the advantage here is that in XJ, XML processing applications have all of Java technology -- including the use of all existing libraries -- at their disposal. Many transformations are easier to write in XJ than in XSLT.
XJ is a strict extension of Java 1.4. Any valid Java 1.4 program is a valid XJ program. The distribution contains two executables: an xjc compiler that generates class files from XJ source code, and an xj runtime environment for running XJ programs. xjc is an extended javac compiler that understands XML Schema, XPath expressions, and XML. The class files generated by xjc are standard Java class files, which run on any Java Virtual Machine. The xj runtime environment is a thin wrapper around java that adds the XJ runtime libraries to the classpath before invoking java.
In this article, we'll use examples to look at how integrating XML support into the language makes life simpler for XML developers. To summarize the benefits of XJ:
- Familiarity (for the XML programmer): XML processing in XJ is consistent with open XML standards.
- Robustness: XJ programs are strongly typed with respect to XML Schemas. The XJ compiler can detect errors in uses of XPath expressions and construction of XML data.
- Easier maintenance: Since XJ programs are written in terms of XML and not low-level APIs such as DOM or SAX, they are easier to maintain and modify if XML Schemas change.
- Performance: Since the compiler is aware of the use of XML in a program, it can optimize the runtime representation, parsing, and XPath evaluation of XML.
Now that we've gone over the design philosophy behind XJ, let's take a look at the details. We'll use examples to demonstrate the three new constructs added to the language:
- XML Schema declarations can be used as Java types
- XML data can be constructed by writing XML inline
- XPath expressions can be written to navigate XML data
Referring to XML Schema declarations
Imagine if in Java, you could not specify any class names as types of variables or parameters; the only type that you
could declare was Object. If you wrote a method that needed an instance of, say, a
Sales class as an argument, you would be forced to specify that the input parameter was of type Object. The compiler would have no way to verify that only
Sales objects were passed to the method, and all sorts of unforseen errors might arise at
runtime. Programming would be pretty difficult, code would be harder to maintain, and you would have to add comments to each method to remember which classes were allowed as input.
In essence, this is what the world of XML programming looks like to a Java programmer. For example, while programming with DOM, you
can only specify that an input to a method is an Element, but you cannot tell the compiler
that the method only accepts XML that's valid according to sales elements in a given schema.
If your code passes the wrong element to the method, the code compiles fine, but might raise a runtime error or silently
return no results. XJ allows code to be more robust and maintainable by allowing programmers to specify the types of XML
elements handled in a program. The compiler checks to make sure that uses of XML data are correct with respect to XML Schema
constraints.
To help you better understand how XJ integrates XML Schema into Java technology, we'll revisit how the Java language allows programmers to specify type names.
Consider the statement import com.ibm.xj.samples.totals.salesschema.*.
The Java compiler attempts to find a package or a type in the classpath that corresponds to salesschema, and allows all accessible types declared in it to be imported. If the compiler cannot find a package or type that corresponds to salesschema, it raises an error. In XJ, if the compiler cannot find a package or type that corresponds to a name, the compiler attempts to find a corresponding XML Schema file. So, in this example, it attempts to find a
schema called com/ibm/xj/samples/totals/salesschema.xsd using the classpath. If such a schema
exists, then the compiler imports all global element declarations in the XML Schema. In a sense,
an XML Schema corresponds to a package,
global element declarations correspond to top-level classes in a package,
and local element declarations correspond to nested classes within a top-level class.
Now take a look at a fragment of salesschema.xsd:
Listing 1. Sample XML Schema
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="salesdata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="year" type="YearType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
....
<xsd:schema>
|
The statement import com.ibm.xj.samples.totals.salesschema.* indicates that any reference
to salesdata refers to the global element declaration in the schema above.
Similarly, the
XJ type com.ibm.xj.samples.totals.salesschema.salesdata.year refers to the local
declaration of year within salesdata. What this means is that
developers are free to declare variables and parameters of these types. The compiler will make sure that all uses
of these XML Schema-derived types (in XPath expressions, in construction of XML data, and in invocations of methods) are correct.
With runtime libraries such as DOM, construction of XML can be painful. A programmer must build up a tree step-by-step, constructing each node and inserting it into its place in the tree. In constructing elements and attributes, a programmer passes strings to the runtime library. Misspellings and other errors are caught at runtime only if the constructed document is validated. XJ, on the other hand, allows programmers to construct XML data by writing inline XML. Programmers can cut-and-paste the XML they wish to generate and include it in the program. The compiler verifies that the constructed XML is valid with respect to the declared types (and can optimize construction as well). For example, suppose you want to construct the XML fragment below:
Listing 2. XML fragment
<region> <name>central</name> <sales unit="millions">12</sales> </region> |
With DOM, you have to build up the tree (see Listing 3). Notice that all element tag names are passed to the DOM API as strings. It is easy to make a mistake, and the code is hard to decipher.
Listing 3. DOM construction of XML fragment
Element region = doc.createElement("region");
Element name = doc.createElement("name");
Text text = doc.createTextNode("central");
name.appendChild(text);
region.appendChild(name);
Element sales = doc.createElement("sales");
text = doc.createTextNode("12");
sales.appendChild(text);
sales.setAttribute("unit", "millions");
region.appendChild(sales);
|
On the other hand, compare Listing 3 to the XJ code in Listing 4, which is derived directly from the original XML:
Listing 4. XJ construction of XML fragment
region r = new region( <region> <name>central</name> <sales unit="millions">12</sales> </region>); |
XJ allows for easy construction of dynamic XML. For example, consider Listing 5, where the value of sales is calculated dynamically. The braces, "{" and "}", in the literal XML delimit expressions that are evaluated at runtime to construct the XML.
Listing 5. Dynamic XJ construction of XML fragment
int million = 1000000;
int sales = 12000000;
region r = new region(
<region>
<name>central</name>
<sales unit="millions">{ sales / million} </sales>
</region>);
|
In addition to construction from literal XML, XJ allows you to read in XML data from external sources
by providing a java.io.InputStream constructor on every XJ XML class. So for example, new region(is), where is is a variable of type InputStream,
will load the XML data stored in is into memory.
XPath is a powerful language that allows you to write concise expressions for retrieving information from XML data. Runtime APIs, such as DOM, are moving towards allowing programmers to write XPath expressions rather than writing explicit tree traversal code (which can be painful). However, the runtime approach has two shortcomings. First, an XPath is passed to the runtime library as a string. Simple errors in XPath, such as syntax errors, are not caught until runtime. The runtime API cannot detect other errors, such as misspelling the name of an element in the document: The XPath evaluation will return no results. The second problem of runtime APIs is performance. The runtime library must parse and evaluate an XPath expression at runtime. A runtime library has no opportunity to analyze a program and structure the query evaluations in an optimal manner.
XJ solves these problems by introducing knowledge of XPath expressions into the language. The compiler can verify that the evaluation of an XPath on an element is appropriate with respect to the XML Schema type of the element (detecting many errors at compile-time). Furthermore, an upcoming version (to be released shortly) will be able to generate optimized code for evaluation of XPath expressions.
For example, if you wish to find all region elements within a document that satisfy the
property that the sales contained within it is greater than some user-supplied
variable, you could write the following code in XJ:
Listing 6. XPath expression example
salesdata sd = ...;
int min = ...;
Sequence<region> regions = sd [| //region[sales > $min] |];
for (XMLCursor<region> i = regions.iterator(); i.hasMoreElements(); ) {
region r = i.next();
String s = r[| /name |];
System.out.println("Region: " + s);
}
|
In Listing 6, the XPath expression sd [| //region[sales > $min] |]
is evaluated as follows: The expression sd specifies one or more XML elements with
respect to which the XPath expression is evaluated (that is, the context nodes). The delimiters, "[|" and "|]",
specify the XPath expression that is evaluated with respect to the context nodes. Observe that it can refer
to any variable live in the lexical scope (in this example, min). The compiler
ensures that the XPath expression is correct according to specified schemas. In this example, it ensures that salesdata elements have region
descendants, and that they, in turn, have sales children whose contents
can be compared with a variable of type int.
The result of an XPath expression is a Sequence. Currently, XJ supports a limited form
of generics (as in Java 5.0). Therefore, a programmer can declare a variable of type Sequence<region>. The compiler ensures that the result of the XPath expression satisfies this type. You can obtain an XMLCursor from a Sequence. For all intents and purposes, you can use an XMLCursor as a java.util.Iterator, except that you can also use it in a generic fashion. XMLCursor is generic, so the line region r = i.next() does not need a cast, since the compiler realizes that i is of type XMLCursor<region>. The last thing to notice is that XJ supports automatic unboxing of XML elements. So, the line String s = r[| /name |] (which extracts the name child of r) is valid because the compiler can figure out that name elements have content of type xsd:string, and it will automatically generate the code to pull out that value as a java.lang.String.
The integration of XML support into the Java language has many benefits. It allows the compiler to detect errors early and report them to a programmer. When XML Schemas change, the compiler can detect those portions of a program that ought to be modified. Furthermore, the compiler can optimize navigation of XML data, choosing the runtime representation of XML to match the usage of XML. All of these benefits are not possible with runtime APIs such as DOM, and are present only to a limited extent with data binding approaches such as JAXB. Download the XJ package and try it out -- let us know if it makes programming with XML easier (or if it doesn't).
- Download the XJ package from IBM alphaWorks, which also includes a page devoted to
emerging XML technologies.
- Want more background on the XJ language? Visit the XJ Research site, which includes a tutorial.
- You can download the XJ manual and a
technical paper (presented at WWW 2005).
- Read this report from HP Labs outlining some of the problems with current approaches to handling XML in Java.
- Learn more about XPath -- start with the W3C's XPath Recommendation. Also check out "Get Started with XPath" (developerWorks, May 2004) by Bertrand Portier.
- Get an introduction to ECMAScript for XML (E4X). Read the two-part developerWorks series "AJAX and scripting Web services with E4X" (developerWorks, April 2005):
- Part 1 shows you how to use E4X for implementing Web service clients.
- Part 2 demonstrates how E4X scripts can be used to implement Web services under Axis using an E4XProvider.
- Find plenty more resources on XML and Java technology on the developerWorks
XML and
Java technology
zones.
Rajesh Bordawekar is a Research Staff Member at the IBM T. J. Watson Research Center in Hawthorne NY. He joined IBM in 1998 and worked on designing scalable Java Virtual Machines, Java JIT and garbage collection, relational databases, and XML processing. Before joining IBM, Dr. Bordawekar worked as post-doctoral researcher at the California Institute of Technology, investigating distributed and parallel file system for clusters and distributed shared memory machines. He received his Ph.D. from Syracuse University in 1996. His Ph.D. dissertation dealt with optimizing memory-intensive parallel scientific applications. You can contact him at bordaw@us.ibm.com.
Michael G. Burke is a Research Staff Member with the Programming Technologies Department of the IBM T. J. Watson Research Center. His current research interests include integration of Java technology and XML programming, program analysis, and program optimization. He received his Ph.D. in 1983 in computer science from the Courant Institute of New York University. He has been a Research Staff Member at IBM Research since March 1983, where he has managed numerous projects. You can contact him at mgburke@us.ibm.com.
Igor Peshansky is a Post-doctoral Researcher with the Programming Technologies Department of the IBM T. J. Watson Research Center. His current research interests include integration of Java technology and XML programming, componentization of large-scale systems, program analysis, and program optimization. He received his Ph.D. in computer science in 2003 from the Courant Institute of Mathematical Sciences at the New York University. He has been at IBM Research in various capacities since February 1999. You can contact him at igorp@us.ibm.com.
Mukund Raghavachari jumped on the XML bandwagon when it became a buzzword and has never looked back. He has published several technical papers in leading conferences on XML processing techniques and algorithms. He received his Ph.D. in computer science from Princeton University and has been a researcher at IBM T. J. Watson Research Center since 1998. You can contact him at raghavac@us.ibm.com.