Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 14

Custom Content Handlers or Custom Marshallers and Unmarshallers

|-Purpose of Custom Marshalling/handlers


|-MessageBodyReader (Custom Unmarshaller)
|-Purpose of writing @Consumes on Readers and Writer classes
|-MessageBodyWriter (Custom Marshaller)
|-Final req flow of Custom Handlers
|-ContextResolvers or Pluggable JAXBContexts using ContextResolvers
|-Purpose of ContextResolvers
|-Writing MessageBodyReader with the help of ContextResolver
|-Writing MessageBodyWriter with the help of ContextResolver
|-Adding pretty printing (@Pretty)
|-Difference between the javax.ws.rs.ext.Provider and javax.ws.rs.ext.Providers
|-Life Cycle of Custome Content Handlers
|-POJOMapping Feature using Jackson
|-Wrapping up
Custom Content Handlers or Custom Marshallers and Unmarshallers:
@Path("/idea")
public class IdeaProvider {
@POST
@Path("/recharge")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Receipt recharge(InputStream is) {
// read the data raw data from the is and tranform into xml
// read the xml and convert into obj
// use obj and send response using StreamingOutput
}
}
If we taking param as File then also we need to do repeated steps a follows
@Path("/idea")
public class IdeaProvider {
@POST
@Path("/recharge")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Receipt recharge(File file) {
// read the data raw binary data from the file and tranform into xml
// read the xml and convert into obj
// use obj and send response using StreamingOutput
}
}
If we wanted to change the input from the InputStream to File or char[] then our
Resource class is going to be effected bcz our Resource class is direclty coupled to
the format in which we are reading. So in order to work any formats our Resource
class method need to take the obj of data and need to return the obj of data so if
the format of the data is changes then our Resource class will not be modified and
which will works agnostic to the data format we are receiving.
If the Client is sending the xml then we need to take the InputStream or some other
Built-in-Marshellers then we need to convert this raw data into the xml then from
that xml we need to convert into the obj which is difficult for the programmers.
If the Client is sending the JSON then we need to take the InputStream or some
other Built-in-Marshellers then we need to convert this raw data into the JSON then
from that JSON we need to convert into the obj which is difficult for the
programmers due to which our Resource class method is tightly coupled the format
of data we are accepting.
That means the built-in JAX-RS handlers that can marshal and unmarshal message
content but not into the obj format. Unfortunately built-in JAX-RS handlers are
either too low level to be useful or may not match the format, that means we in the
obj format but it will not support by the Built-in-Marshallers.
The JAX-RS Injection will allows us read the data from the request URI, Headers,
Cookies using annotations but if we want to read in the form of obj then we can use
ParamConverters which helps us to read the data in the form obj by reading from
the URI, Req Headers, Cookies. But now we wanted to read the data from the
Request Body then JAX-RS Injection ParamConverters will not works that's bcz they
are meant for reading the data from the URI, Headers, Cookies and converts into
the obj but not for to read the data from the request Body.
That's where in order to convert req body in to obj format we need to go for Custom
Content Handlers.
Purpose of Custom Marshalling/handlers:
1. Object conversion:
The purpose of Custom Content Handlers is to read the data from the Request Body
and to convert into the java obj JAX-RS allows you to write our own handlers and
plug them into the JAX-RS runtime, so these JAX-RS Custom Handlers which will
reads the data from the Req Body and converts into the obj format and passes as
input to our Resource class Resource methods and we can write our own Custom
Handler which return our own obj format as well so that our Resource methods will
works agnostic to the data format that we are going to read bcz it always takes obj
data only.
2. Multiple MIME types:
Some clients wants to send XML, some clients wants to send the data in JSON to
Recharge the mobile then we need to take 2-methods with same duplicates business
logic rather we can use same method with multiple MIME types representing the
input & output/response on the same method itself so that will works for both XML,
JSON data formats which is called as Representaion Oriented. All these can be
achieved if we are going to the Custom Content Handlers.
That means our Resource class method is not dealing with raw data rather it is
dealing with obj of data so it can support of XML, JSON any format of data which is
agnostic to data format and the Resource class has decoupled from which raw
format we are using to read and return/write.

@Path("/idea")
public class IdeaProvider {
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/recharge")
public Receipt recharge(Subscriber subscriber) {
Receipt receipt = null;

receipt = new Receipt();


receipt.setReceiptNo(UUID.randomUUID().toString());
receipt.setMobile(subscriber.getMobile());
receipt.setBalance(subscriber.getAmount());
receipt.setStatus("success");

return receipt;
}
}
To know how to write your own handlers, we’re going to pretend/assume that there
is no built-in JAX-RS JAXB support and instead write one our-selves Custom
Handlers using JAX-RS APIs which will converts into an obj format.
So we need write our Custom handler which read the XML data from the req body
and converts into an obj format for this JAX-RS has provided MessageBodyReder
and if we are returing an obj response then we need to write Custom handler which
will converts obj to XML format for this JAX-RS has provided MessageBodyWriter,
But JAX-RS Runtime will not knows what are Reader and Writers that's where we
need to register CustomMessageBodyReaders and MessageBodyWriters with the
JAX-RS runtime with help of @Provider annotation so that it will takes care of
identifying the Readers and Writers by introspecting which are annotated with
@Provider at the start-up of the application and whenever the req comes it will
takes care of converting obj to xml and xml to obj and if we use any other formats
like JSON then it will takes care of converting JSON into java obj and java obj to
JSON by the help of Readers and Writers that we have register with JAX-RS
Runtime.
That means for every data format we need to write one Reader and one Writer, that
means if we wanted convert xml to obj then we need to write JAXB Custom Reader
and JAXB Custom Writer, Similarly if we wanted to convert JSON to java obj then we
need to JSON Custom Reader and JSON Custom Writer
In order to convert xml to obj we need to write Binding classes or generate the
Binding classes using "xjc tool" by passing XSD as input and the we need to write
logic to using JAXB with help of these Binding classes for unmarshall/marshall.
Writing Custom Handlers:
MessageBodyReader (Custom Unmarshaller):
Let’s write an unmarshaller (converts XML to obj) that knows how to convert HTTP
XML request bodies into a Java object. To do this, we need to use the
javax.ws.rs.ext.MessageBodyReader interface.

public interface MessageBodyReader<T> {


boolean isReadable(Class<?> type,
Type genericType,
Annotation annotations[],
MediaType mediaType);

T readFrom(Class<T> type,
Type genericType,
Annotation annotations[],
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException;
}

The MessageBodyReader interface has only two methods.


The isReadable() method is called by the JAX-RS runtime when it is trying to find a
MessageBodyReader to unmarshal the message body of an HTTP request.
The readFrom() method is responsible for creating a Java object from the HTTP
request body.

As we are not specifying in which raw format we reading the req data in method
param of the Resource class method, we need to specify this raw type info in the
Custom MessageBodyReader so that JAX-RS will takes this raw format and with the
help of Custom MessageBodyReader and Binding classes it will converts xml into obj
of data.
As we are registering the Readers, Writers with the help of @Provider annotation so
the JAX-RS Runtime will reads the Readers and Writers creates obj for Readers and
Writes for only once and uses it for every req.

@Provider
@Consumes(MediaType.APPLICATION_XML)
public class JAXBMessageBodyReader implements MessageBodyReader<Object> {
@Context
private Providers providers;

@Override
public boolean isReadable(Class<?> type,
Type genericType,
Annotation annotations[],
MediaType mediaType) {
if (classType.isAnnotationPresent(XmlRootElement.class)) {
return true;
}
return false;
}

@Override
public Object readFrom(Class<Object>,
Type genericType,
Annotation annotations[],
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException {

Object obj = null;


JAXBContext jContext = null;
Unmarshaller unmarshaller = null;

try {
contextResolver = providers.getContextResolver(JAXBContext.class,
MediaType.APPLICATION_XML_TYPE);
jContext = JAXBContext.newInstance(classType);

unmarshaller = jContext.createUnmarshaller();
obj = unmarshaller.unmarshal(is);
} catch (JAXBException e) {
e.printStackTrace();
throw new WebApplicationException(e);
}
return obj;
}
}
Our JAXBUnmarshaller or Custom Handler or JAXBCustom Handler or Custom
JAXBMessageBodyReader class is annotated with @Provider and @Consumes.
The @Consumes annotation tells the JAX-RS runtime which media types it can
handle. The matching rules for finding a MessageBodyReader are the same as the
rules for matching MessageBodyWriter.
The readFrom() method gives us access to the HTTP headers of the incoming
request as well as a java.io.InputStream that represents the request message body.
Here, we just create a JAXBContext based on the Java type we want to create and
use a javax.xml.bind.Unmarshaller to extract it from the stream.

Purpose of writing @Consumes on Readers and Writer classes:


We are writing @Consume annotation to avoid the ambiguity in identifying the a
Custom Reader or Writer class from the bunch of Custom reader and writer classes
bcz, the JAX-RS allows to define a Resource method which will take multiple
MediaTypes that means a method can support for XML and JSON for accepting the
input and XML, JSON as response that means multiple MediaTypes, So while
converting XML to java obj (or) JSON to java obj we need to write
MessageBodyReader for JAXB and one more Reader for JSON similarly writers well.
Among these Readers which Reader is for XML and which Reader for JSON the JAX-
RS will don't know that is the reason we need to annotate with
@Consumes( MediaType.APPLICATION_XML) for XML Reader class and
@Consumes(MediaType.APPLICATION_JSON) for JSON Reader class so that it
converts accordingly.
So now if the req body contains XML then it will calls XML type Reader and if req
Body contains JSON type format then it will calls JSON Reader class bcz we
annotated with @Consumes annotation in each and every Reader and Writer class.
MessageBodyWriter (Custom Marshaller):
To implement is JAXB-marshalling (converts obj to xml) support that means to
automatically convert Java objects into XML, we have to create a class that
implements the javax.ws.rs.ext.MessageBodyWriter interface.
public interface MessageBodyWriter<T> {
boolean isWriteable(Class<?> type,
Type genericType,
Annotation annotations[],
MediaType mediaType);
long getSize(T t,
Class<?> type,
Type genericType,
Annotation annotations[],
MediaType mediaType);
void writeTo(T t,
Class<?> type,
Type genericType,
Annotation annotations[],
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException;
}
The MessageBodyWriter interface has only three methods.
The isWriteable() method is called by the JAX-RS runtime to determine if the writer
supports marshalling the given type.
The getSize() method is called by the JAX-RS runtime to determine the Content-
Length of the output. Finally, the writeTo() method does all the heavy lifting and
writes the content out to the HTTP response buffer. Let’s implement this interface to
support JAXB
@Provider
@Produces(MediaType.APPLICATION_XML)
class JAXBMessageBodyWriter implements MessageBodyWriter<Object> {
@Context
private Providers providers;

@Override
public long getSize(Object object,
Class<?> classType,
Type rawType,
Annotation[] annotations,
MediaType mediaType) {
return -1;
}// getSize()

@Override
public boolean isWriteable(Class<?> classType,
Type rawType,
Annotation[] annotations,
MediaType mediaType) {

if (classType.isAnnotationPresent(XmlRootElement.class)) {
return true;
}
return false;
}// isWriteable()

@Override
public void writeTo(Object object,
Class<?> classType,
Type rawType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String,
Object> responseHeaders,
OutputStream os)
throws IOException, WebApplicationException {

JAXBContext jContext = null;


Marshaller marshaller = null;

try {
jContext = JAXBContext.newInstance(classType);
marshaller = jContext.createMarshaller();
marshaller.marshal(object, os);
} catch (JAXBException e) {
e.printStackTrace();
throw new WebApplicationException(e);
} finally {
os.close();
}
}// writeTo()
}
In the Writer class we annoated with the @javax.ws.rs.ext.Provider annotation, this
tells JAX-RS that this is a deployable JAX-RS component. We must also annotate it
with @Produces to tell JAX-RS which media types this MessageBodyWriter supports.
Here, we’re saying that our JAXBMarshaller class supports application/xml.
Purpose of isWriteable() method and its params:
The isWriteable() method is a callback method that tells the JAX-RS runtime whether
or not the class can handle writing out this type. JAX-RS follows this algorithm to
find an appropriate MessageBodyWriter to write out a Java object into the HTTP
response.
1. First, JAX-RS calculates a list of MessageBodyWriters by looking at each writer’s
@Produces annotation to see if it supports the media type that the JAX-RS resource
method wants to output.
2. This list is sorted, with the best match for the desired media type coming first. In
other words, if our JAX-RS resource method wants to output application/xml and we
have three MessageBodyWriters (one produces application/*, one supports anything
*/*, and the last supports application/xml), the one producing application/xml will
come first.
3. Once this list is calculated, the JAX-RS implementation iterates through the list in
order, calling the MessageBodyWriter.isWriteable() method. If the invocation on
isWritable() returns true, that MessageBodyWriter is used to output the data.
The isWriteable() method takes four parameters.
Purpose isWriteable() method params:
The first one is a java.lang.Class that is the type of the object that is being
marshalled. We determine the type by calling the getClass() method of the object.
In our example, we use this parameter to find out if our object’s class is annotated
with the @XmlRootElement annotation.
The second parameter is a java.lang.reflect.Type. This is generic type information
about the object being marshalled. We determine it by introspecting the return type
of the JAX-RS resource method. We don’t use this parameter in our
JAXBMarshaller.isWriteable() implementation. This parameter would be useful, for
example, if we wanted to know the type parameter of a java.util.List generic type.
The third parameter is an array of java.lang.annotation.Annotation objects. These
annotations are applied to the JAX-RS resource method we are marshalling the
response for. Some MessageBodyWriters may be triggered by JAX-RS resource
method annotations rather than class annotations. In our JAXBMarshaller class, we
do not use this parameter in our isWriteable() implementation.
The fourth parameter is the media type that our JAX-RS resource method wants to
produce. The MediaType obj purpose given below.

public class MediaType {


private String type;
private String subtype;
// setters & getters

public MediaType(String type, String subtype) {


}
public MediaType() {
}
public static final String APPLICATION_XML = "application/xml";
public static final MediaType APPLICATION_XML_TYPE = new
MediaType("application", "xml");
// Here APPLICATION_XML_TYPE is an obj of MediaType which contains "type" as
"application" and "subtype" represents "xml", that means APPLICATION_XML_TYPE
is obj of MediaType type

public static final String APPLICATION_JSON = "application/json";


public static final MediaType APPLICATION_JSON_TYPE = new
MediaType("application", "json");
// Here "type" represents "application" and "subtype" represents "json"
public static final String TEXT_PLAIN = "text/plain";
public static final MediaType TEXT_PLAIN_TYPE = new MediaType("text","plain");
// Here "type" represents "text" and "subtype" represents "plain"
}

MediaType mediaType=new MediaType();


MediaType mediaType=new MediaType(type, subtype);
This MediaType is used whenever we need to access the type and subtype
separately using getters.
Purpose of getSize() method and its params:
The getSize() method is responsible for determining the Content-Length of the
response. If we cannot easily determine the length, just return –1, so that
underlying HTTP layer (i.e., a servlet container) will handle populating the Content-
Length in this scenario or use the chunked transfer encoding.
The first parameter of getSize() is the actual object we are outputting. The rest of
the parameters serve the same purpose as the parameters for the isWriteable()
method.
Purpose of writeTo() method and its params:
The target, type, genericType, annotations, and mediaType parameters of the
writeTo() method are the same information passed into the getSize() and
isWriteable() methods.
The httpHeaders parameter is a javax.ws.rs.core.MultivaluedMap that represents the
HTTP response headers. we may modify this map and add, remove, or change the
value of a specific HTTP header as long as you do this before outputting the
response body. The outputStream parameter is a java.io.OutputStream and is used
to stream out the data.
Our implementation simply creates a JAXBContext using the type parameter. It then
creates a javax.xml.bind.Marshaller and converts the Java object to XML.
Final req flow of Custom Handlers:
JAX-RS Runtime will takes the req and identifies the Resource method with the help
of req URI and MediaType and then looks in to the req body and tries to pass this
raw info to the method param so it will checks in which raw format the user is
wanted to take like InputStream or char[] or byte[] or etc Built-in-Content handlers,
if any Built-in-Content Handler is not found then immediately it will not throws
Exception rather it will takes that method param class type and will checks is there
any Message Body readers or not if not there throws Exception, if there then goes to
that Reader class and identifies the type using @Consumes annotation that is there
in the Reader and calls isReadable(....) and then checks whether the Resource class
method param type is annotated with @XmlRootElement.class or not if this reader
class method return true then it will calls readFrom() of the Reader class and then
creates JAXBContext obj by loading the classType Binding class of that method
param has and with JAXBContext obj creates unmarshaller and converts into obj
format.
Similarly Writers flow will happen by calling methods of the Writer class and
checking process and creates one more JAXBContext obj by loading the Binding
class of the classType of that method return type and with this JAXBContext obj
creates marshaller and converts into xml and send back to the client by writing xml
output to the OutputStream.

Access the application:


http://localhost:8083/3.1JAXBCustomContentHandlerWithoutContextResolver
/resource/idea/recharge
Select method as POST
Content-Type=application/xml
Send the req with following xml data
<subscriber><mobile>929922</mobile><plan>Vennala</plan><amount>100.9</
amount></subscriber>
Response:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<receipt>
<receiptNo>65da3760-2c36-4aff-adb8-0986040f79fe</receiptNo>
<mobile>929922</mobile>
<balance>100.9</balance>
<status>success</status>
</receipt>
Output at the Console:
Send Req-1
isReadable(..)
readFrom(..)
jCOntext hashCode: 59659970
isWritable(..)
isWritable(..)
writeTo(..)
jContext hashCode: 165152303

Send Req-2
isReadable(..)
readFrom(..)
jCOntext hashCode: 390184556
isWritable(..)
isWritable(..)
writeTo(..)
jContext hashCode: 791996013

If we observe here JAXBContext obj has been created for every Reader and Writer of
each and every req which will kilss the JVM memory which leads to performance
issues.
So inorder to avoid this we need to go for Custom ContextResolvers that has been
provided by the JAX-RS API so that it will creates only one obj irrespective of
whether it is a Reader or Writer and irrespective of request so that JVM memory will
not be wasted so that performance issues will not raise.

You might also like