Using the XML API

by Andrew Taylor <andy@benow.ca>
The BeNOW XML API consists of a worker class for marshalling objects to and from XML, and a set of annotations which guide XML presentation. With the XML API it is very easy to produce well designed XML from objects and objects from XML. After this tutorial, you will be able to easily introduce XML-Object interchange within your applications.
Contents
  1. History of the XML API
  2. Converting Objects to XML
  3. Producting Objects from XML
  4. Customizing XML Presentation
History of the XML API
The Castor XML Marshalling was used for early XML to object translations, and worked well, but lacked the speed and flexibility required. I decided to try a rewrite of Castor XML to provide the same or better functionality and more performance. The result was the light XML API.
Converting Objects to XML
It is very easy to convert objects into XML. Nothing special has to be done to an object in preparation for conversion to XML. XML marshalling is shown in src/java/test/org/benow/xml/Tutorial.java and can be run via bin/tutorial_xml.sh toXML (or bin/tutorial_xml.bat toXML in windows). Open Tutorial.java and locate the source in doToXML():
User user=Security.getCurrentUser();
long stamp=System.currentTimeMillis();
String xml=new ClassTransformer(user).toString();
System.out.println("XML created in "+(System.currentTimeMillis()-stamp)+"ms:\n"+xml);
Yup, that is it... one line. The object to transform is passed to the constructor and the xml fetched with toString(). Many other output destinations may be specified to the to(out) methods, including OutputStream, Writer, File, etc. The user is transformed to XML using reflection (and guided by optional annotations). The output XML is:
<?xml version="1.0" encoding="ISO-8859-1"?>
<users key="1">
    <name>admin</name>
    <description><![CDATA[Administrative user.]]></description>
    <validated>true</validated>
    <validation-code>1228264627630</validation-code>
    <modules type="org.benow.repository.mapping.JSQLArrayList">
        <item key="1" type="org.benow.mail.MailUserModule">
            <confirmed>false</confirmed>
        </item>
    </modules>
    <needs-password>false</needs-password>
    <confirmed>true</confirmed>
</users>
The discovered reflection information is cached, which makes repeated unmarshalling of certain classes very quick. As an example, run bin/tutorial_xml.sh benchmark (or bin/tutorial_xml.bat benchmark in windows) to do the above conversion to XML 100 times:
XML of pass 1 created in 51ms
XML of pass 2 created in 1ms
XML of pass 3 created in 1ms
:
XML of pass 98 created in 1ms
XML of pass 99 created in 7ms
XML of pass 100 created in 1ms
Marshalled to XML 100 times with avg time of: 2ms
... quite fast.
Producting Objects from XML
Conversion from XML to objects is similarly easy. The only code consideration is that objects must have a zero parameter constructor, in order to be constructed via reflection. This constructor may be private or protected, so as to not interfere with the object model. XML to object conversion can be seen in doFromXML():
User user=Security.getCurrentUser();
String xml=new ClassTransformer(user).toString();
User fromXML=(User) new ClassTransformer(user.getClass()).from(xml);
System.out.println(fromXML);
the line
User fromXML=(User) new ClassTransformer(user.getClass()).from(xml);
does the transformation of XML to a user. The class to produce is passed to the constructor and from is called with the XML source. In this case it's a string, but many other sources are possible, including InputStream, Reader, Document, Element, File, etc.
Customizing XML Presentation
Annotations can be used to customize the produced (or required) XML. The use of these annotations can be seen in src/java/test/org/benow/xml/SampleObject.java:
package test.org.benow.xml;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import org.benow.java.annotations.GetMethod;
import org.benow.java.annotations.SetMethod;
import org.benow.java.annotations.StringLength;
import org.benow.java.annotations.xml.XMLNodeType;
import org.benow.java.mapping.MapTo.XMLMapTo;

@XMLMapTo("a-sample-object")
public class SampleObject {
  private String firstName;
  @StringLength(64)
  @SetMethod("setLastName")
  private String lastName;
  @XMLNodeType(XMLNodeType.ATTRIBUTE)
  private Date birthDate;
  @GetMethod("getAge")
  @XMLNodeType(XMLNodeType.ATTRIBUTE)
  private int age;
  @XMLNodeType(XMLNodeType.TEXT)
  @GetMethod("toString")
  private String text;
  
  private SampleObject() {
    super();
  }
  
  public SampleObject(String firstName, String lastName, Date birthDate) {
    this.firstName=firstName;
    setLastName(lastName);
    this.birthDate=birthDate;
  }
  
  private int getAge() {
    Calendar cal = Calendar.getInstance();
    // there's a bug here, but I can't be bothered.
    cal.setTime(new Date(System.currentTimeMillis()));
    int nowYear = cal.get(Calendar.YEAR);
    cal.setTime(birthDate);
    int thenYear=cal.get(Calendar.YEAR);
    return nowYear-thenYear;
  }
  
  @Override
  public String toString() {
    Calendar cal=Calendar.getInstance();
    cal.setTime(birthDate);
    String month=cal.getDisplayName(Calendar.MONTH, Calendar.ALL_STYLES, Locale.ENGLISH);
    int dom=cal.get(Calendar.DAY_OF_MONTH);
    int year=cal.get(Calendar.YEAR);
    String day=cal.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.ALL_STYLES, Locale.ENGLISH);
    String bd=day+" "+dom+" of "+month+", "+year;
    return firstName+" "+lastName+" was born "+bd+" and is "+getAge()+" years young";
  }
  
  private void setLastName(String lastName) {
    if (lastName==null)
      throw new NullPointerException("lastName must not be null");
    this.lastName=lastName;
  }
}
@XMLMapTo (class|field)
@XMLMapTo can be used on classes to provide an element name for the class. If not provided, the default behaviour is to use the simple name of the class with dashes on case changes and in lower case. For example, the element name org.some.SpecialClass would be special-class.
@StringLength (field)
@StringLength is used to specify an expected length of a string. When provided, the string will not be wrapped with <![CDATA[ .. ]]>. The CDATA is used to allow inclusion of malformed XML characters within text. So use @StringLength only if you know that the string is XML safe.
@SetMethod (field)
The method to be used to set the field when transforming from XML. The method should exist and take a parameter of the same type as the field. Through the use of @SetMethod the value of the method can be validated.
@XMLNodeType (field)
The type of node to output can be specified with @XMLNodeType. By default, elements are output for each field, but output to an attribute or as text of the class element can be specified. To specify that the value is to be as an attribute, specify @XMLNodeType(XMLNodeType.ATTRIBUTE). To specify that the value of a field is to be as text of the class element, specify @XMLNodeType(XMLNodeType.TEXT). By using @XMLNodeType, the object can be custom fit to the XML, producing more readable and appropriate XML.
@GetMethod (field)
The method to be used to get the field value when transforming to XML. The method should exist and return a parameter of the same type as the field. Through the use of @GetMethod the value can be calculated or determined through some other manner. It should be noted that the field need not be used at all the class, but simply be a place holder for getting a value.

The XML produced by transforming a sample object to XML can be seen by running bin/tutorial_xml.sh annotations (or bin/tutorial_xml.bat annotations in windows):

<a-sample-object age="35" birth-date="Mon May 20 00:00:00 PDT 1974">
    <first-name><![CDATA[Andrew]]></first-name>
    <last-name>Taylor</last-name>Andrew Taylor was born Mon 20 of May, 1974 and is 35 years young</a-sample-object>
The XML can be thoroughly messed with, according to your needs,
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <andy@benow.ca>.