ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using the Jakarta Commons, Part 2
Pages: 1, 2

XML Category

The XML category contains the components that are related to Java AND XML in some particular way. This includes Betwixt, Digester, Jelly, and JXPath.



Betwixt

Summary: Provides a mapping between XML and JavaBeans.

Where: Main Page, Binaries, Source.

When: When you require a data binding framework for mapping beans to XML in a flexible way, without worrying about a schema.

Example Applications: BetwixtDemo.java, Mortgage.java, mortgage.xml require commons-betwixt-1.0-alpha-1.jar, commons-logging.jar, commons-beanutils.jar, commons-collections.jar, and commons-digester.jar in the CLASSPATH.

Description:

If you have used Castor for data binding before, you will appreciate the flexibility Betwixt provides. While Castor is good if you want to work against a well-defined schema for converting your beans to and from XML, Betwixt is good if all you are worried about is converting your data to XML or vice versa. It is flexible and allows you to output the data in human-readable XML.

Using Betwixt is quite simple. To convert a bean to XML, instantiate a BeanWriter, set its properties, and output it. To convert XML to a bean, instantiate a BeanReader, set its properties, and convert it using Digester.

Converting a bean to XML:

// to use Betwixt, create an instance of a BeanWriter
// since a constructor for BeanWriter takes an instance of a
// writer object, we will start by writing to a StringWriter
StringWriter outputWriter = new StringWriter();

// note that the output is not well formed and therefore needs the
// following at the top:
outputWriter.write("<?xml version='1.0' ?>");

// create the BeanWriter
BeanWriter writer = new BeanWriter(outputWriter);

// now for this writer, we can set various parameters
// the first one disables writing of IDs and the second one enables
// formatted output
writer.setWriteIDs(false);
writer.enablePrettyPrint();

// finally create a bean and write it out
Mortgage mortgage = new Mortgage(6.5f, 25);

// output it to standard output
try {
    writer.write("mortgage", mortgage);
    System.err.println(outputWriter.toString());
} catch(Exception e) {
    System.err.println(e);
}

Converting XML to a bean:

// to see how Betwixt can read XML data and create beans based on it
// we will use the BeanReader class. Note that this class extends
// the Digester class of the Digester package.
BeanReader reader = new BeanReader();

// register the class
try {
    reader.registerBeanClass(Mortgage.class);

// and parse it Mortgage mortgageConverted = (Mortgage)reader.parse(new File("mortgage.xml")); // Let's see if this converted mortgage contains the values in the file System.err.println("Values in file: Rate: " + mortgageConverted.getRate() + ", Years: " + mortgageConverted.getYears()); } catch(Exception ee) { ee.printStackTrace(); }

Note that while registering the class with the reader, if the top-level element doesn't have the same name as the class name, you will have to use a different method that specifies the exact path. In that case, use reader.registerBeanClass("toplevelelementname", Mortgage.class).

Digester

Summary: Provides developer-friendly, high-level, and event-driven processing of XML documents.

Where: Main Page, Binaries, Source.

When: When you want to process your XML documents and have the ability to perform useful functions based on a set of rules that are triggered by particular patterns within your XML document.

Example Applications: DigesterDemo.java, Employee.java, Company.java, rules.xml and company.xml require commons-digester.jar, commons-logging.jar, commons-beanutils.jar, and commons-collections.jar in the CLASSPATH.

Description:

Digester is most useful in parsing configuration files. In fact, Digester was first built for reading the Struts configuration file, and was later moved to the Commons package.

Related Reading

Java & XML Data Binding
By Brett McLaughlin

Digester is a powerful pattern-matching utility that allows developers to process XML documents at a higher level than the SAX or DOM APIs and fire a set of rules once these patterns are found (or not found). The idea is to create an instance of Digester, register the patterns and the rules with it, and pass to it your XML document. The Digester then takes over and fires the rules in their registered order. If an element within your document matches more than one rule, all of the rules are fired for it in the order in which they were registered. The patterns that you register must match the XML elements, based on their name and location in the XML tree.

Digester comes prebuilt with a set of 12 rules. Do you want to invoke a method on a top-level object when a particular pattern is found within your XML document? Simple -- use the prebuilt CallMethodRule! You don't need to use the prebuilt rules; you can create your own from scratch by extending the Rule class.

When specifying the pattern matching rules, elements are designated as absolute. Therefore, the root element is specified by name and the elements following it are specified by /. For example, if company is the root-level element, company/employee might be a pattern for matching with a child element. You can use wildcards, so */employee will match all occurrences of the employee element within the document.

When a pattern is matched, four callback methods are called within the rules associated with the matched pattern. These methods are begin, end, body, and finish. These are called at the appropriate intervals. For example, begin and end are called when the opening and closing tags of the matched element are found, respectively. body is called when the text nested within the matched pattern is encountered, and finish when the matched pattern has been completely processed.

Finally, these patterns can be specified within an external rules XML document (using the digester-rules.dtd) or within the code itself. I will illustrate the first method, as it is more widely used than the other.

To start using Digester, you need to create two XML files. The first file is your data or configuration file, to which the rules need to be applied:

<?xml version="1.0"?>
<company>
    <name>My Company</name>
    <address>200, Bayside Drive, CA</address>
    <employee>
        <name>Thompson</name>
        <employeeNo>10000</employeeNo>
    </employee>
    <employee>
        <name>Wilson</name>
        <employeeNo>10001</employeeNo>
    </employee>
</company>

The second file is the rules file. This rules.xml file tells Digester what patterns to look for in company.xml, and what to do when they are found.

<?xml version="1.0"?>
<digester-rules>
    <!-- this rule creates the top level company object -->
    <object-create-rule pattern="company" classname="Company" />
    <call-method-rule pattern="company/name" methodname="setName"
	    paramcount="0" />
    <call-method-rule pattern="company/address" methodname="setAddress"
        paramcount="0" />
    <pattern value="company/employee">
        <object-create-rule classname="Employee" />
        <call-method-rule pattern="name" methodname="setName"
            paramcount="0" />
        <call-method-rule pattern="employeeNo" methodname="setEmployeeNo" 
            paramcount="0" />
        <set-next-rule methodname="addEmployee" />
    </pattern>
</digester-rules>

What are we doing here? The first rule, <object-create-rule pattern="company" classname="Company" />, tells Digester that if it encounters the pattern "company", it needs to follow the object-create-rule, which is geekspeak for creating an instance of a class! How does Digester know which class to create? By the classname="Company" attribute. When the top-level element company is encountered while company.xml is being parsed, after this rule is executed we will have an instance of the Company class created for us by Digester.

The call-method-rule should not be difficult to understand now. It calls a method (the name of which is supplied by the methodname attribute) once the pattern company/name or company/address is encountered.

The last pattern matching is interesting because we have enclosed the rules within a matching pattern. Both ways of specifying the rules and patterns are acceptable. Choose whichever feels more comfortable. This particular pattern creates an Employee class when the pattern company/employee is found. It sets its values, and finally, with the set-next-rule, adds this employee to the top-level Company class.

Once these two XML files are created, using Digester is as simple as writing two lines of code.

Digester digester = DigesterLoader.createDigester(rules.toURL()); 
Company  company  = (Company)digester.parse(inputXMLFile);

The first line loads the rules file and creates a Digester. The second one uses this Digester to apply the rules.

DigesterDemo.java contains the full source code.

Jelly

Summary: A Java-and-XML-based scripting and processing language.

Where: Main Page, Binaries, Source.

When: In a nutshell, whenever and wherever you want a flexible and extensible way to write scripts using XML.

Example Applications: JellyDemo.java, jellydemo.xml and TrivialTag.java require commons-jelly-1.0-dev.jar, dom4j.jar, commons-logging.jar, commons-beanutils.jar, and commons-collections.jar in the CLASSPATH.

Description:

It is difficult to define what Jelly is or what role it fulfills. Jelly tries to provide a unified XML scripting engine that can be extended by the developer by way of custom actions and tags. Elements within an XML document map to JavaBeans, while attributes map to properties. In a way, it is a combination of Betwixt and Digester, but more extensible and powerful.

There are several components of a Jelly system. The first is the Jelly script, which is simply an XML document that is ultimately parsed by the Jelly engine. The parsed elements within the XML document are bound to Jelly Tags for dynamic processing. Jelly Tags, our next component, are simply JavaBeans that implement the Tag interface of Jelly. This forces these Jelly Tags to implement the doTag method. It is this method that is run when the scripting engine encounters this element within your XML document, and thus allows you to provide dynamic scripting. In a way, this is very similar to what Digester does.

Jelly comes with a variety of prebuilt tags. Some of these tags are provided for core Jelly support and some provide help with parsing, looping, and conditional execution of code. Jelly also comes with extended support for Ant tasks.

Using Jelly inside of your code requires you to create an instance of JellyContext. Think of a JellyContext object as the environment in which Jelly scripts are run and compiled.

JellyContext context = new JellyContext();

This context allows you to run your scripts against it. Note that the output is actually an instance of the XMLOutput class that is used to output XML events.

context.runScript(new File("jellydemo.xml"), output);

While creating your own tags, you can either override the doTag method, as explained above and shown below, or provide an execution method such as invoke() or run().

public void doTag(XMLOutput output) throws Exception {
    // do what you want to here
    // set properties, access file systems etc..
    this.intProp = 3;
}

An example XML file that defines a Jelly script is:

<j:jelly xmlns:j="jelly:core" xmlns:define="jelly:define" 
    xmlns:tr="trivialTag">
    <define:taglib uri="trivialTag">
        <define:jellybean name="trivial" className="TrivialTag" />
    </define:taglib>
    <tr:trivial intProp="1" stringProp="ball">Hello World</tr:trivial>
</j:jelly>

This example uses the jelly:define and jelly:core tags, along with a trivialTag. When the trivial tag instance is encountered, Jelly creates an instance of the corresponding JavaBean. Further, it executes the method doTag (or an invokable method such as run or invoke).

There are many other things that you can do with Jelly. It can be run from the command line or within ANT, and can be embedded within your code.

JXPath

Summary: XPath interpretation in Java.

Where: Main Page, Binaries, Source.

When: When you want to apply XPath traversal to graphs of JavaBeans, DOM, and other types of objects.

Example Applications: JXPathDemo.java, Book.java, Author.java require commons-jxpath-1.1.jar in the CLASSPATH.

Description:

Note that the discussion below requires that you have a basic understanding of XPath.

XPath is the syntax for traversing through an XML document. JXPath applies the same concept for traversal of other Java Objects, notably JavaBeans, Collections, Arrays, and Maps.

The central class in JXPath usage is JXPathContext, which uses a factory method to locate and create an instance of a context. This mechanism allows a developer to plug in a new implementation of JXPath, if so required. To use it, you simply pass to it the JavaBean, the collection, the map, etc., that you want to traverse.

JXPathContext context = JXPathContext.newContext(book);

You can perform a variety of tasks on this context. You can access the values of properties or nested properties, or even set these properties.

System.err.println(context.getValue("title"));
System.err.println(context.getValue("author/authorId"));
context.setValue("author/authorId", "1001");

There are several other types of objects that you can traverse with the help of JXPath. However, there is no change in the way that you instantiate the context object. You still get a new context through the static method described above, passing in the object that you wish traversed.

Conclusion

This concludes Part 2 of the coverage of Jakarta Commons. In the next and final installment, I will cover the Utilities components. I hope you have fun trying out the examples in this installment. Good luck!

Vikram Goyal is the author of Pro Java ME MMAPI.


Return to ONJava.com.