Monday, August 03, 2009

Three Views of REST resources

whew. Just hacked most of the day away learning how to make a custom JAX-RS provider to generate an HTML view of a resource using an XSLT transformation on the XML.

@Provider
@Produces(MediaType.TEXT_HTML)
public class UsersProvider implements MessageBodyWriter {
@Override
public void writeTo(Users users, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap params, OutputStream os) throws IOException, WebApplicationException {
try {
JAXBContext jc = new JSONJAXBContext(JSONConfiguration.natural().build(), new Class[]{users.getClass()});//JAXBContext.newInstance(users.getClass());
DOMResult xml = new DOMResult();
jc.createMarshaller().marshal(users, xml);
StreamSource styleSheet = new StreamSource(getClass().getResourceAsStream("/stylesheet/Users.xsl"));
Transformer transformer = TransformerFactory.newInstance().newTransformer(styleSheet);
transformer.transform(new DOMSource(xml.getNode()), new StreamResult(os));
}catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}

@Override
public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return (Users.class.isAssignableFrom(type) && mediaType.isCompatible(MediaType.TEXT_HTML_TYPE));
}

@Override
public long getSize(Users arg0, Class arg1, Type arg2, Annotation[] arg3, MediaType arg4) {
return -1;
}

}


In the end it's actually quite easy, but what threw me off was that JAX-RS has built-in support for the serialization of List<t> where T is a JAXB annotated class. The problem is that my custom provider, which applies a stylesheet to the XSL necessarily bypasses the built-in support for collections of JAXB annotated classes. Therefore, the method in my resource can't return a List. Instead, I created the Users (note the plural "s") wrapper class, and JAXB annoted it. Note incredibly annoyingly named accessor method, "getUser". Unfortunately, this had to be named in the singular, due to a quirk of JAXB. Naming it "getUser" produces
<users>
<user>...</user>
<user>...</user>
<!-- etc -->
</users>


versus naming the accessor method in the plural produces (note redundant plural users):
<users>
<user&sgt;...</users>
<users>...</users>
<!-- etc -->
</users>

so here is the JAXB annotated "wrapper" class for use instead of returning a raw List from the Resource method:


@XmlRootElement(name = "users")
public class Users {

private List users;

/**
* @return the users
*/
@XmlElement
public List getUser() {
return users;
}

/**
* @param users the users to set
*/
public void setUsers(List users) {
this.users = users;
}

}


then the Resource method just looks like this (note that it returns the Users wrapper class):


@GET
@Produces({MediaType.TEXT_HTML})
@Path("/users/{user}")
public Users getUsersHTML(@PathParam("user") String username) throws Exception {
Users users = new Users();
if(username.equalsIgnoreCase("*")){
users.setUsers(net.nextdb.SysAdminSearchAccounts.getUsers("%"));
}else{
users.setUsers(net.nextdb.SysAdminSearchAccounts.getUsers(username));
}
return users;
}

1 comment:

  1. Anonymous4:19 PM

    Nice. I spent an how trying to figure out myself, and then another 1/2 hour trying to find someone else who knew. Your post demonstrates the ease of the implementation where jax-rs implementation isn't so easy to figure out =).

    Thanks.

    ReplyDelete