Sunday, 24 August 2014

Dynamic Content Mapping with Jersey

In this article you will learn how to do return the response dynamically based on header or client request, something called Dynamic Content Mapping,much like Spring's ContentNegotiatingViewResolver.
So the response could be in any format xml, json,text ,html, protobuf.Also you will learn to create
restfull serviceprovider with protocol buffer.
There is no default mediatype for protocolbuffer,so you have to create Custom MessageBodyWriter
and MessageBodyReader to Serialize and Deserialize protobuf format.

There are  many ways how you can achieve this feature,but i will show you two most commonly used approach in real world scenario.

1) We can use the javax.ws.rs.HeaderParam in the Service provider to the Content-Type requested by the client and send the response accordingly to that format.

@GET
@Path("/dynamic")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response service(@QueryParam("msg") String msg,@HeaderParam("Content-Type")String contentType) {
String output = "Welcome : " + msg;
 return Response.status(200).entity(output).header(HttpHeaders.CONTENT_TYPE,      
         "application/json".equals(contentType) ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML)      
          .build();
}

2) Another approach is write custom Resource Config for your application and map your application accordingly.You can also write your own Media Type and Map it to your custom resource configuration.In-order to write custom mdeia type you need to override the MessageBodyWriter and MessageBodyReader for your Produce and Consume annotation tag. Here's a simple example to demonstrate it:

Order.java- Model class

@XmlRootElement(name = "orders")
public class Order implements Serializable {

    private static final long serialVersionUID = -3631611117467960241L;

    @JsonProperty("id")
    private String orderId;

    @JsonProperty("invoice")
    private double invoice;

    @JsonProperty("number")

    private int itemNumber;

//getter setters .....


CustomResourceConfig.java

public class CustomResourceConfig extends PackagesResourceConfig {

 public CustomResourceConfig(String... packages) {    //this constructor needs to be here, do not delete it or else the com.sun.jersey.config.property.packages param can't be passed in.
       super(packages);
   }

   public CustomResourceConfig(Map<String, Object> props) { //this constructor needs to be here, do not delete it or else the com.sun.jersey.config.property.packages param can't be passed in.
       super(props);
   }

 @Override
 public Map<String, MediaType> getMediaTypeMappings() {
   Map<String, MediaType> map = new HashMap<String, MediaType>();
   map.put("xml", MediaType.APPLICATION_XML_TYPE);
   map.put("json", MediaType.APPLICATION_JSON_TYPE);
   map.put("html", MediaType.TEXT_HTML_TYPE);
   map.put("js", MediaType.valueOf("text/javascript"));
   map.put("js", MediaType.valueOf("application/x-protobuf"));
   return map;
 }

}

Now Map your custom Resource Config in application web.xml

<!-- Custom Resource Config for Dynamic Response Mapping-->
<init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.service.content.mapping.CustomResourceConfig</param-value>

        </init-param> 



HtmlWriterProvider.java

@Produces(MediaType.TEXT_HTML)
@Provider
public class HtmlWriterProvider implements MessageBodyWriter<Order> {
    public boolean isWriteable(Class<?> arg0, Type arg1, Annotation[] arg2,
            MediaType arg3) {
        return true;
    }
    public long getSize(Order t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }
    public void writeTo(Order order, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream) throws IOException,
            WebApplicationException {
        String result = "<html>\n" + "<body>\n" + "<table border=\"1\">\n"
                + "<tr>\n" + "<td>Id Order</td>\n" + "<td>"
                + order.getOrderId() + "</td>\n" + "</tr><tr>\n"
                + "<td>Number Of Item</td>\n" + "<td>" + order.getItemNumber()
                + "</td>\n" + "</tr><tr>\n" + "<td>Invoice</td>\n" + "<td>"
                + order.getInvoice() + "</td>\n" + "</tr>\n" + "</table>\n"
                + "</body>\n" + "</html>";
        entityStream.write(result.getBytes());
    }
}

Similarly you can have your other custom writer and reader.Refer to Protocol Buffer example using Jersey.

OrderService.java

@Path("/order-service")
public class OrderService {

    @GET
    @Path("/get-order")
    @Produces({
        MediaType.APPLICATION_XML,
        MediaType.APPLICATION_JSON,
        MediaType.TEXT_HTML,
        "text/javascript" })
    public Order getOrder() {

        Order order = new Order();
        order.setOrderId("cb123");
        order.setInvoice(120);
        order.setItemNumber(100);
        return order;
    }

}

Now after deploying the service,navigate to the following in the browser:

1) HTML http://localhost:8080/RestFileServer/service/order-service/get-order



2) Rest Client with Content-Type(application/xml)  http://localhost:8080/RestFileServer/service/order-service/get-order




This dynamic response make the service very scalable and  effective for different type of consumer expecting different response format.

No comments:

Post a Comment