10

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.

Does someone know of an example/tutorial that shows how to do this?

Thanks!

bdoughan
  • 147,609
  • 23
  • 300
  • 400
sdoca
  • 7,832
  • 23
  • 70
  • 127

2 Answers2

15

You 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;
            schema = 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);
        }
    }

}
Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • In the end I didn't need to validate against a schema, but this is great reference info if I do need to in the future. – sdoca Aug 18 '10 at 20:30
9

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:

import 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;
}

Override the validate method and annotate the sub-class with @Provider and we're done.

GWTNewbie
  • 395
  • 4
  • 16
  • Hello, thanks for this. can you please answer a related question here:http://stackoverflow.com/questions/12530155/jax-rs-request-validation-using-xsd-jaxb – adi Sep 24 '12 at 09:40
  • I must be missing something - this class doesn't actually do any of the validation? – DeejUK Sep 13 '13 at 11:04
  • @Deejay This was sometime ago and I no longer even work in the same place. But indeed, the abstract class doesn't do the validation. It just creates the type and the validation is done in the sub class. Obviously looking at it now, I can imagine tweaking the abstract super class to set the schema and do the unmarshalling with it as well. This all depends on your needs. – GWTNewbie Sep 16 '13 at 12:18