Sie sind auf Seite 1von 16

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
|-Possible and recommended places we can inject Providers interface reference
using @Context annotation
|-POJOMapping Feature using Jackson
|-Wrapping up

ContextResolvers or Pluggable JAXBContexts using ContextResolvers:


Purpose of ContextResolvers:
If we written Custom Content Handlers there will be only one obj will be created for
Reader and Writer by the JAX-RS Runtime but there are multiple obj's has been
created for JAXBContext that means in Reader and Writer and for each and every req
due to which JVM memory will gets wasted which leads to performance issues bcz
JAXBContext obj is costly 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 in order to avoid this problem 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.
So we need to Custom ContextResolver using ContextResolver interface that has been
provide by the JAX-RS API and plug in our own JAXBContext with the ContextResolver
interface, But JAX-RS will don't know whether ContextResolver is there or not there
that's where we need register our ContextResolver class with JAX-RS Runtime by using
@Provider annotation. So that JAX-RS will creates ContextResolver obj which inturn
contains JAXBContext obj (JAXBContext inturn contains Binding classes meta data)
and places this ContextResolver obj with in the Providers interface reference variable.
If we wanted to perform Unmarshalling/Marshalling we need JAXBContext obj so in
order to get the JAXBContext obj we need to get the ContextResolver obj that is there
with in Runtime Providers interface.
In order to achieve this automation of injecting req body data to Resource method
class 1st we need locate a ContextResolver that can provide a custom JAXBContext
and we do this through the javax.ws.rs.ext.Providers interface.
So get the Providers interface reference annotate with @Context on top of the
Providers interface so that Runtime will injects Providers obj to Readers and Writers
classes and from this Providers reference we can get ContextResolver obj and from
this ContextResolver we can get JAXBContext obj so that we can use JAXBContext obj
for Unmarshallin/Marshalling. Eventhough we are getting the ContextResolver obj in
both Reader and Writer classes Runtime will gives same obj and as ContextReolver
obj is same if we get JAXBContext obj multiple times in Readers or Writers class we
will get same JAXBContext obj so that only obj will be used in entire life cycle of
application so that memory issues will be avoided and performance of the application
will be improved.
In order to get the ContextResolver obj from the Providers interface the Providers
interface has provided one method called getContextResolver(Class<T> contextType,
MediaType mediaType) and some methods are given below.
public interface Providers {
<T> ContextResolver<T> getContextResolver(Class<T> contextType,
MediaType mediaType);
<T> MessageBodyReader<T> getMessageBodyReader(Class<T> type,
Type genericType,
Annotation annotations[],
MediaType mediaType);
<T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type,
Type genericType,
Annotation annotations[],
MediaType mediaType);
<T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> type);
}

@Provider
class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext jContext;

public JAXBContextResolver() {
try {
jContext = JAXBContext.newInstance(Subscriber.class, Receipt.class);
} catch (JAXBException e) {
e.printStackTrace();
throw new WebApplicationException(e);
}
}

@Override
public JAXBContext getContext(Class<?> classType) {
if (classType.isAssignableFrom(Subscriber.class)
|| classType.isAssignableFrom(Receipt.class)) {
return jContext;
}
return null;
}
}
The ContextResolver will contains JAXBContext obj which inturn contains all Binding
classes as meta data. So the JAX-RS Runtime will creates ContextResolver which is
one per application so that all the meta data will be available at one sigle shot.
Writing MessageBodyReader with the help of ContextResolver:
@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;
ContextResolver<JAXBContext> contextResolver = null;

try {
contextResolver = providers.getContextResolver(JAXBContext.class,
MediaType.APPLICATION_XML_TYPE);
jContext = contextResolver.getContext(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 We use the providers.getContextResolver() method to find a ContextResolver. We
inject a reference to a Providers object using the @Context annotation that means in
order to get the reference that is there with the JAX-RS Injection we need to annotate
with @Context annotation.
The ContextResolver returned by providers.getContextResolver() is actually a proxy
that sits in front of a list of ContextResolvers that can provide JAXBContext instances.
When getContextResolver() is invoked, the proxy iterates on this list, recalling
getContextResolver() on each individual resolver in the list. If it returns a JAXBContext
instance, it returns that to the original caller; otherwise, it tries the next resolver in
this list.
Here we are saying that ContextResolver is proxy bcz ContextResolver is an interface
and we can write JAXBContextResolver and JSONContextResolver using same
ContextResolver interface that means when we call providers.getContextResolver()
1st it needs to identify whether it is a JXBContextResolver or JSONContextResolver
from the list of ContextResolvers.
If we observe in the Reader class we call providers.getContextResolver() method
which will gives ContextReolver obj, that means we can use the Providers interface to
find a ContextResolver that can give us a custom JAXBContext by calling
resolver.getContext(), passing in the type of the object we want a JAXBContext for.
Writing MessageBodyWriter with the help of ContextResolver:
@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 0;
}// 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 {
ContextResolver<JAXBContext> contextResolver=
providers.getContextResolver(JAXBContext.class,
MediaType.APPLICATION_XML_TYPE);
jContext = contextResolver.getContext(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.
If we observe in the Writer class we call providers.getContextResolver() method which
will gives ContextReolver obj, that means we can use the Providers interface to find a
ContextResolver that can give us a custom JAXBContext by calling
resolver.getContext(), passing in the type of the object we want a JAXBContext for.

Adding pretty printing (@Pretty):


By default, JAXB outputs XML without any whitespace or special formatting. The XML
output is all one line of text with no new lines or indentation. We may have human
clients looking at this data, so we want to give our JAX-RS resource methods the
option to pretty-print the output XML. We will provide this functionality using an
@Pretty annotation.
Example:
@Path("/idea")
public class IdeaProvider {
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/recharge")
@Pretty
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;
}
}
Since the writeTo() method of our MessageBodyWriter has access to the recharge()
method’s annotations, we can implement this easily. Let’s modify our JAXBMarshaller
class writeTo() as follows.
@Provider
@Produces(MediaType.APPLICATION_XML)
class JAXBMessageBodyWriter implements MessageBodyWriter<Object> {
@Context
private Providers providers;

@Override
public long getSize(...) {}

@Override
public boolean 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 {
ContextResolver<JAXBContext> contextResolver =
providers.getContextResolver(JAXBContext.class,
MediaType.APPLICATION_XML_TYPE);
jContext = contextResolver.getContext(classType);
marshaller = jContext.createMarshaller();

// To display response in an pretty format


boolean pretty = false;
for (Annotation ann : annotations) {
if (ann.annotationType().equals(Pretty.class)) {
pretty = true;
break;
}
}

if (pretty) {
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
}
// (or) We can direclty write as follows
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(object, os);
} catch (JAXBException e) {
e.printStackTrace();
throw new WebApplicationException(e);
} finally {
os.close();
}
}// writeTo()
}

Here, we iterate over the annotations parameter to see if any of them are the @Pretty
annotation. If
@Pretty has been set, we set the JAXB_FORMATTED_OUTPUT property on the
Marshaller so that it will format the XML with line breaks and indentation strings.

Difference between the javax.ws.rs.ext.Provider and javax.ws.rs.ext.


Providers:
The @Provider (javax.ws.rs.ext.Provider) annotation is used to register the Custom
classes like Custom ParamConverters and Custom Content Handlers with the JAX-RS
Runtime.
The Providers interface (javax.ws.rs.ext.Providers) is an built obj/reference that is
there with the JAX-RS Rutime. JAX-RS Runtime creates the obj for the registered
classes and places as meta data as with the same registered classes with in the
javax.ws.rs.ext.Providers reference variable so that if the developer wants any info of
registered classes it will allows to access these classes via javax.ws.rs.ext.Providers
reference and in order to get this pre-defined reference variable we need to annotate
javax.ws.rs.ext.Providers with @Context so that JAX-RS will injects
javax.ws.rs.ext.Providers reference in to our classes.
Life Cycle of JAX-RS:
The instances of MessageBodyReader, MessageBodyWriter, or ContextResolver must
be singleton hence we need to register these classes as singletons with the JAX-RS
Runtime as follows.
@ApplicationPath("/resource")
class IdeaApplication extends Application {
private Set<Object> singletons;
private Set<Class<?>> classes;

public IdeaApplication() {
// register singletons
singletons.add(new JAXBMessageBodyReader());
singletons.add(new JAXBMessageBodyWriter());
singletons.add(new JAXBContextResolver());

// register non-singletons
classes.add(IdeaProvider.class);
}

@Override
public Set<Class<?>> getClasses() {
return classes;
}

@Override
public Set<Object> getSingletons() {
return singletons;
}
}
If we didn't register also by default, only one instance of each MessageBodyReader,
MessageBodyWriter, or ContextResolver is created per application by the Jax-RS
Runtime bcz these are contractual classes hence it will creates only one obj but if we
want to make any class that is not a contractual with JAX-RS then we need to make
it as singleton if we want by registering as singletons using Application class.
If JAX-RS is allocating instances of these components the classes of these components
must provide a public default constructor or public single param constructor for which
the JAX-RS runtime can provide all the parameter values. A public constructor may
only include parameters annotated with the @Context annotation.
Possible and recommended places we can inject Providers interface reference using
@Context annotation:
We can inject the Providers reference in class at 3-places in which we are going to
inject the Providers interface reference to get the inbuilt obj of JAX-RS.
1. Class level as Attribute:
If the class is singleton then we can inject Providers interface reference in the class
level as attribute and on top of the Providers attribute itself we can write @Context
so that JAX-RS will injects so that we can use in any method bcz class singleton.
2. Constructor level:
If the class is singleton then we can inject Providers interface reference in the
contractor as param and on that contractor param it self we will write @Context and
declare Providers attribute in the class level so that JAX-RS will injects so that we can
use in any method bcz class singleton.
3. At each and every method param level:
If the class is non-singleton then we should not declare the Providers reference in the
class level rather we need to inject in a method where ever we want to avoid the
multithreading concurrency issues.

@Provider
@Consumes(MediaType.APPLICATION_XML)
public class JAXBMessageBodyReader implements MessageBodyReader<Object> {
// This class is singletone hence we can inject Providers ref in the class level, if this
Class is not singleton then we should not inject in the class level rather we need
to inject in the method level or contractor level
@Context
private Providers providers;

// By default there will be public Default Constructor


.....
}

We can write alternately as follows and we may can take any MediaType like
@Consumes(MediaType.APPLICATION_XML) as well.

@Provider
@Consumes(MediaType.APPLICATION_XML)
public class JAXBMessageBodyReader implements MessageBodyReader<Object> {
private Providers providers;
// This class is singletone hence we can inject Providers ref in the class level, if this
Class is not singleton then we should not inject in the class level rather we need
to inject in the method level or contractor level
public JAXBMessageBodyReader(@Context Providers providers) {
this.providers = providers;
}
....
}
Whether or not the JAX-RS runtime is allocating the component instance, JAX-RS will
perform injection into properly annotated fields and setter methods. Again, you can
only inject JAX-RS objects that are found using the @Context annotation.
Access the application:
JERSEY:
http://localhost:8083/3.2JAXBCustomContentHandlerWithContextResolver
/resource/idea/recharge
Select method as POST
Content-Type=application/xml
Send the req with data as part of body
<subscriber><mobile>929922</mobile><plan>Vennala</plan><amount>100.9</
amount></subscriber>
Response:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<receipt>
<receiptNo>7cb3266c-32d7-4f56-91c2-f27830c71787</receiptNo>
<mobile>929922</mobile>
<balance>100.9</balance>
<status>success</status>
</receipt>
Send multiple req's and observe the output on the console:
For Req-1
JAXBContext obj hashCode in ContextResolver: 1113914983
isReadable(..)
readFrom(..)
JAXBContext obj hashCode in Reader: 1113914983
isWritable(..)
isWritable(..)
writeTo(..)
JAXBContext obj hashCode in Writer: 1113914983

For Req-2
isReadable(..)
readFrom(..)
JAXBContext obj hashCode in Reader: 1113914983
isWritable(..)
isWritable(..)
writeTo(..)
JAXBContext obj hashCode in Writer: 1113914983
POJOMapping Feature using Jackson:
JERSEY:
If we don't want to write Custom Content handlers and ContextResolvers in Jersy then
add Jackson jar which is 3rd party library and register these Reader and writer classes
with JAX-RS Runtime in JERSEY it will work.
Jackson is used to convert xml to obj and vice versa.
RESTEasy:
We no need to write any Custom Content Handlers or COntext Resolvers in case of
RESTEasy bcz it has built in Custom Content Handlers and ContextResolvers so we
don't need to write any Readers or Writers and if we wanted to write also there is no
problem.
Access the application:
http://localhost:8082/4PredefinedCustomContentHandlerRESTEasy
/resource/idea/recharge
Check with multiple combinations:
We annotated the Resource as follows
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })

http://localhost:8082/4PredefinedCustomContentHandlerRESTEasy
/resource/idea/recharge
Content-Type=application/xml
<subscriber><mobile>929922</mobile><plan>Vennala</plan><amount>100.9</
amount></subscriber>
Response:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<receipt>
<receiptNo>b364b383-0f6d-4c24-913b-8689e5bbb0b0</receiptNo>
<mobile>929922</mobile>
<balance>100.9</balance>
<status>success</status>
</receipt
http://localhost:8082/4PredefinedCustomContentHandlerRESTEasy
/resource/idea/recharge
Content-Type=application/json
Note:
Syntax of JSON:
We taken mobile as int, plan as String, amount as float,
Element/Attribute names put in "mobile"
Data if it string then put in "Vennala"
If float data then send as 100.9 but not like 100.9f
If int data then send as 929922
Send the req as follows
{"mobile":929922,"plan":"Vennala","amount":100.9}
Response:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<receipt>
<receiptNo>c624724f-bd10-49fe-82ca-4ac6eff2acc3</receiptNo>
<mobile>929922</mobile>
<balance>100.9</balance>
<status>success</status>
</receipt

Bydefault it will sends response as who is in 1st place that as response type for
example if we write @Produces({ MediaType.APPLICATION_XML,
MediaType.APPLICATION_JSON }) then it will sends response type as XML to the client
bcz XML type is at the 1st place.

If we write @Produces({MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML}) then response type as JSON will be sent to the client
bcz JSON type is at the 1st place.

But we can send any type as input either XML or JSON which is called as
Representation Orientation hence RESTful can take any data formats and hence more
interoperable bcz there is no restrictions of protocol and versions of data formats.
Wrapping Up:
In this chapter learned that JAX-RS can automatically convert Java objects to a specific
data type format and write it out as an HTTP response. It can also automatically read
in HTTP request bodies and create specific Java objects that represent the request.
JAX-RS has a number of built-in handlers, but we can also write your own custom
marshallers and unmarshallers.

How did you written the Binding classes as part of your RESTful services to work with
Custom Content handlers?
Representing the XML we need to write the XSD from that XSD we need to generate
the Binding classes. In order to generate the Binding classes we need to run xjc tool
that is there as part of the java by passing XSD as input.
How many places JAX-RS injection is possible?
JAX-RS injection is possible in class attribute level, Resource method level and
contractor level.

Das könnte Ihnen auch gefallen