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).
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:
Hi,
ReplyDeletewhy the Node field of Locator is always null?
How can I get it valorized?
Hi, I've too needed these values... that are always null, why?
ReplyDeleteHi Macs & chesteric31,
ReplyDeleteThe 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
Hi, very interesting article. I was wondering if this type of validation could also be done for Unmarshalling using a streamReader. (StAX and JAXB)
ReplyDeleteHi Randall,
ReplyDeleteSchema validation for marshalling and unmarshalling is available for streams, DOM, SAX, and StAX.
-Blaise
I am developing web service using JAX WS,
ReplyDeletemy 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);
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.
ReplyDeleteSo, 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.
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:
Delete- 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.
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.
ReplyDeleteHi Chris,
DeleteI 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