Search This Blog

Showing posts with label jersey jax-rs. Show all posts
Showing posts with label jersey jax-rs. Show all posts

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!

Saturday, November 22, 2008

Home town of the Boss, jax-rs, jersey, spring, maven

I have previously tried jax-rs implementations by Restlet and JBoss RESTEasy. You can find the following same at :


One implementation that I had been postponing was Sun's RI, i.e., jersey. Trying to save 'hopefully' the best for last ;-). The name of the implementation has a part of the Boss's town of birth after all! Born in the USA! I am not born in the USA, but love it as much as my own country and is my home away from home! Moving on...

As before, I tweaked the simple Order Web Service example to use jersey.
  • Support for a Client API to communicate with Rest Resources.
  • Very Easy Spring integration.
  • Sun's RI, i.e., from the source
  • Support for exceptions
  • Very good support for JSON Media Type
  • Maven
  • Good set of examples
  • Automatic WADL generator
  • IOC
  • Embedded deployment using Grizzly
  • Filters on client and server side
  • Utilities for working with URI
One of the things that has impressed me about jersey is their out of the box JSON support. Being able to support JSON format without having to create a new javax.ws.rs.ext.Provider is rather convenient. By default the JSON convention is JSONJAXBContext.JSON_NOTATION. One can quite easily change the same to use Jettison or Badgerfish convention.

I was easily able to enable JSON representation for my Product resource by defining the Product data transfer objects with JAXB annotations, adding a @Produces("application/json") in the ProuductsResource class and ensuring that I have the jersey-json jar in my build.



ProductDTO.java
@XmlType(name = "product")

@XmlRootElement(name = "product")
public class ProductDTO implements Serializable {

....
}

ProductListDTO.java
@XmlRootElement(name = "productList")

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "products", propOrder = {"productDTOs"})

public class ProductListDTO implements Iterable<ProductDTO> {
....
}

ProductsResource.java
@GET @Produces("application/json")
public ProductListDTO getProducts() {

...
}






<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>

<version>${jersey-version}</version>
</dependency>




There is an excellent tutorial, Configuring JSON for RESTful Web Services in Jersey 1.0 by Jakub Podlesak that you can read more about.

To support Spring integration, the web applications deployment descriptor has been modified to use the Jersey Spring Servlet. All the Spring managed beans defined by the @Component, @Service, @Resource annotations are automatically wired.



<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>

</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<servlet>
<servlet-name>JerseySpring</servlet-name>

<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>





On the Client Side, I was pleasantly surprised by the ease in which I could invoke REST calls. As mentioned in my RESTEasy blog, jax-rs has no requirements for a client side specifications. However, those implementations that do provide one will be the ones to gain more adoption IMO. The jersey implementation provides a DSL like implementation to talk to the services. Very "VERBY", if there is no word in the dictionary like "VERBY", I stake my claim on the same :-). I modified the client from the RESTEasy implementation of the Order Service to use Jersey Client support as follows :



public class OrderClientImpl implements OrderClient {

private final WebResource resource;

/**
* @param uri Server Uri
*/

public OrderClientImpl(String uri) {

ClientConfig cc = new DefaultClientConfig();
// Include the properties provider

Client delegate = Client.create(cc);
// Note that the Resource has been created here

resource = delegate.resource(uri).path("order");

}

public OrderDTO createOrder(OrderDTO orderDTO) throws IOException {

return resource.accept(MediaType.APPLICATION_XML).type(MediaType.APPLICATION_XML)

.post(OrderDTO.class, orderDTO);
}

public void deleteOrder(Long orderId) throws OrderException {

resource.path(orderId.toString()).type(MediaType.APPLICATION_XML).delete();
}

public OrderDTO getOrder(Long orderId) throws OrderNotFoundException, IOException {

try {
return resource.path(orderId.toString())

.type("application/xml").accept("application/xml").get(OrderDTO.class);

} catch (UniformInterfaceException e) {

if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {

throw new OrderNotFoundException(e.getResponse().getEntity(String.class));

}
throw new RuntimeException(e);
}
}

public void updateOrder(OrderDTO orderDTO, Long id) {

resource.path(id.toString()).type("application/xml").put(orderDTO);

}
}





As you can see from the above the use of the Jersey client API is rather straight forward and intuitive. One point to note is that Jersey provides a Exception framework for easily handling common exception cases like 404 etc. There are some classes that enable this support, com.sun.jersey.api.NotFoundException and com.sun.jersey.api.WebApplicationException that one can use. As I did not want to tie my data transfer object maven project to jersey in particular, I did not use jersey exceptions but instead stuck with my custom Exception Provider.

Running the Example:
The Example can be downloaded from HERE.

This example has been developed using maven 2.0.9 and jdk1.6.X. Unzip the project using your favorite zip tool, and from the root level execute a "mvn install". Doing so will execute a complete build and run some integration tests. One interesting thing to try is to start the jetty container from the webapp project using "mvn jetty:run" and then access the jersey generated WADL from http://localhost:9090/SpringTest-webapp/application.wadl

Now that you have the WADL, you should be able to use tools like SOAPUI or poster-extension (a Firefox plugin) to test your RESTful services as well.

It would be interesting to see how wadl2java and the maven plugin provided there in can be used to create a separate client project to talk to the web services.

The jersey web site has pretty good documentation about using jax-rs. It is not thorough but getting there. There are a good set of examples that one can download and try as well. It is my understanding that NetBeans has good tooling support for jersey as well.

So now that I have tried Restlet, different jax-rs implementations and jax-ws what would be my direction if I were to take a decision on what to use for my SOA project? Some food for my next blog :-)

Again, if the example fails to run, ping me...Enjoy!