2007/07/03
3 Jul, 2007

ADB Generated Code in Apache Axis2

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies - WSO2

IntroductionADB

The Axis2 Databinding Framework (ADB) is a light weight data binding framework specially designed for Apache Axis2, which is a leading Apache Web services project to process SOAP messages. Although it was started as a simple data binding framework with very limited features, ADB is now used in all commonly used XML Schema constructs in most Web service scenarios. As a result, ADB has now been chosen as the default data binding framework for Axis2.

Background

The primary objective of any data binding framework is to map the XML schema constructs to programing language objects, and hence provide the marshaling (i.e., produce an equivalent XML document from a programing language object structure) and unmarshaling (i.e, produce an equivalent programing language object structure from an XML document) capabilities. In fact, ADB is designed to support any programing language with some customized templates and also those who write the programing language. Currently it has a full implementation only for the Java programing language, and a relatively less implementation for the C programing language. Therefore, this article focuses only on the Java programing language implementation. In other words, the rest of this article is going to discuss how ADB maps the XML schema constructs to Java classes, and how it provides marshaling and unmarshaling facilities.

Axis2 uses AXIOM as its object model. AXIOM uses the pull parsing mechanism to parse the XML documents. On the other hand, ADB is tightly coupled with AXIOM since it is primarily designed to be used with Axis2. Therefore, the unmarshaling process or parsing mechanism in ADB mainly focuses on how to create the Java object structure from the input XML stream (i.e, from XMLStreamReader). Similarly, the marshaling process or serializing in ADB mainly focuses on how to write the Java object structure to an XML stream (i.e, to XMLStreamWriter).

Examining the Generated Code

Now let's take a very simple XML Schema fragment and examine the generated code to understand the above concepts properly.


<schema targetNamespace="https://axis2.samples/adb"
xmlns="https://www.w3.org/2001/XMLSchema"
xmlns:xsd="https://www.w3.org/2001/XMLSchema"
xmlns:tns="https://axis2.samples/adb"
elementFormDefault="qualified">
<element name="SampleElement1">
<complexType>
<sequence>
<element name="Parameter1" type="xsd:string"/>
<element name="Parameter2" type="xsd:int"/>
</sequence>
</complexType>
</element>
</schema>

    Sample schema 1

This simple schema fragment defines a simple Element called SampleElement1, and it belongs to the namespace https://axis2.samples/adb (target namespace of the schema). SampleElement1 contains two elements called Parameter1(string type) and Parameter2 (boolean type).

Axis2 does not provide any tools to compile the .xsd files directly. We can use the org.apache.axis2.schema.XSD2Java class to compile the above schema fragment. This task can be done as follows with the Axis2.sh script file. The AXIS2_HOME environment variable must be properly set before running this command.

sh axis2.sh org.apache.axis2.schema.XSD2Java <path to xsd file> <path to output folder>

e.g., sh axis2.sh org.apache.axis2.schema.XSD2Java /home/amila/articals/sample1/sample1.xsd /home/amila/articals/sample1/src/

Now let's see the generated code. There are two files in the samples/axis2/adb directory. For the moment let's examine the SampleElement1.java file.

  1. Package name

        package samples.axis2.adb;

    The package name is generated using the targetNamespace (i.e. https://axis2.samples/adb).

  1. Class name declaration

        public class SampleElement1
implements org.apache.axis2.databinding.ADBBean {

    All the ADB classes implement the ADBBean interface. The ADBBean interface contains only one method called the getPullParser. This method was used in the earlier Axis2 versions to generate an XMLStreamReader when serializing, but now it has been deprecated for performance reasons.

  1. Element QName

        public static final
javax.xml.namespace.QName MY_QNAME = new javax.xml.namespace.QName(
"https://axis2.samples/adb",
"SampleElement1",
"ns1");

    All the generated classes for the top level XML schema elements contain a self QName as defined in the XML Schema.

  1. Variable declarations, getter methods, and setter methods.

protected java.lang.String localParameter1;

public java.lang.String getParameter1() {
return localParameter1;
}

public void setParameter1(java.lang.String param) {
this.localParameter1 = param;
}

protected int localParameter2;

public int getParameter2() {
return localParameter2;
}

public void setParameter2(int param) {
this.localParameter2 = param;
}

    Each child element contains a local variable, a getter method and a setter method with the respective type. Getter and setter methods can have some validation checks specified in the XML Schema parameters.

  1. GetOMElement and serialize methods.

            public org.apache.axiom.om.OMElement getOMElement(
final javax.xml.namespace.QName parentQName,
final org.apache.axiom.om.OMFactory factory) {

org.apache.axiom.om.OMDataSource dataSource = new org.apache.axis2.databinding.ADBDataSource(this, MY_QNAME) {

public void serialize(javax.xml.stream.XMLStreamWriter xmlWriter) throws javax.xml.stream.XMLStreamException {
SampleElement1.this.serialize(MY_QNAME, factory, xmlWriter);
}
};
return new org.apache.axiom.om.impl.llom.OMSourcedElementImpl(MY_QNAME, factory,dataSource);
}

public void serialize(final javax.xml.namespace.QName parentQName,
final org.apache.axiom.om.OMFactory factory,
javax.xml.stream.XMLStreamWriter xmlWriter) throws javax.xml.stream.XMLStreamException {
java.lang.String prefix = parentQName.getPrefix();
java.lang.String namespace = parentQName.getNamespaceURI();
if (namespace != null) {
java.lang.String writerPrefix = xmlWriter.getPrefix(namespace);
if (writerPrefix != null) {
xmlWriter.writeStartElement(namespace, parentQName.getLocalPart());
} else {
if (prefix == null) {
prefix = org.apache.axis2.databinding.utils.BeanUtil.getUniquePrefix();
}
xmlWriter.writeStartElement(prefix, parentQName.getLocalPart(),namespace);
xmlWriter.writeNamespace(prefix, namespace);
xmlWriter.setPrefix(prefix, namespace);
}
} else {
xmlWriter.writeStartElement(parentQName.getLocalPart());
}
namespace = "https://axis2.samples/adb";
if (! namespace.equals("")){
prefix = xmlWriter.getPrefix(namespace);
if (prefix == null) {
prefix = org.apache.axis2.databinding.utils.BeanUtil.getUniquePrefix();
xmlWriter.writeStartElement(prefix, "Parameter1",namespace);
xmlWriter.writeNamespace(prefix, namespace);
xmlWriter.setPrefix(prefix, namespace);
} else {
xmlWriter.writeStartElement(namespace, "Parameter1");
}
} else {
xmlWriter.writeStartElement("Parameter1");
}
if (localParameter1 == null) {
// write the nil attribute
throw new RuntimeException("Parameter1 cannot be null!!");
} else {
xmlWriter.writeCharacters(localParameter1);
}
xmlWriter.writeEndElement();
namespace = "https://axis2.samples/adb";
if (! namespace.equals("")){
prefix = xmlWriter.getPrefix(namespace);
if (prefix == null) {
prefix = org.apache.axis2.databinding.utils.BeanUtil.getUniquePrefix();
xmlWriter.writeStartElement(prefix, "Parameter2",namespace);
xmlWriter.writeNamespace(prefix, namespace);
xmlWriter.setPrefix(prefix, namespace);
} else {
xmlWriter.writeStartElement(namespace, "Parameter2");
}
} else {
xmlWriter.writeStartElement("Parameter2");
}
if (localParameter2 == java.lang.Integer.MIN_VALUE) {
throw new RuntimeException("Parameter2 cannot be null!!");
} else {
xmlWriter.writeCharacters(org.apache.axis2.databinding.utils.ConverterUtil
.convertToString(localParameter2));
}
xmlWriter.writeEndElement();
xmlWriter.writeEndElement();
}

    These two methods are used to serialize the Java object structure to an XML stream. Before we go through the above code, let's try to get the XML string from an ADBBean using the following sample code.

        SampleElement1 sampleElement1 = new SampleElement1();
sampleElement1.setParameter1("parameter 1");
sampleElement1.setParameter2(2);
OMElement omElement = sampleElement1.getOMElement(SampleElement1.MY_QNAME,
OMAbstractFactory.getOMFactory());

String omElementString = omElement.toStringWithConsume();
System.out.println("XML String ==> " + omElementString);

    Sample output 1 (slightly formatted to fit this page)

     

    XML String ==> <ns1:SampleElement1 xmlns:ns1="https://axis2.samples/adb">
    <ns1:Parameter1>parameter1</ns1:Parameter1>
    <ns1:Parameter2>2</ns1:Parameter2>
    </ns1:SampleElement1>

    In the above sample, first a SampleElement1 class instance is created and the variable values are set. Then it gets an OMElement using the getOMElement method.

    Internally it creates an Anonymous OMDataSource within the getOMElement method and returns an OMSourcedElementImpl with this OMDataSource. The serialize method of the OMDataSource is implemented within the ADBBean class and this is later used to serialize the Java object structure by AXIOM. When you get the XML Stream, external calling classes (e.g., Stubs and MessageRecievers) to ADBBeans always use the getOMElement method to get an equivalent OMElement.

    The serialize method gets a QName (parentQName) and an XMLStreamWriter(xmlwriter) as arguments. This is the method where the ADBBean class directly writes its object structure to the XMLStreamWriter. One XMLStreamWriter object is used per ADB object structure by passing the same XMLStreamWriter to the child objects as well.

    When serializing the object structure, it first writes the parent element details. It then writes its child attributes one by one according to the given order. Before writing a particular child element, ADB always validates the child object's value. This is a very important feature when using the minOccures and nullable attributes.

  1. Parse method

 

        public static SampleElement1 parse(javax.xml.stream.XMLStreamReader reader) throws java.lang.Exception {
SampleElement1 object = new SampleElement1();

try {
while (!reader.isStartElement() && !reader.isEndElement())
reader.next();
if(reader.getAttributeValue("https://www.w3.org/2001/XMLSchema-instance","type") != null) {
java.lang.String fullTypeName = reader.getAttributeValue("https://www.w3.org/2001/XMLSchema-instance", "type");
if (fullTypeName != null) {
java.lang.String nsPrefix = fullTypeName.substring(0,fullTypeName.indexOf(":"));
nsPrefix = nsPrefix == null ? "" : nsPrefix;
java.lang.String type = fullTypeName.substring(fullTypeName.indexOf(":") + 1);
if(!"SampleElement1".equals(type)) {
//find namespace for the prefix
java.lang.String nsUri = reader.getNamespaceContext().getNamespaceURI(nsPrefix);
return (SampleElement1) samples.axis2.adb.ExtensionMapper.getTypeObject(nsUri, type, reader);
}
}
}

reader.next();
while(!reader.isStartElement() && !reader.isEndElement())
reader.next();

if(reader.isStartElement() && new javax.xml.namespace.QName("https://axis2.samples/adb", "Parameter1").equals(reader.getName())){
java.lang.String content = reader.getElementText();
object.setParameter1(org.apache.axis2.databinding.utils.ConverterUtil.convertToString(content));
reader.next();
} // End of if for expected property start element
else { // A start element we are not expecting indicates an invalid parameter was passed
throw new java.lang.RuntimeException("Unexpected subelement " + reader.getLocalName());
}

while(!reader.isStartElement() && !reader.isEndElement())
reader.next();
if(reader.isStartElement() && new javax.xml.namespace.QName("https://axis2.samples/adb", "Parameter2").equals(reader.getName())){
java.lang.String content = reader.getElementText();
object.setParameter2(org.apache.axis2.databinding.utils.ConverterUtil.convertToInt(content));
reader.next();
} // End of if for expected property start element
else {// A start element we are not expecting indicates an invalid parameter was passed
throw new java.lang.RuntimeException("Unexpected subelement " + reader.getLocalName());
}

while(!reader.isStartElement() && !reader.isEndElement())
reader.next();
if(reader.isStartElement()) // A start element we are not expecting indicates a trailing invalid property
throw new java.lang.RuntimeException("Unexpected subelement " + reader.getLocalName());
} catch (javax.xml.stream.XMLStreamException e) {
throw new java.lang.Exception(e);
}
return object;
}

    The parse method is used to create an ADBBean object from an XMLStreamReader. Again, before going through the parse method, let's see how we can generate a SampleElement1 from the XML string we got earlier.

 

        SampleElement1 sampleElement1 = new SampleElement1();
sampleElement1.setParameter1("parameter 1");
sampleElement1.setParameter2(2);
OMElement omElement = sampleElement1.getOMElement(SampleElement1.MY_QNAME, OMAbstractFactory.getOMFactory());

String omElementString = omElement.toStringWithConsume();
// getting the SimpleElement1 from the xml string
XMLStreamReader xmlReader = StAXUtils.createXMLStreamReader(new ByteArrayInputStream(omElementString.getBytes()));
SampleElement1 newSampleElement1 = SampleElement1.Factory.parse(xmlReader);
System.out.println("Parameter 1 ==> " + newSampleElement1.getParameter1());
System.out.println("Parameter 2 ==> " + newSampleElement1.getParameter2());

    Sample output 2

     

            Parameter 1 ==> parameter 1
    Parameter 2 ==> 2

    In this sample, an XMLStreamReader object is created from the XML string using the StaxUtils class. Then pass this XMLStreamReader object to the parse method of the SampleElement1.Factory class to receive a SimpleElement1 object.

    In the parse method, first it points the XMLStreamReader object to a Start Element by using a while loop. After that, in each bean, it checks whether this XML element represents an extended instance of the given class. (In an XML element, this situation is represented by using the xsi:type attribute.) We will discuss this in more detail when we talk about the XML Schema Extensions with another article. If this XML element is an extended type, then it calls for the correct object to be parsed using the ExtensionMapper.

    Then it starts to parse the child attributes. In this case, there are two attributes to parse: parameter1 and parameter2. First it checks for the parameter1 element and throws an exception if it is not there, since an element sequence is used and the minOccures for this element is 1 (default value). As it can be seen, ADB has been able to validate the incoming XML stream correctly since it directly parses the XMLStreamReader object.

The above description gives a basic overview of the generated ADBBean class. You can compile and see the following schema fragment to further enhance the knowledge with the named Complex types.

 

        <schema targetNamespace="https://axis2.samples/adb"
xmlns="https://www.w3.org/2001/XMLSchema"
xmlns:xsd="https://www.w3.org/2001/XMLSchema"
xmlns:tns="https://axis2.samples/adb"
elementFormDefault="qualified">
<element name="SampleElement2" type="tns:SampleComplexType2"/>
<complexType name="SampleComplexType2">
<sequence>
<element name="Parameter1" type="xsd:string"/>
<element name="Parameter2" type="xsd:int"/>
</sequence>
</complexType>
</schema>

    Sample schema 2

For this sample, there will be two Java classes for SampleElement2 and SampleComplexType2. The SampleElement2 class keeps a variable of the SampleComplexType2 type, and parsing and serializing of the SampleElement2 class simply delegates them to the SampleComplexType2 class.

Conclusion

This article gives a basic overview of the generated ADB Bean Classes and its main methods. As it can be clearly seen, ADB directly reads from the XMLStreamReader object and directly writes to the XMLStreamWriter object without any intermediate utility classes. This gives a good hint about why it is faster than the other data binding frameworks like xmlbeans and jaxb. Also, reading directly from the XMLStreamReader has enabled the incoming XML stream validation in ADB.

Author

Amila Suriarachchi,Senior Software Engineer, WSO2 Inc. amila at wso2 dot com

 

About Author

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies
  • WSO2 Inc.