Tutorials » Simplify XML to Java Object Configuration using Jakarta Digester

During your software development life one or many times you might have come across a situation where you needed to populate java objects from XML on the fly. I described one approach for populating Java Beans from XML using JOX in one of my previous articles. In this article I will describe how this can be achieved using Jakarta Digester package, which originally was developed to process the struts-config.xml configuration file, soon it was renowned as more useful in general way and was moved to Jakarta-commons project.

When software application deals with the XML-formatted data, it is useful to be able to process an XML document in “event driven” manner, where particular java objects are created when particular patters of nested XML elements have been recognized. Digester provides a high level and more developer-friendly interface to SAX events. Since digester uses SAX APIs for parsing it becomes choice for the developer in opposed to Document Object Model (DOM) parser. Also because most of the details of shoving the XML elements are hidden, digester allows the developer to focus on the processing to be performed. Digester package allows you to configure an XML to Java object mapping, which sets off defined actions called rules whenever a particular pattern of XML elements is recognized. Full sets of predefined rules are available from Jakarta digester package, or you can create your own rules. Among the other XML parsing options available, the Digester package offers greater simplicity with very simple classes and a collection of “predefined rules”.

Understanding Jakarta Digester

Digester depends on following Jakarta common components in order to compile and run.

  • commons-digester.jar
  • commons-collections.jar
  • commons-logging.jar
  • commons-beanutils.jar

Main feature of the org.apache.commons.digester.Digester parser is that the Digester automatically walks through the element hierarchy of the XML document you are parsing, without entailing any attention to this process. This process is called as element matching patterns. Assume that you have registered processing rules that match patterns “x” “x/y” and “x/y/z”. For specified XML document with the below contents, the designated patters will be matched when the corresponding element is parsed. You can also use “*” wildcard character in your matching pattern rule.

<x>         Matches pattern “x”
  <y>       Matches pattern “x/y”
    <z/>    Matches pattern “x/y/z”

 

Using Jakarta Digester
In order to use a Digester, the following essential steps are necessary:

  • Create a new instance of the org.apache.commons.digester.Digester class.
  • Register all the element matching patterns for which you wish to have processing rules fired when this pattern is recognized in an inputted XML document. If there is more than one rule for a given pattern, then the rules will be executed in the order that they were listed.
  • Call the digester.parse() method, passing the reference to the XML document to be parsed.

Developing sample application

Following code example will make you understand Digester unambiguously.

address.xml

<addresses>
  <address>
    <addressLine1>101 blvd</addressLine1>
    <addressLine2>Little park</addressLine2>
  </address>
  <address>
    <addressLine1>Author 2</addressLine1>
    <addressLine2>His One Book</addressLine2>
  </address>
 
  <person>
    <name>Megha</name>
    <detail age="22">
      <education>PT</education>
    </detail>
  </person>
  <person>
    <name>person 2</name>
    <detail age="32">
      <education>engineer</education>
    </detail>
  </person>
</addresses>

Following are the Java beans you need to populate.

Addresses.java
package address;

import java.util.Vector;

public class Addresses {
  private Vector addresses;
  private Vector persons;

  public Addresses() {
    addresses = new Vector();
    persons = new Vector();
  }

  public void addAddress(Address address ) {
    addresses.addElement(address );
  }
  public void addPerson(Person person ) {
    persons.addElement( person );
  }

}

Address.java
package address;

public class Address {
  private String addressLine1;
  private String addressLine2;

  public Address() {}

  public void setAddressLine1( String addressLine1 ) {
    this.addressLine1 = addressLine1;
    System.out.println("addressLine 1 is "+this.addressLine1);
  }
  public void setAddressLine2( String addressLine2 ) {
    this.addressLine2 = addressLine2;
  }

}

Person.java
package address;

import java.util.Vector;

public class Person {
  private String name;
  private Vector details;

  public Person() {
    details = new Vector();
  }

  public void setName( String name ) {
    this.name = name;
    System.out.println("name " +this.name);
  }

  public void addDetail(Detail detail ) {
    details.addElement(detail);
  }

}

Detail.java
package address;

public class Detail {
  private String age;
  private String education;

  public Detail() {

  }

  public void setAge(String age){
    this.age = age;
    System.out.println("Age is "+this.age);
  }
  public void setEducation(String education) {
    this.education = education;
    System.out.println("education is "+this.education);
 }

}

Following is the AddressDigester class in which you specify the rules in digester with this class.

AddressDigester.java
package address;

import org.apache.commons.digester.Digester;

import java.io.File;

public class AddressDigester {

  public static void main( String[] args ) {

    try {
      Digester digester = new Digester();
      digester.setValidating( false );

      digester.addObjectCreate( "addresses", Addresses.class );

      digester.addObjectCreate( "addresses/address", Address.class );
      digester.addBeanPropertySetter( "addresses/address/addressLine1", "addressLine1" );
      digester.addBeanPropertySetter( "addresses/address/addressLine2", "addressLine2" );
      digester.addSetNext( "addresses/address", "addAddress" );

      digester.addObjectCreate( "addresses/person", Person.class );
      digester.addBeanPropertySetter( "addresses/person/name", "name" );

      digester.addObjectCreate( "addresses/person/detail", Detail.class );
      digester.addSetProperties( "addresses/person/detail", "age", "age" );
      digester.addBeanPropertySetter( "addresses/person/detail/education" );
      digester.addSetNext( "addresses/person/Detail", "addDetail" );

      digester.addSetNext( "addresses/person", "addPerson" );

      File inputFile = new File( args[0] );
      Addresses address = (Addresses)digester.parse( inputFile );

    } catch( Exception exc ) {
      exc.printStackTrace();
    }
  
}

As I discussed in the “Using Jakarta Digester” section, The Digester class processes the input XML document based on patterns and rules. The patterns must match XML elements, based on their name and location in the document tree. The pattern address matches the top level <address> element and likewise for the other elements.

  • addObjectCreate adds an “object create” rule for the specified parameters.
  • addBeanPropertySetter adds a bean property setter rule for the specified parameters.
  • addsetNext adds a set next rule for the specified parameters.

Although Digester is a simple solution, it’s not always ideal in the real programming world. When you have to parse XML whose elements keep on changing based on some input. In this situation, you need to write custom Digester Rules.

Now you know the simplicity and creativity of Jakarta Digester for XML Java parsing.

Download article source files.

About the Author

Vikas Pandya is a Sun-certified Java software engineer. He has several years of extensive hands-on experience on Java, XML,
Webservices technologies. He currently works as J2EE,XML/XSL engineer for Framework Inc. in New York.He writes articles on Java, XML and related technologies for different web-sites. He has a passion of exploring new technologies. Contact him at vikasdp@yahoo.com.