Search This Blog

Monday 13 May 2013

JAXB and simple elements

In the past few weeks I have been working with web-services. So exposure to xml and xsd was inevitable. I had used xsd a while back and so I needed a revision of some of those topics. I decided to do a quick summary of some of the options available in xsd and how JAXB works with them
XSD or XML Schema Definition is exactly what it sounds - a definition. It defines the rules for the XML document. The XSD provides the xml creator/generator/builder with information, as to what constitutes a valid and correct xml.
I decided to start with the simplest of elements
<firstName xmlns="http://test.com/xsd/simple/01/">
     Robin
</firstName>
The firstName element holds the first name of the user. Its a simple element - no attribute, no nested elements. Pure textual content. The element also indicates the namespace to identify the associated xsd.
If we were to look at the xsd for the same:
<?xml version="1.0" encoding="UTF-8"?>
<schema targetNamespace="http://test.com/xsd/simple/01/"
 elementFormDefault="unqualified" version="1.0"
 xmlns="http://www.w3.org/2001/XMLSchema" 
        xmlns:si="http://test.com/xsd/simple/01/">

 <element name="firstName" type="string" nillable="false" /> 
</schema>
The namespace indicates that this is the xsd the xml refers to. The nillable attribute when true indicates that empty or blank content is allowed for the element (There is a blog link that I found which gives a very good explanation of the attribute).
A simple element need not be so simple. Consider the next element.
<element name="gender" default="male">
 <simpleType>
  <restriction base="string">
   <enumeration value="male" />
   <enumeration value="female" />
  </restriction>
 </simpleType>
</element>
The xsd fragment defines a gender element. I have placed the definition within a simpleType. The gender element can only take one of the two values - male and female. The type is indicated using the base attribute here.(For restrictions W3 Schools has a good collection of resources...)
Thus when things cannot fit directly within the element tag, we can nest a simpleType within it.
XSD allows us to go one step further and separate the simpleType from the element.
<element name="age" type="si:ageType" />

<simpleType name="ageType">
 <restriction base="integer">
  <minInclusive value="0" />
  <maxInclusive value="45" />
 </restriction>
</simpleType>
Here our age element is now not of any predefined types but of a custom type. It is still a simple element that allows integer content in the range of 0 to 45.
Now I can create multiple elements of type age (Just one of the advantages! )
I decided to convert the XSD into java.
For this we have the XJC tool (a part of the JDK)
>xjc simple.xsd
The command created a series of classes.
 For simple elements no Root classes are generated. Instead the element creation code is also simple and found in the ObjectFactory class.
    private final static QName _Age_QNAME = new QName("http://test.com/xsd/simple/01/", "age");
    private final static QName _FirstName_QNAME = new QName("http://test.com/xsd/simple/01/", "firstName"); 
    private final static QName _Gender_QNAME = new QName("http://test.com/xsd/simple/01/", "gender");  
 
    @XmlElementDecl(namespace = "http://test.com/xsd/simple/01/", name = "age")
    public JAXBElement<Integer> createAge(Integer value) {
        return new JAXBElement<Integer>(_Age_QNAME, Integer.class, null, value);
    }

    @XmlElementDecl(namespace = "http://test.com/xsd/simple/01/", name = "firstName")
    public JAXBElement<String> createFirstName(String value) {
        return new JAXBElement<String>(_FirstName_QNAME, String.class, null, value);
    }

    @XmlElementDecl(namespace = "http://test.com/xsd/simple/01/", name = "gender", defaultValue = "male")
    public JAXBElement<String> createGender(String value) {
        return new JAXBElement<String>(_Gender_QNAME, String.class, null, value);
    }
The methods return JAXBElements of the appropriate type. XJC tool also ignored the different xsd definition styles, generating similar code. The restrictions applied did not make it into the code.
If we tried to marshal these objects to xml and reverse.
import com.test.xsd.simple._01.ObjectFactory;

public class TestSimple {

 private static final JAXBContext context;

 static {
  try {
   context = JAXBContext.newInstance();
  } catch (JAXBException e) {
   throw new RuntimeException(e);
  }
 }

 public static String marshall(Object jaxbElement) throws JAXBException {
  Marshaller marshaller = context.createMarshaller();
  StringWriter stringWriter = new StringWriter();
  marshaller.marshal(jaxbElement, stringWriter);
  return stringWriter.toString();
 }

 public static <T> JAXBElement<T> unmarshall(StreamSource xmlSource,
   Class<T> elementClass) throws JAXBException {
  Unmarshaller unmarshaller = context.createUnmarshaller();
  JAXBElement<T> ele = unmarshaller.unmarshal(xmlSource, elementClass);
  return ele;
 }

 public static void main(String[] args) throws JAXBException {
  ObjectFactory objectFactory = new ObjectFactory();
  System.out.println(marshall(objectFactory.createGender("male")));
  StreamSource streamSource =new StreamSource(TestSimple.class.getResourceAsStream("/gender.xml"));
  System.out.println(unmarshall(streamSource, String.class).getValue());
 }
}
The code is pretty straight-forward. The marshall method takes a JAXBElement and returns the xml. I passed the Gender Element with value "male". The output returned is as below:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gender xmlns="http://test.com/xsd/simple/01/">male</gender>
I then passed this same value to the unmarshall method via the file gender.xml . The unmarshaller read the xml and returned an appropriate JAXBElement. Calling the getValue method gives us the value for a simple element.
To test restrictions I passed the following invalid  input:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gender xmlns="http://test.com/xsd/simple/01/">1234</gender>
In an ideal scenario validation should have cause the element creation to fail. However this was not the case. The JAXBElement returned had a gender 1234. The restrictions were ignored by JAXB Unmarshaler. Similarly the below code :
System.out.println(marshall(objectFactory.createGender("133333")));
resulted in successful marshaling when it should have actually failed.
What we need is that the Marshaler and Unmarshaller validate the input available. The code for the same is
SchemaFactory sf = SchemaFactory
 .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
simpleSchema = sf.newSchema(new StreamSource(TestSimple.class
 .getResourceAsStream("/simple.xsd")));
For the marshaller
marshaller.setSchema(simpleSchema);
marshaller.marshal(jaxbElement, stringWriter);
On trying to marshal the Gender instance into xml:
Exception in thread "main" javax.xml.bind.MarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: cvc-enumeration-valid: Value '133333' 
is not facet-valid with respect to enumeration '[male, female]'. 
It must be a value from the enumeration.] 
Similarly the schema can also be set for the unmarshaler. If we now try to marshal the invalid age element:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<age xmlns="http://test.com/xsd/simple/01/">210</age>
The validation will now ensure that an exception is generated:
Exception in thread "main" javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: cvc-maxInclusive-valid: Value '210' is not facet
-valid with respect to maxInclusive '45' for type 'ageType'.]

No comments:

Post a Comment