ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Using the Jakarta Commons, Part 2

by Vikram Goyal
07/09/2003

Jakarta Commons is a Jakarta subproject that creates and maintains independent packages unrelated to any other framework or product. The packages are collections of components that serve small useful purposes in their own right and are usually server-centric.

The previous installment of this series divided these components into five categories and described the Web and Trivial categories. This article describes the XML and Packages categories. The final installment will cover the Utilities category. Note that these categorizations are purely for organizational reasons.

Packages Category

The Packages category contains the Codec and Modeler components.

Codec

Summary: Provides implementations of commonly used encoders and decoders.

Where: Main Page, Binaries, Source.

When: When you want a standard implementation of the Base64 and Hex encoding mechanisms.

Example Application: CodecDemo.java, needs commons-codec-1.1.jar in the CLASSPATH.

Description:

Source Code

Download all of the example code in one zip file.

The classes in the Codec component are divided into two packages. One is a pure implementation of the commonly used Base64 and Hex encoding mechanisms; the other contains the encoding mechanisms for language and phonetic encoders. Since the usage of the language and phonetic encoders is not so common, I will give examples of the first package only. The usage is similar for both packages.

Base64 encoding is primarily used in the transfer of email. The RFC that defines the transfer of MIME documents specifies Base64 encoding, so that arbitrary binary data can be transferred safely using only the printable ASCII character set. For example, if you have an image that needs to be transferred using email, your email client will use Base64 encoding to convert this binary data into ASCII. Using this encoding, triplets of 8-bit octets are encoded as groups of four characters, each representing 6 bits of the source 24 bits. The encoded string is 1.3 times the size of the original. The = character is used to pad the end of the file. Besides MIME documents, this encoding is also used for encoding the user:password string in an HTTP Authorization header in BASIC Authorization.

Using Base64 is pretty simple. Use the static methods of the Base64 class: Base64.encodeBase64(byte[] byteArray) and Base64.decodeBase64(byte[] byteArray). You can also use a static method, Base64.isArrayByteBase64(byte[] byteArray), to determine if a given byte array would pass the Base64 test for correctly encoded data (since it should only contain printable ASCII characters).

byte[] encodedBytes  = Base64.encodeBase64(testString.getBytes());
String decodedString = new String(Base64.decodeBase64(encodedBytes));
System.err.println("Is \'^\' a valid Base64 character? " 
      + Base64.isArrayByteBase64(invalidBytes));

Using the Hex encoder and decoder is similar and serves the purpose of encoding/decoding data bytes to and from their hexadecimal equivalents.

Modeler

Summary: Provides support for configuring and instantiating Model MBeans (Management Beans) according to the JMX (Java Management Extensions) specification.

Where: Main Page, Binaries, Source.

When: When you want to create and manage the Model MBeans that let you manage your application using a standards-based management API.

Example Applications: ModelerDemo.java, DemoManagedBean.java and mbeans-descriptors.xml require commons-modeler-1.0.jar, commons-logging.jar, commons-digester.jar, commons-collections.jar, commons-beanutils.jar, and the Reference Implementation of the JMX, jmxri.jar, available from Sun, in the CLASSPATH.

Description:

Please note that the following discussion requires that you have a working understanding of the JMX API.

Managed Beans (Management Beans or MBeans) are the beans that get associated with the components in your application that you wish to manage. Model MBeans are a special type of MBeans that are highly dynamic and configurable and require metadata information about the classes that are being managed (as opposed to managed classes implementing interfaces). The Modeler provides a set of functions that can make your job of supplying this metadata information easier, through an XML interface. Further, the Modeler provides a registry and a base Model MBean to work with.

Using Modeler is relatively simple. Modeler allows you to create the metadata information required for your Model MBeans in the form of an XML file. This XML file must conform to a DTD supplied with the Modeler. This metadata is used to create the registry at runtime. This registry is the central repository of all your Model MBeans that are managing your managed beans. It acts as a factory for these beans.

Let us start by creating this XML file for a targeted Managed Bean, called DemoManagedBean. This managed bean has an attribute called name that can be read and written.

<?xml version="1.0"?>
<!DOCTYPE mbeans-descriptors PUBLIC
"-//Apache Software Foundation//DTD Model MBeans Configuration File"
"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd"> 

<!-- Descriptions of JMX MBeans -->
<mbeans-descriptors>
    <mbean name="ManagedBean" description="Example Managed Bean" type="ManagedBean">
        <attribute   name="name" description="Simple Name" type="java.lang.String" />
        <constructor name="ManagedBean"/>
    </mbean>
</mbeans-descriptors>

As you can see, this file provides information about the ManagedBean, including its attributes, constructors, and, even though this is not shown in this example, its operations. This is our metadata information. You can specify the class name for the Model MBean as an attribute of the mbean element if you intend to extend the base MBean, called BaseModelMBean, supplied with Modeler:

<mbean name="ManagedBean" className="MyExtendedManagedBean"
    description="Example Managed Bean" type="ManagedBean">

The standard Model MBean, as specified above, simply passes on all of the calls directly to the ManagedBean class.

Next, we will supply this information to the registry. Note that after loading the registry with the descriptor file, a static method allows us to retrieve the fully formed registry.

// create the registry
Registry registry = null;
try {
    URL url = ModelerDemo.class.getResource("mbeans-descriptors.xml");
    InputStream stream = url.openStream();
    Registry.loadRegistry(stream);
    stream.close();
    registry = Registry.getRegistry();
} catch (Throwable t) {
    t.printStackTrace(System.out);
    System.exit(1);
}

After creating the registry, we need to create a Model MBean and register it with the default managed server. This will allow any JMX clients to call functions on the managed bean through the Model MBean. These steps are illustrated in the following two tables:

 // get a handle on a managed bean instance
DemoManagedBean mBean = new DemoManagedBean();

// create a Model MBean and register it with the MBean server
// using the instance of the mBean
MBeanServer mServer = registry.getServer();
ManagedBean managed = registry.findManagedBean("ManagedBean");

try {
	ModelMBean modelMBean = managed.createMBean(mBean);
	String domain         = mServer.getDefaultDomain();
	ObjectName oName      = new ObjectName(domain + ":type=ManagedBean");
	mServer.registerMBean(modelMBean, oName);
} catch(Exception e) {
	System.err.println(e);
	System.exit(0);
}

try {
    ObjectName name =
        new ObjectName(mServer.getDefaultDomain() + ":type=ManagedBean"); 
    ModelMBeanInfo info = (ModelMBeanInfo) mServer.getMBeanInfo(name);
    System.err.println(" className="       + info.getClassName());
    System.err.println(" description="     + info.getDescription());
    System.err.println(" mbeanDescriptor=" + info.getMBeanDescriptor());
    System.err.println("================================================");
    System.err.println("Original Value of Name: " +
        mServer.getAttribute(name, "name"));
    mServer.setAttribute(name, new Attribute("name", "Vikram"));
    System.err.println("New Value of Name: " +
        mServer.getAttribute(name, "name"));
} catch(Exception e) {
    System.err.println(e);
    System.exit(0);
}

Although this example might seem trivial, it illustrates the value of using Modeler. Contrast this with the steps you would have to take for creating a similar Model MBean without the Modeler. Using an XML file for describing the ModelMBeanInfo is powerful and extensible, and a much better approach than hardcoding the information.

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.

Copyright © 2009 O'Reilly Media, Inc.