I have a JAX-RS webservice (Jersey) that is a CRUD interface for JPA (EclipseLink) entities. My entities were autogenerated from the database tables and I have annotated them with JAXB annotations so that they can be marshalled/unmarshalled to/from XML. My resource methods take JAXBElement objects as a parameter where required.
I don't have an XSD, however, I'm willing to write one to validate the XML received in the requests. But, I don't know how to initiate the validation. Jersey is automatically handling the marshalling/unmarshalling and any references I've found about validation is done at that level.
uou could handle this by creating a custom MessageBodyReader. The example below is based on a Customer model:
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URL;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
@Provider
@Consumes("application/xml")
public class ValidatingReader implements MessageBodyReader<Customer> {
@Context
protected Providers providers;
private Schema schema;
public ValidatingReader() {
try {
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
URL schemaURL = null;
sf.newSchema(schemaURL);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2, MediaType arg3) {
return arg0 == Customer.class;
}
public Customer readFrom(Class<Customer> arg0, Type arg1, Annotation[] arg2, MediaType arg3, MultivaluedMap<String, String> arg4, InputStream arg5)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, arg3);
if(null != resolver) {
jaxbContext = resolver.getContext(arg0);
}
if(null == jaxbContext) {
jaxbContext = JAXBContext.newInstance(arg0);
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
return (Customer) unmarshaller.unmarshal(arg5);
} catch(JAXBException e) {
throw new RuntimeException(e);
}
}
}
We can go one step further and create a generic (abstract) ValidatingReader which can be sub-classed as and when needed. This is what I've done, thanks to the idea from Blaise:
mport java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
public abstract class AbstractValidatingReader<T> implements
MessageBodyReader<T> {
@Context
protected Providers providers;
@SuppressWarnings("unchecked")
@Override
public boolean isReadable(Class<?> arg0, Type arg1, Annotation[] arg2,
MediaType arg3) {
Class<T> readableClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
return arg0 == readableClass;
}
@SuppressWarnings("unchecked")
@Override
public T readFrom(Class<T> arg0, Type arg1, Annotation[] arg2,
MediaType arg3, MultivaluedMap<String, String> arg4,
InputStream arg5) throws IOException, WebApplicationException {
T type = null;
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers.getContextResolver(
JAXBContext.class, arg3);
try {
if (resolver != null) {
jaxbContext = resolver.getContext(arg0);
}
if (jaxbContext == null) {
jaxbContext = JAXBContext.newInstance(arg0);
}
type = (T) jaxbContext.createUnmarshaller().unmarshal(arg5);
validate(type);
} catch (JAXBException e) {
throw new WebApplicationException(
Response.Status.INTERNAL_SERVER_ERROR);
}
return type;
}
protected abstract void validate(T arg0) throws WebApplicationException;
}
That will not work because the JAXB reader wraps the
UnmarshalException around a WebApplicationException.
public final Object readFrom(
Class<Object> type,
Type genericType,
Annotation annotations[],
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException {
try {
return readFrom(type, mediaType, getUnmarshaller(type,
mediaType), entityStream);
} catch (UnmarshalException ex) {
throw new WebApplicationException(ex, 400);
} catch (JAXBException ex) {
throw new WebApplicationException(ex, 500);
}
}
Two suggestions:
1) map WebApplicationException and check the cause, if the cause is
not what you expect return the response from
the WebApplicationException, otherwise look at the exception and
generate a response.
2) map WebApplicationException and inject @Context Providers ps. Map
UnmarshalException.
Then in the mapped WebApplicationException check if there is an
exception mapper for the cause by calling
ps.getExceptionMapper(cause). If non-null return the response
from that mapper, otherwise return the response for
the WebApplicationException.
I think we could support something in Jersey along the approach of 2).
Namely Jersey defined a DeferredWebApplicationException and
DeferredWebApplicationExceptionMapper (which is registered by Jersey).
The JAXB stuff throws DeferredWebApplicationExceptionMapper which
means you could register an exception mapper for UnmarshalException or
JAXBException. The DeferredWebApplicationExceptionMapper could ensure
the status code of the response in WebApplicationException correctly
takes priority.