n this post I will demonstrate how to validate a JAXB object model against an XML schema. This will be done using the
javax.xml.validation APIs.
A Validator will be created from the customer XML Schema. This Validator can accept different types of XML inputs. We will leverage JAXBSource (which implements javax.xml.transform.Source ) to expose our JAXB object model as an XML input to the Validator.
An ErrorHandler provides a mechanism to capture the validation exceptions. If you re-throw the exception then parsing stops, and if you swallow the exception parsing continues. This provides a useful mechanism to ignore validation constraints.
Output
The following output is produced when EclipseLink MOXy is used as the JAXB implementation.
Background Information
In JAXB 1.0 there was the concept of a Validator. This Validator could be used to determine if when marshalled a graph of objects would produce valid XML. Since JAXB 1.0 had standard interfaces backed by vendor specific implementation classes it was easy for vendors to code generate the necessary validation logic. When JAXB 2.0 moved to annotated POJO classes the ability to have generated validation logic was lost and Validator was deprecated. In this post I'll demonstrate how to leverage the j
avax.xml.validation APIs to get the same sort of behaviour.
Java Model
The following object model will be used for this example. Notice how the model classes do not contain any validation specific information.
The following object model will be used for this example. Notice how the model classes do not contain any validation specific information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package
blog.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;
public
Customer() {
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;
}
}
|
1
2
3
4
5
|
package
blog.validation;
public
class
PhoneNumber {
}
|
XML Schema
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
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:element
>
<
xs:simpleType
name
=
"stringMaxSize5"
>
<
xs:restriction
base
=
"xs:string"
>
<
xs:maxLength
value
=
"5"
/>
</
xs:restriction
>
</
xs:simpleType
>
</
xs:schema
>
|
Demo Code
In this example we will create an instance of Customer that would produce an XML document that does not conform to our XML schema. The customers name will be too long, and it will contain more than two phone numbers.
A Validator will be created from the customer XML Schema. This Validator can accept different types of XML inputs. We will leverage JAXBSource (which implements javax.xml.transform.Source ) to expose our JAXB object model as an XML input to the Validator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package
blog.validation;
import
java.io.File;
import
javax.xml.XMLConstants;
import
javax.xml.bind.JAXBContext;
import
javax.xml.bind.util.JAXBSource;
import
javax.xml.validation.Schema;
import
javax.xml.validation.SchemaFactory;
import
javax.xml.validation.Validator;
public
class
Demo {
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());
JAXBContext jc = JAXBContext.newInstance(Customer.
class
);
JAXBSource source =
new
JAXBSource(jc, customer);
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(
new
File(
"customer.xsd"
));
Validator validator = schema.newValidator();
validator.setErrorHandler(
new
MyErrorHandler());
validator.validate(source);
}
}
|
An ErrorHandler provides a mechanism to capture the validation exceptions. If you re-throw the exception then parsing stops, and if you swallow the exception parsing continues. This provides a useful mechanism to ignore validation constraints.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
blog.validation;
import
org.xml.sax.ErrorHandler;
import
org.xml.sax.SAXException;
import
org.xml.sax.SAXParseException;
public
class
MyErrorHandler
implements
ErrorHandler {
public
void
warning(SAXParseException exception)
throws
SAXException {
System.out.println(
"\nWARNING"
);
exception.printStackTrace();
}
public
void
error(SAXParseException exception)
throws
SAXException {
System.out.println(
"\nERROR"
);
exception.printStackTrace();
}
public
void
fatalError(SAXParseException exception)
throws
SAXException {
System.out.println(
"\nFATAL ERROR"
);
exception.printStackTrace();
}
}
|
Output
The following output is produced when EclipseLink MOXy is used as the JAXB implementation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
ERROR
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'
.
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:
417
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:
3181
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:
3096
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:
3006
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:
2149
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:
817
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:
563
)
at org.xml.sax.helpers.XMLFilterImpl.endElement(XMLFilterImpl.java:
546
)
at org.eclipse.persistence.oxm.record.ContentHandlerRecord.endElement(ContentHandlerRecord.java:
235
)
at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:
315
)
at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:
325
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
934
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
648
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
608
)
at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:
233
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
222
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
210
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.validate(ValidatorHandlerImpl.java:
697
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:
97
)
at javax.xml.validation.Validator.validate(Validator.java:
127
)
at blog.validation.Demo.main(Demo.java:
29
)
ERROR
org.xml.sax.SAXParseException: cvc-type.
3.1
.
3
: The value
'Jane Doe'
of element
'name'
is not valid.
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:
417
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:
3181
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:
3097
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:
3006
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:
2149
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:
817
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:
563
)
at org.xml.sax.helpers.XMLFilterImpl.endElement(XMLFilterImpl.java:
546
)
at org.eclipse.persistence.oxm.record.ContentHandlerRecord.endElement(ContentHandlerRecord.java:
235
)
at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:
315
)
at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:
325
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
934
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
648
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
608
)
at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:
233
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
222
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
210
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.validate(ValidatorHandlerImpl.java:
697
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:
97
)
at javax.xml.validation.Validator.validate(Validator.java:
127
)
at blog.validation.Demo.main(Demo.java:
29
)
ERROR
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.
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:
417
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:
3181
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidComplexType(XMLSchemaValidator.java:
3168
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:
3104
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:
3006
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:
2149
)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:
817
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:
563
)
at org.xml.sax.helpers.XMLFilterImpl.endElement(XMLFilterImpl.java:
546
)
at org.eclipse.persistence.oxm.record.ContentHandlerRecord.endElement(ContentHandlerRecord.java:
235
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
961
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
648
)
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:
608
)
at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:
233
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
222
)
at javax.xml.bind.util.JAXBSource$
1
.parse(JAXBSource.java:
210
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.validate(ValidatorHandlerImpl.java:
697
)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:
97
)
at javax.xml.validation.Validator.validate(Validator.java:
127
)
at blog.validation.Demo.main(Demo.java:
29
)
|
Summary
This example demonstrates the benefit of standards. JAXB was not written specifically to work with
javax.xml.validation APIs, but since it can be exposed as a
javax.xml.transform.Source (using JAXBSource) it does.
In future posts I write more about schema validation, and other uses for JAXBSource.