Over a million developers have joined DZone.

Selecting Level of Detail Returned by Varying the Content Type, part II

DZone's Guide to

Selecting Level of Detail Returned by Varying the Content Type, part II

· Java Zone ·
Free Resource

Get the Edge with a Professional Java IDE. 30-day free trial.

In my previous entry, we looked at using the feature of MOXy to control the level of data output for a particular entity. This post looks at an abstraction provided by Jersey 2.x that allows you to define a custom set of annotations to have the same effect. 

As before we have an almost trivial resource that returns an object that Jersey will covert to JSON for us, note that for the moment there is nothing in this code to do the filtering - I am not going to pass in annotations to the Response object as in the Jersey examples:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

public class SelectableHello {

  @Produces({ "application/json; level=detailed", "application/json; level=summary", "application/json; level=normal" })
  public Message hello() {

    return new Message();


In my design I am going to define four annotations: NoViewSummaryViewNormalViewand DetailedView. All root objects have to implement the NoView annotation to prevent un-annotated fields from being exposed - you might not feel this is necessary in your design. All of these classes look the same so I am going to only show one. Note that the factory method creating a AnnotationLiteral has to be used in preference to a factory that would create a dynamic proxy to have the same effect. There is code in 2.5 that will ignore any annotation implemented by a java.lang.reflect.Proxy object, this includes any annotations you may have retrieved from a class. I am working on submitting a fix for this.

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.enterprise.util.AnnotationLiteral;

import org.glassfish.jersey.message.filtering.EntityFiltering;

@Target({ ElementType.TYPE, 
  ElementType.METHOD, ElementType.FIELD })
public @interface NoView {

   * Factory class for creating instances of the annotation.
  public static class Factory extends AnnotationLiteral<NoView> 
    implements NoView {

    private Factory() {

    public static NoView get() {
      return new Factory();


Now we can take a quick look at our Message bean, this is slightly more complicated than my previous example to showing filtering of subgraphs in a very simple form. As I said before the class is annotated with a NoView annotation at the root - this should mean that the privateData is never returned to the client as it is not specifically annotated.

import javax.xml.bind.annotation.XmlRootElement;

public class Message {

  private String privateData;
  private String summary;
  private String message;
  private String subtext;
  private SubMessage submessage;

  public Message() {
    summary = "Some simple summary";
    message = "This is indeed the message";
    subtext = "This is the deep and meaningful subtext";
    submessage = new SubMessage();
    privateData = "The fox is flying tonight";

  // Getters and setters not shown

public class SubMessage {

  private String message;

  public SubMessage() {
    message = "Some sub messages";

  // Getters and setters not shown

As noted before there is no code in the resource class to deal with filtering - I considered this to be a cross cutting concern so I have abstracted this into a WriterInterceptor. Note the exception thrown if a entity is used that doesn't have the NoView annotation on it.

import java.io.IOException;

import java.lang.annotation.Annotation;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.ws.rs.ServerErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

public class ViewWriteInterceptor implements WriterInterceptor {

  private HttpHeaders httpHeaders;

  public ViewWriteInterceptor(@Context HttpHeaders httpHeaders) {
    this.httpHeaders = httpHeaders;

  public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) 
    throws IOException,
           WebApplicationException {

    // I assume this case will never happen, just to be sure
    if (writerInterceptorContext.getEntity() == null) {

      Class<?> entityType = writerInterceptorContext.getEntity()
      String entityTypeString = entityType.getName();
      // Ignore any Jersey system classes, for example wadl
      if (entityType == String.class  || entityType.isArray() 
        || entityTypeString.startsWith("com.sun") 
        || entityTypeString.startsWith("org.glassfish")) {
      // Fail if the class doesn't have the default NoView annotation 
      // this prevents any unannotated fields from showing up
      else if (!entityType.isAnnotationPresent(NoView.class)) {
        throw new ServerErrorException("Entity type should be tagged with @NoView annotation " + entityType, Response.Status.INTERNAL_SERVER_ERROR);

    // Get hold of the return media type:

    MediaType mt = writerInterceptorContext.getMediaType();
    String level = mt.getParameters().get("level");

    // Get the annotations and modify as required

    Set<Annotation> current = new LinkedHashSet<>();

    switch (level != null ? level : "") {
      case "detailed":
      case "normal":
      case "summary":


      current.toArray(new Annotation[current.size()]));



Finally you have to enable the EntityFilterFeature manually, to do this you can simple register it in your Application class

import java.lang.annotation.Annotation;

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
import org.glassfish.jersey.server.ResourceConfig;

public class SelectableApplication extends ResourceConfig {
  public SelectableApplication() {


    // Set entity-filtering scope via configuration.
    new Annotation[] {
      NormalView.Factory.get(), DetailedView.Factory.get(), 
      NoView.Factory.get(), SummaryView.Factory.get()


Once you have this all up and running the application will respond as before:

GET .../hello Accept application/json; level=detailed or application/json
  "message" : "This is indeed the message",
  "submessage" : {
    "message" : "Some sub messages"
  "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 is feel is a better alternative to using the MOXy annotations directly - using custom annotations should have to much easier to port your application to over implementation even if you have to provide you own filter. Finally it is worth also exploring the Jersey extension to this that allows Role based filtering which I can see as being useful in a security aspect.

Get the Java IDE that understands code & makes developing enjoyable. Level up your code with IntelliJ IDEA. Download the free trial.


Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}