December 20, 2010

JAXB and Marshal/Unmarshal Schema Validation

In a previous post I described how to validate an object model (mapped with JAXB annotations) against an XML schema using the javax.xml.validation APIs.  In this post I'll describe how to leverage those APIs during unmarshal and marshal operations.


Java Model

The following object model will be used for this example.  Notice how the model classes do not contain any validation specific information.


package blog.jaxb.validation;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {

    private String name;

    private List<PhoneNumber> phoneNumbers = 
        new ArrayList<PhoneNumber>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement(name="phone-number")
    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

}

package blog.jaxb.validation;

public class PhoneNumber {

}

XML Schema (customer.xsd)

The following is our XML schema.  The following are the interesting constraints:
  • The customer's name cannot be longer than 5 characters.
  • The customer cannot have more than 2 phone numbers.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="stringMaxSize5"/>
                <xs:element ref="phone-number" maxOccurs="2"/>
             </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="phone-number">
        <xs:complexType>
            <xs:sequence/>
        </xs:complexType>
    </xs:element>

    <xs:simpleType name="stringMaxSize5">
        <xs:restriction base="xs:string">
            <xs:maxLength value="5"/>
        </xs:restriction>
    </xs:simpleType>

</xs:schema> 

ValidationEventHandler

JAXB reports validation events through the ValidationEventHandler.  The event is represented as an instance of ValidationEvent, and provides many details about the issue.  The data is quite similar to what is available from a SAXParseException.  Returning false from the handleEvent method will cause the JAXB operation to stop, returning true will allow it to continue (if possible).

package blog.jaxb.validation;

import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;

public class MyValidationEventHandler implements ValidationEventHandler {

    public boolean handleEvent(ValidationEvent event) {
        System.out.println("\nEVENT");
        System.out.println("SEVERITY:  " + event.getSeverity());
        System.out.println("MESSAGE:  " + event.getMessage());
        System.out.println("LINKED EXCEPTION:  " + event.getLinkedException());
        System.out.println("LOCATOR");
        System.out.println("    LINE NUMBER:  " + event.getLocator().getLineNumber());
        System.out.println("    COLUMN NUMBER:  " + event.getLocator().getColumnNumber());
        System.out.println("    OFFSET:  " + event.getLocator().getOffset());
        System.out.println("    OBJECT:  " + event.getLocator().getObject());
        System.out.println("    NODE:  " + event.getLocator().getNode());
        System.out.println("    URL:  " + event.getLocator().getURL());
        return true;
    }

}

Unmarshal Demo

To enable validation an instance of Schema must be set on the Unmarshaller.  To handle the events an implementation of ValidationEventHandler must also be set.

package blog.jaxb.validation;

import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

public class UnmarshalDemo {

    public static void main(String[] args) throws Exception {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
        Schema schema = sf.newSchema(new File("customer.xsd")); 

        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setSchema(schema);
        unmarshaller.setEventHandler(new MyValidationEventHandler());
        Customer customer = (Customer) unmarshaller.unmarshal(new File("input.xml"));
    }

}

Input (input.xml)

<customer>
   <name>Jane Doe</name>
   <phone-number/>
   <phone-number/>
   <phone-number/>
</customer>


Output

The validation performed during the unmarshal raised 3 events.  The first 2 events are related to the text value of the "name" element being too long.  The 3rd event is related to the extra "phone-number" element.

EVENT
SEVERITY:  1
MESSAGE:  cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LOCATOR
    LINE NUMBER:  3
    COLUMN NUMBER:  25
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null

EVENT
SEVERITY:  1
MESSAGE:  cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LOCATOR
    LINE NUMBER:  3
    COLUMN NUMBER:  25
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null

EVENT
SEVERITY:  1
MESSAGE:  cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LOCATOR
    LINE NUMBER:  7
    COLUMN NUMBER:  12
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null



Marshal Demo

To enable validation an instance of Schema must be set on the Marshaller.  To handle the events an implementation of ValidationEventHandler must also be set.

package blog.jaxb.validation;

import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

public class MarshalDemo {

    public static void main(String[] args) throws Exception {
        Customer customer = new Customer();
        customer.setName("Jane Doe");
        customer.getPhoneNumbers().add(new PhoneNumber());
        customer.getPhoneNumbers().add(new PhoneNumber());
        customer.getPhoneNumbers().add(new PhoneNumber());

        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
        Schema schema = sf.newSchema(new File("customer.xsd")); 

        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setSchema(schema);
        marshaller.setEventHandler(new MyValidationEventHandler());
        marshaller.marshal(customer, System.out);
    }

}

Output

The validation performed during the marshal raised 3 events.  The first 2 events are related to the text value of the "name" element being too long.  The 3rd event is related to the extra "phone-number" element.

EVENT
SEVERITY:  1
MESSAGE:  cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LINKED EXCEPTION:  org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LOCATOR
    LINE NUMBER:  -1
    COLUMN NUMBER:  -1
    OFFSET:  -1
    OBJECT:  blog.jaxb.validation.Customer@10045eb
    NODE:  null
    URL:  null

EVENT
SEVERITY:  1
MESSAGE:  cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LINKED EXCEPTION:  org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LOCATOR
    LINE NUMBER:  -1
    COLUMN NUMBER:  -1
    OFFSET:  -1
    OBJECT:  blog.jaxb.validation.Customer@10045eb
    NODE:  null
    URL:  null

EVENT
SEVERITY:  1
MESSAGE:  cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LINKED EXCEPTION:  org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException: cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child element '{phone-number}' is expected at this point.
LOCATOR
    LINE NUMBER:  -1
    COLUMN NUMBER:  -1
    OFFSET:  -1
    OBJECT:  blog.jaxb.validation.Customer@10045eb
    NODE:  null
    URL:  null
<customer>
   <name>Jane Doe</name>
   <phone-number/>
   <phone-number/>
   <phone-number/>
</customer>

Further Reading

If you enjoyed this post, then you may also be interested in:

10 comments:

  1. Hi,
    why the Node field of Locator is always null?
    How can I get it valorized?

    ReplyDelete
  2. Hi, I've too needed these values... that are always null, why?

    ReplyDelete
  3. Hi Macs & chesteric31,

    The node and url properties are not always available to be set on the locator. That being said, I believe there are currently use cases where they are available and are not being returned. I have entered the following EclipseLink bug for this issue:

    - https://bugs.eclipse.org/346542

    -Blaise

    ReplyDelete
  4. Hi, very interesting article. I was wondering if this type of validation could also be done for Unmarshalling using a streamReader. (StAX and JAXB)

    ReplyDelete
  5. Hi Randall,

    Schema validation for marshalling and unmarshalling is available for streams, DOM, SAX, and StAX.

    -Blaise

    ReplyDelete
  6. I am developing web service using JAX WS,
    my coursework is to utilise web services to compose a travel agency. The travel agency consists of three independent services: flight booking, hotel reservation, and currency conversion. i will build the first two application services by myself but i should consume a publically available service for currency conversion.

    My issue is getting the flight information from java objects to xml document,
    would you please hlp me in this issue,

    Thanks for your time.
    Chao Fan,

    @WebMethod(operationName = “Flightfile”)
    public Boolean CreateItinerary() {
    Flights ss = new Flights();
    List newSegments = (List) setSegments.getFlightSegments();
    ss toflights;
    toflights = new ss();
    toflights.setAirline(“American Airlines”);
    toflights.setOriginCity(“long beach”); toflights.setDestinationCity(“aus islands”);
    try {
    javax.xml.bind.JAXBContext jaxbCtx =
    javax.xml.bind.JAXBContext.newInstance(ss.getClass().getPackage().getName());
    javax.xml.bind.Marshaller marshaller = jaxbCtx.createMarshaller();
    marshaller.setProperty(javax.xml.bind.Marshaller.JAXB_ENCODING, “UTF-8″); //NOI18N
    marshaller.setProperty(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    File flightstore = new File(“file.xml”);
    marshaller.marshal(ss, flightstore);

    ReplyDelete
  7. I have one doubt that if I had actually used JAXB and gotmy classes\stubs from a schema (e.g. abc.xsd).. then those classes are tightly linked to that schema.

    So, is there any need to again do a schema validation, because anyway now my classes\beans are already tightly linked to the schema using which they were generated.

    ReplyDelete
    Replies
    1. Agreed that an object model generated from an XML schema ensures that most of the structure will be valid against the XML schema. There are a few items that you may still wish to validate:

      - Collections with a maxOccurs other that unbounded aren't exceeded.
      - The values that correspond to simple types with facets are valid.
      - Non-nillable required elements are present in the document.
      - etc.

      Delete
  8. Thanks for all the great code! Please add a variation that shows how to use an XML catalog, which allows mapping of URIs to local files for convenience when coping with un-hosted schemas and disconnected computers.

    ReplyDelete
    Replies
    1. Hi Chris,

      I will try to add a post on this. In the mean time you can leverage the setResourceResolver method on SchemaFactory for this.
      - http://docs.oracle.com/javase/7/docs/api/javax/xml/validation/SchemaFactory.html#setResourceResolver%28org.w3c.dom.ls.LSResourceResolver%29

      You will need to implement an LSResourceResolver to pull in the local schemas:
      - http://docs.oracle.com/javase/7/docs/api/org/w3c/dom/ls/LSResourceResolver.html

      -Blaise

      Delete