Using the XML API
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
- History of the XML API
- Converting Objects to XML
- Producting Objects from XML
- 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>.