This post shown how JAXB2 Schema validation can be used in your project, validating the generated XML against specific XML Schema Definition [XSD].
An XML Schema Definition describes the structure of an XML document and governs the rules and constraints being applied on XML content. JAXB2 provides API to validate the generated XML against an XSD in order to verify the correctness of generated XML.
There are choices available to perform Schema Validation
Below is the XSD file [“edu.xsd”] used to validate the XML generated via Student & University mapping example. Please note that Section element in below XSD contains a minLength constraint(4 chars). We will verify the validation against this constraint.
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Student"> <xs:complexType> <xs:sequence> <xs:element name="firstName" type="xs:string" /> <xs:element name="lastName" type="xs:string" /> <xs:element name="birthDate" type="xs:date" /> <xs:element name="section" > <xs:simpleType> <xs:restriction base="xs:string"> <xs:minLength value="4"/> </xs:restriction> </xs:simpleType> </xs:element> </xs:sequence> <xs:attribute name="id" type="xs:int" /> </xs:complexType> </xs:element> <xs:element name="University"> <xs:annotation> <xs:documentation>University Details</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="name" type="xs:string" /> <xs:element name="address" type="xs:string" /> <xs:element name="Students"> <xs:complexType> <xs:sequence> <xs:element ref="Student" minOccurs="1" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Following steps are involved in validation:
// create JAXB context JAXBContext context = JAXBContext.newInstance(Student.class);
SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ); Schema schema = sf.newSchema(new File("edu.xsd"));
Create Marshaller to Generate XML from mapped and populated Java objects using JAXBContext created above. Set the Marshaller schema to the one created above.
// create Marshaller using JAXB context Marshaller m = context.createMarshaller(); // To format the [to be]generated XML output m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); //Set Schema m.setSchema( schema ); // Marshall the mapped object and write output to System.out [or to a file] m.marshal(university, System.out); m.marshal(university, new File(SOME_FILE));
Use Javax.xml.validation.Validator class to validate.
JAXBSource source = new JAXBSource(context, university); Validator validator = schema.newValidator(); validator.setErrorHandler(new CustomValidationErrorHandler()); validator.validate(source);
Provide an ErrorHandler implementation required by Validator.
package com.websystique.xml.model; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class CustomValidationErrorHandler implements ErrorHandler{ public void warning(SAXParseException exception) throws SAXException { System.out.println("WARNING Occured"); exception.printStackTrace(); } public void error(SAXParseException exception) throws SAXException { System.out.println("ERROR Occured"); exception.printStackTrace(); } public void fatalError(SAXParseException exception) throws SAXException { System.out.println("FATAL ERROR Occured"); exception.printStackTrace(); } }
That’s it. In case the generated XML does not comply with specified schema , Marshaller/Validator will throw error and eventually fail.
In our example, section property in Student class contains a minLength (4) constraints. In case the populated object is not meeting this constraint:
s1.setSection("Com");
then Marshaller will generate following error:
Exception in thread "main" javax.xml.bind.MarshalException - with linked exception: [org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.] at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317) at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243) at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75) at com.websystique.xml.JaxbSchemaValidation.main(JaxbSchemaValidation.java:78) Caused by: org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'. at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195) at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:131) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:384) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:318) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:423) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3188) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:3103) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:3013) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:2156) at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:824) at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:565) at org.xml.sax.helpers.XMLFilterImpl.endElement(XMLFilterImpl.java:546) at com.sun.xml.internal.bind.v2.runtime.output.SAXOutput.endTag(SAXOutput.java:117) at com.sun.xml.internal.bind.v2.runtime.output.XmlOutputAbstractImpl.endTag(XmlOutputAbstractImpl.java:109) at com.sun.xml.internal.bind.v2.runtime.output.ForkXmlOutput.endTag(ForkXmlOutput.java:76) at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:294) at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179) at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166) at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239) at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87) at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306) at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:664) at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:54) at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:157) at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:141) at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306) at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561) at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290) at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462) at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314) ... 3 more
That’s all with validations. Below is the complete example for this post.
Following technologies being used:
Let’s begin.
Following will be the final project directory structure for this example:
Now let’s add the content mentioned in above structure explaining each in detail.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.xml</groupId> <artifactId>JaxbBasicDemo</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>JaxbBasicDemo</name> <properties> <joda-time.version>2.7</joda-time.version> </properties> <dependencies> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${joda-time.version}</version> </dependency> </dependencies> </project>
There is no special dependencies to be declared. Since i am using Joda Time for any date time related processing (and also to show an important feature of JAXB), i’ll declare that here. You can skip it if you prefer to use Java Date api for the same.
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Student"> <xs:complexType> <xs:sequence> <xs:element name="firstName" type="xs:string" /> <xs:element name="lastName" type="xs:string" /> <xs:element name="birthDate" type="xs:date" /> <xs:element name="section" > <xs:simpleType> <xs:restriction base="xs:string"> <xs:minLength value="4"/> </xs:restriction> </xs:simpleType> </xs:element> </xs:sequence> <xs:attribute name="id" type="xs:int" /> </xs:complexType> </xs:element> <xs:element name="University"> <xs:annotation> <xs:documentation>University Details</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="name" type="xs:string" /> <xs:element name="address" type="xs:string" /> <xs:element name="Students"> <xs:complexType> <xs:sequence> <xs:element ref="Student" minOccurs="1" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Create Sample Java classes [POJOs], and annotate them with appropriate JAXB annotations, in order to provide mapping between Java and plain XML.
com.websystique.xml.model.Student
package com.websystique.xml.model; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.joda.time.LocalDate; @XmlRootElement(name = "Student") @XmlType(propOrder = { "firstName", "lastName", "dob", "section" }) public class Student { private int id; private String firstName; private String lastName; private LocalDate dob; private String section; @XmlAttribute(name = "id") public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getSection() { return section; } public void setSection(String section) { this.section = section; } @XmlElement(name = "birthDate") @XmlJavaTypeAdapter(LocalDateAdapter.class) public LocalDate getDob() { return dob; } public void setDob(LocalDate dob) { this.dob = dob; } @Override public String toString() { return "Student [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", dob=" + dob + ", section=" + section + "]"; } }
com.websystique.xml.model.University
package com.websystique.xml.model; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement(name = "University") @XmlType(propOrder = { "name", "address", "students" }) public class University { List<Student> students; private String name; private String address; @XmlElementWrapper(name = "Students") @XmlElement(name = "Student") public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
This post depicts an example of Student and University relation. Each student can have several properties. A University can have several properties and additionally multiple students.
Student and University classes[both are pojo’s] and there properties, are annotated with JAXB annotations. These annotations tells JAXB which property of a Java object will be mapped to which element or attribute in XML.
@XmlRootElement
can be used to map a java class to the root of XML document produced by that class. Once applied on a class, all properties of that class maps to XML elements. You can however , use @XmlElement
on individual property to override default behavior( for example specify an Element with different name in XML than in java, specify required/optional/nillable attributes).For example Student.dob will be mapped to birthDate in XML.
@XmlElementWrapper
can be used to create a wrapper around a collection of elements.
@XmlType
can be used to specify the order in which the properties of a java object will appear in XML document.
@XmlAttribute
can be used to map a property to an XML attribute.
Note that @XmlAttribute and @XmlElement can be applied on field level or on method level. In our case we are using them on methods.
Adapters are used to convert java data types into XML types and vice versa. You may have noticed that we are using Joda-time LocalDate
class to specify dob property in Student class. But JAXB knows nothing about LocalDate. So we need to provide a way to help JAXB map this property into xml format. That’s where XmlAdapter
comes into play. Using XmlAdapter you can convert Java Type into XML type. In our case we are converting LocalDate to String format. We can specify the adapter to be used using @XmlJavaTypeAdapter
on respective property in java.
com.websystique.xml.model.LocalDateAdapter
package com.websystique.xml.model; import javax.xml.bind.annotation.adapters.XmlAdapter; import org.joda.time.LocalDate; public class LocalDateAdapter extends XmlAdapter<String, LocalDate> { public LocalDate unmarshal(String v) throws Exception { return new LocalDate(v); } public String marshal(LocalDate v) throws Exception { return v.toString(); } }
Above class provides a way to convert a LocalDate type into String and viceversa.
Create a Custom error handler to be used by Validator during schema validation.
package com.websystique.xml.model; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; public class CustomValidationErrorHandler implements ErrorHandler{ public void warning(SAXParseException exception) throws SAXException { System.out.println("WARNING Occured"); exception.printStackTrace(); } public void error(SAXParseException exception) throws SAXException { System.out.println("ERROR Occured"); exception.printStackTrace(); } public void fatalError(SAXParseException exception) throws SAXException { System.out.println("FATAL ERROR Occured"); exception.printStackTrace(); } }
Below is the complete Main class:
com.websystique.xml.JaxbSchemaValidation
package com.websystique.xml; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.util.JAXBSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.joda.time.LocalDate; import org.xml.sax.SAXException; import com.websystique.xml.model.CustomValidationErrorHandler; import com.websystique.xml.model.Student; import com.websystique.xml.model.University; public class JaxbSchemaValidation { private static final String XML_FILE = "education_centers.xml"; public static void main(String[] args) throws JAXBException, SAXException, IOException { JaxbSchemaValidation schemaValidation = new JaxbSchemaValidation(); List<Student> students = new ArrayList<Student>(); Student s1 = new Student(); s1.setId(1); s1.setFirstName("Alan"); s1.setLastName("Turing"); s1.setSection("Com"); s1.setDob(new LocalDate(1956, 10, 1)); students.add(s1); Student s2 = new Student(); s2.setId(2); s2.setFirstName("Thomas"); s2.setLastName("Edison"); s2.setSection("Artio"); s2.setDob(new LocalDate(1916, 3, 3)); students.add(s2); Student s3 = new Student(); s3.setId(3); s3.setFirstName("Linus"); s3.setLastName("Torvald"); s3.setSection("Computer Science"); s3.setDob(new LocalDate(1958, 11, 4)); students.add(s3); University university = new University(); university.setName("Cambridge"); university.setAddress("England"); university.setStudents(students); SchemaFactory sf = SchemaFactory .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = sf.newSchema(schemaValidation.getSchemaFile()); // create JAXB context JAXBContext context = JAXBContext.newInstance(University.class); System.out.println("<!----------Generating the XML Output-------------->"); // Instantiate marshaller via context Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.setSchema(schema); // Write to System.out m.marshal(university, System.out); m.marshal(university, new File(XML_FILE)); System.out.println("<!---------------Generating the Java objects from XML Input-------------->"); // Instantiate Unmarshaller via context Unmarshaller um = context.createUnmarshaller(); University unif = (University) um.unmarshal(new FileReader(XML_FILE)); List<Student> studentsList = unif.getStudents(); for (Student s : studentsList) { System.out.println("Student : " + s); } // JAXBSource source = new JAXBSource(context, university); // Validator validator = schema.newValidator(); // validator.setErrorHandler(new CustomValidationErrorHandler()); // validator.validate(source); } private File getSchemaFile() { // Get file from resources folder ClassLoader classLoader = getClass().getClassLoader(); File schemaFile = new File(classLoader.getResource("edu.xsd").getFile()); return schemaFile; } }
Below is the generated output:
Exception in thread "main" javax.xml.bind.MarshalException - with linked exception: [org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'.] at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:317) at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243) at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75) at com.websystique.xml.JaxbSchemaValidation.main(JaxbSchemaValidation.java:81) Caused by: org.xml.sax.SAXParseException: cvc-minLength-valid: Value 'Com' with length = '3' is not facet-valid with respect to minLength '4' for type '#AnonType_sectionStudent'. at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:195)
With correct input[constraint meets], we will see following output
<!----------Generating the XML Output--------------> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <University> <name>Cambridge</name> <address>England</address> <Students> <Student id="1"> <firstName>Alan</firstName> <lastName>Turing</lastName> <birthDate>1956-10-01</birthDate> <section>Comp</section> </Student> <Student id="2"> <firstName>Thomas</firstName> <lastName>Edison</lastName> <birthDate>1916-03-03</birthDate> <section>Artio</section> </Student> <Student id="3"> <firstName>Linus</firstName> <lastName>Torvald</lastName> <birthDate>1958-11-04</birthDate> <section>Computer Science</section> </Student> </Students> </University> <!---------------Generating the Java objects from XML Input--------------> Student : Student [id=1, firstName=Alan, lastName=Turing, dob=1956-10-01, section=Comp] Student : Student [id=2, firstName=Thomas, lastName=Edison, dob=1916-03-03, section=Artio] Student : Student [id=3, firstName=Linus, lastName=Torvald, dob=1958-11-04, section=Computer Science]
That’s it. In the next post , we will discuss about the code generation using XSD.
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…