Consider this very simple hello world class:
@Path("hello") public class SelectableHello { @GET @Produces({"application/json; level=detailed", "application/json; level=summary", "application/json; level=normal"}) public Message hello() { return new Message(); } }
The
Message
class is annotated with some special annotations from MOXy that allows you to specify different partial object graphs. So my very simple message class looks like the following with three different levels defined:import javax.xml.bind.annotation.XmlRootElement; import org.eclipse.persistence.oxm.annotations.XmlNamedAttributeNode; import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraph; import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraphs; @XmlNamedObjectGraphs({ @XmlNamedObjectGraph(name = "summary", attributeNodes = { @XmlNamedAttributeNode("summary") }), @XmlNamedObjectGraph(name = "normal", attributeNodes = { @XmlNamedAttributeNode("summary"), @XmlNamedAttributeNode("message") }), @XmlNamedObjectGraph(name = "detailed", attributeNodes = { @XmlNamedAttributeNode("summary"), @XmlNamedAttributeNode("message"), @XmlNamedAttributeNode("subtext") }) }) @XmlRootElement public class Message { private String summary, message, subtext; public Message() { summary = "Some simple summary"; message = "This is indeed the message"; subtext = "This is the deep and meaningful subtext"; } public void setSummary(String summary) { this.summary = summary; } public String getSummary() { return summary; } public void setMessage(String message) { this.message = message; } public String getMessage() { return message; } public void setSubtext(String subtext) { this.subtext = subtext; } public String getSubtext() { return subtext; } }
Then it is a relatively easy step to define a JAX-RS ContextResolver that returns a suitably configured MoxyJsonConfig depending on the mime type. The code in here is a little bit ropey as it assumes that the first item in the accept list is the correct one.
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import org.eclipse.persistence.jaxb.MarshallerProperties; import org.glassfish.jersey.moxy.json.MoxyJsonConfig; @Provider public class MoxyJsonConfigResolver implements ContextResolver<MoxyJsonConfig> { HttpHeaders httpHeaders; @Context public void setHeaders(HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } @Override public MoxyJsonConfig getContext(Class<?> c) { // Assume we are going to match the first accept // MediaType responseType = httpHeaders.getAcceptableMediaTypes().get(0); MoxyJsonConfig config = new MoxyJsonConfig(); String level = responseType.getParameters().get("level"); config.getMarshallerProperties().put(MarshallerProperties.OBJECT_GRAPH, level != null ? level : "detailed"); return config; } }
So once this little application is running, you can see the output for different mime types. In our case the default for "application/json" is "detailed" but I can see the argument in many cases for "normal" to be the default.
GET .../hello Accept application/json; level=detailed or application/json { "message" : "This is indeed the message", "subtext" : "This is the deep and meaningful subtext", "summary" : "Some simple summary" } GET .../hello Accept application/json; level=normal { "message" : "This is indeed the message", "summary" : "Some simple summary" } GET .../hello Accept application/json; level=summary { "summary" : "Some simple summary" }
This does work, but as you can see the annotations could be prettier, the next blog post will be an example using EntityFilteringFeature of Jersey. This feature builds on top of the MOXy functionality above while allowing a custom annotation scheme. This to my mind is a lot easier to work with.
No comments:
Post a Comment