Validate JAXBElement in JPA/JAX-RS Web Service

13,308

Solution 1

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

}

Solution 2

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.

Share:
13,308
sdoca
Author by

sdoca

Updated on June 09, 2022

Comments

  • sdoca
    sdoca almost 2 years

    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!

  • sdoca
    sdoca over 13 years
    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.
  • adi
    adi over 11 years
    Hello, thanks for this. can you please answer a related question here:stackoverflow.com/questions/12530155/…
  • DeejUK
    DeejUK over 10 years
    I must be missing something - this class doesn't actually do any of the validation?
  • GWTNewbie
    GWTNewbie over 10 years
    @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.