In my opinion the problem you have is a validation issue. Input validation today is an inevitable responsibility of the developer. In case of data files like JSON or XML this can easily be done by using schema validation. This validation needs to be integrated into the data conversion process, recommendable in both marshalling and unmarhalling. In case of null values or any other inappropriate input, like a to short post code, etc. an JAXBException will be thrown, which in turn can be wrapped into a more expressive exception. Finally you have to declare an ExceptionMapper which will catch the exception and responde with an appropriaten message to the caller. Below you will find an example.
The entity class Address.java
@XmlRootElement(name = "address", namespace = DataEntity.NSP)
public class Address implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2508433924387468026L;
private Integer id;
private String street;
private String number;
private Short zipCode;
private String city;
protected Address() {
}
public Address(final Integer id, final String street, final String number,
final Short zipCode, final String city) {
setId(id);
setStreet(street);
setNumber(number);
setZipCode(zipCode);
setCity(city);
}
@XmlAttribute
public Integer getId() {
return id;
}
private void setId(Integer id) {
ValidationHelper.validateId(id);
this.id = id;
}
@XmlElement(namespace = DataEntity.NSP)
public String getStreet() {
return street;
}
private void setStreet(String street) {
ValidationHelper.validateAddressStreet(street);
this.street = street;
}
@XmlElement(namespace = DataEntity.NSP)
public String getNumber() {
return number;
}
private void setNumber(String number) {
ValidationHelper.validateAddressNumber(number);
this.number = number;
}
@XmlElement(namespace = DataEntity.NSP)
public Short getZipCode() {
return zipCode;
}
private void setZipCode(Short zipCode) {
ValidationHelper.validateAddressZipCode(zipCode);
this.zipCode = zipCode;
}
@XmlElement(namespace = DataEntity.NSP)
public String getCity() {
return city;
}
private void setCity(String city) {
ValidationHelper.validateAddressCity(city);
this.city = city;
}
}
The schema validation files:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="https://localhost/JAX-RS_BookStore_Service/xsd/" targetNamespace="https://localhost/JAX-RS_BookStore_Service/xsd/"
elementFormDefault="qualified" xmlns:pref="https://localhost/JAX-RS_BookStore_Service/xsd/">
<xs:include schemaLocation="Types.xsd" />
<xs:element name="address" type="addressType" />
</xs:schema>
The generic xsd types:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="https://localhost/JAX-RS_BookStore_Service/xsd/"
targetNamespace="https://localhost/JAX-RS_BookStore_Service/xsd/"
elementFormDefault="qualified" xmlns:pref="https://localhost/JAX-RS_BookStore_Service/xsd/">
<xs:simpleType name="unsignedInteger">
<xs:restriction base="xs:integer">
<xs:minInclusive value="1" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="addressStreet">
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-ZöäüÖÄÜß -]{2,32}" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="addressNumber">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{1}[0-9a-z/\\ -]{0,7}" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="addressCity">
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-ZöäüÖÄÜß -]{2,32}" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="zipCodeMoreThan4Digits">
<xs:restriction base="xs:short">
<xs:minInclusive value="1000"></xs:minInclusive>
<xs:maxInclusive value="9999"></xs:maxInclusive>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="addressType">
<xs:sequence>
<xs:element name="city" type="addressCity" />
<xs:element name="number" type="addressNumber" />
<xs:element name="street" type="addressStreet" />
<xs:element name="zipCode" type="zipCodeMoreThan4Digits" />
</xs:sequence>
<xs:attribute name="id" type="unsignedInteger" use="required">
</xs:attribute>
</xs:complexType>
</xs:schema>
The abstract MessageBodyReader which is used by several implementations. In this class the XML input is validated against the schema file.
public abstract class AbstractXmlValidationReader<T> implements
MessageBodyReader<T> {
private final Providers providers;
private final Schema schema;
/**
* Instantiates the XML validation class (MessageBodyReader)
*
* @param providers
* @param servletContext
* @param xsdFileName
*/
public AbstractXmlValidationReader(final Providers providers,
final ServletContext servletContext, final String xsdFileName) {
this.providers = providers;
try {
SchemaFactory sf = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
File xsd = new File(servletContext.getRealPath(xsdFileName));
schema = sf.newSchema(xsd);
} catch (Exception e) {
throw new RuntimeException(
"Unable to create XSD validation schema", e);
}
}
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
@SuppressWarnings("unchecked")
Class<T> readableClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
if (type == readableClass
&& type.isAnnotationPresent(XmlRootElement.class)) {
return true;
}
return false;
}
/**
* Source adapted from: <a href=
* "http://stackoverflow.com/questions/3428273/validate-jaxbelement-in-jpa-jax-rs-web-service"
* >stackoverflow.com</a>
*/
@Override
public T readFrom(Class<T> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers
.getContextResolver(JAXBContext.class, mediaType);
if (null != resolver) {
jaxbContext = resolver.getContext(type);
}
if (null == jaxbContext) {
jaxbContext = JAXBContext.newInstance(type);
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
@SuppressWarnings("unchecked")
T entity = (T) unmarshaller.unmarshal(entityStream);
return entity;
} catch (JAXBException e) {
throw new MessageBodyReaderValidationException(
"Failure while performing xml validation or xml marhalling!",
e);
}
}
}
The concreate MeassagBodyReader implementation, which provides the specific XSD file.
@Provider
@Consumes(MediaType.APPLICATION_XML)
public class AddressXmlValidationReader extends
AbstractXmlValidationReader<Address> {
private final static String xsdFileName = "/xsd/Address.xsd";
public AddressXmlValidationReader(@Context Providers providers,
@Context ServletContext servletContext) {
super(providers, servletContext, xsdFileName);
}
}
And finally the specific ExceptionMapper. The response-body in this example just contains the exception message. Probably this should be changed to sth. different, like HTML.
Don't forget to declare this class within the service Application class! classes.add(MessageBodyReaderValidationExceptionMapper.class);
@Provider
public class MessageBodyReaderValidationExceptionMapper implements
ExceptionMapper<MessageBodyReaderValidationException> {
@Override
public Response toResponse(MessageBodyReaderValidationException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
}
}
The result of a POST request by using Firefox RESTClient plugin.
Note that the content of the <street> tag is empty, NULL value!
