Search This Blog

Tuesday, October 4, 2011

Jersey JAX-RS and JAXB Schema Validation

This BLOG is about Jersey web services and Schema based validation with the same. I have also been playing with Google Guice (I hear it, Spring Traitor, err not quite) :-) and figured I'd use Guice instead of Spring for once.

When un-marshalling XML using JAXB, schema based validation facilitates stricter validation. The Jersey recommended approach to enable Schema based validation is to create a javax.ws.rs.ext.ContextResolver. I have seen examples of using a javax.ws.rs.ext.MessageBodyReader as well. The code demonstrated is largely based and influenced by the discussion on XSD validation between Andrew Cole and Paul Sandoz. The goals of the resolver are:
  1. Enable schema based validation if desired
  2. Provide the ability to enable a custom validation event handler
  3. Enable formatted JAXB and the ability to set the character encoding
It is said that JAXB contexts are better of cached as they are expensive to create. I am only going by what I have read in different posts and/or discussions and do not have any metrics to claim the same. A generic JAXB Context resolver is shown here that accomplishes the above:
public class JaxbContextResolver implements ContextResolver&<JAXBContext> {
  static final ConcurrentMap<String, JAXBContext> CONTEXT_MAP = new MapMaker()
      .makeComputingMap(new Function<String, JAXBContext>() {

        @Override
        public JAXBContext apply(String fromPackage) {
          try {
            return JAXBContext.newInstance(fromPackage);
          }
          catch (JAXBException e) {
            throw new RuntimeException("Unable to create JAXBContext for Package:" + fromPackage, e);
          }
        }
      });
  
  private Schema schema;
  private ValidationEventHandler validationEventHandler;
  
  public JaxbContextResolver withSchema(Schema schema) {
    this.schema = schema;
    return this;
  }
  ...
  public JaxbContextResolver withValidationEventHandler(ValidationEventHandler validationEventHandler) {
    this.validationEventHandler = validationEventHandler;
    return this;
  }
  
  @Override
  public JAXBContext getContext(Class<?> type) {
    return new ValidatingJAXBContext(CONTEXT_MAP.get(type.getPackage().getName()),
      schema, formattedOutput, encoding, validationEventHandler);
  }
   ...
  public static class ValidatingJAXBContext extends JAXBContext {
    private final JAXBContext contextDelegate;
     ....
    private final ValidationEventHandler validationEventHandler;

    @Override
    public javax.xml.bind.Unmarshaller createUnmarshaller() throws JAXBException {
      javax.xml.bind.Unmarshaller unmarshaller = contextDelegate.createUnmarshaller();
      
      // Set the Validation Handler
      if (validationEventHandler != null) {
        unmarshaller.setEventHandler(validationEventHandler);
      }
      
      // Set the Schema
      if (validatingSchema != null) {
        unmarshaller.setSchema(validatingSchema);
      }

      return unmarshaller;
    }

    @Override
    public javax.xml.bind.Marshaller createMarshaller() throws JAXBException {
      javax.xml.bind.Marshaller m = contextDelegate.createMarshaller();
      m.setProperty("jaxb.formatted.output", formattedOutput);

      if (encoding != null) {
        m.setProperty("jaxb.encoding", encoding);
      }

      return m;
    }

    @Override
    public Validator createValidator() throws JAXBException {
      return contextDelegate.createValidator();
    }
   }
 }
The Context resolver itself is registered as a Singleton with Jersey in an Application class. For example:
public class NotesApplication extends Application {
  private Set<Object> singletons;
  
  public NotesApplication() {
   ....
    Schema schema = null;
   
    try {
      schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
        getClass().getResource("/notes.xsd"));
    }
    catch (SAXException e) {
      throw new RuntimeException("Error obtaining Schema", e);
    }

    JaxbContextResolver resolver = new JaxbContextResolver().withSchema(schema)
      .formatOutput(true);
  
    Set<Object> single = Sets.newHashSet();
    single.add(resolver);
    singletons = Collections.unmodifiableSet(single);
  }
  
  public Set&lt;Object> getSingletons() {
    return singletons;
  }
}
With the above, Unmarshaller's are provided that utilize the provided schema when unmarshalling received payload. When an error occurs during Unmarshalling, Jersey catches the javax.xml.bind.UnmarshallException, wraps it in a javax.ws.rs.WebApplicationException and throws the same. If one desires to customize the response sent back to the consumer the only option is to create an javax.ws.rs.ext.ExceptionMapper for javax.ws.rs.WebApplicationException and interrogate the cause to determine if it were a javax.xml.bind.UnmarshallException. I could not find a way to map the Unmarshall exception thrown by JAXB directly to an ExceptionMapper. If anyone has done so, I would love to hear about their solution.
class WebApplicationExceptionMapper imlements ExceptionMapper<WebApplicationException> {
   public Response toResponse(WebApplicationException e) {
      if (e.getCause() instanceof UnmarshallException) {
         return Response.status(404).entity("Tsk Tsk, XML is horrid and you provide the worst possible one?").build();
      }
      else {
         return Response.....
      }
  }
}
Sometimes, one does not need the full fledged validation benefits of a schema and can make do with a ValidationEventHandler. In such a case, one can provide the JaxbContextResolver with an instance of a javax.xml.bind.ValidationEventHandler. The handler could then be configured to throw a custom exception which can be mapped to a custom response using an ExceptionMapper as shown below. This approach is what appears on the Jersey mailing list entry:
JaxbContextResolver resolver = new JaxbContextResolver().withValidationEventHandler(new ValidationEventHandler() {
     public boolean handleEvent(ValidationEvent event) {
       if (event.getSeverity() == ValidationEvent.WARNING) {
          // Log and return
          return true;
      }
      throw new CustomRuntimeException(event);
   });

@Provider
class CustomRuntimeExceptionMapper implements ExceptionMapper<CustomRuntimeException> {
   public Response toResponse(CustomRuntimeException e) {
      return Response.status(400).entity("Oooops:" + e).build();
   }
}
Note that throwing a custom Exception and catching the same via an ExceptionMapper will work only if one does NOT provide a Schema. Once a schema is in place, the exception will be caught by JAXB and swallowed and one has to catch WebApplicationException and provide a custom response as described earlier.

An example provided herewith demonstrates a simple Notes service that manages the life cycle of a Note. It employs Guice to inject dependencies into Resource and Sub-Resource classes. The application also demonstrates the JaxbContextResolver and the registration of a schema for validating a received Note payload. Quite sweet actually. The details of integrating Guice and Jersey is not being detailed in this BLOG as there is already a pretty thorough BLOG by Iqbal Yusuf that describes the same.

Download the maven example by clicking HERE, extract the archive and simply execute "mvn install" at the root level to see it a client-server interaction. If any passer by is a JAXB Guru or has any tips on the Resolver, I would love to hear. Enjoy!

3 comments:

Anonymous said...

I had exactly this problem - to validate request against schema in JAX-RS. Your post helped me a lot! Thank you!

Sanjay Acharya said...

Glad it helped.

Anonymous said...

Hi Sanjay,
Good Post. I have a question I would like to intercept the request before going to the web service, get the request and do some validations on it. For e.g. check if the xml is malicious, malformed, large xml, attachment. I am using ibm wink jar and to intercept the request I am using org.apache.wink.server.handlers.RequestHandler.

Please help me getting the request xml from here.