Rest Assured With Jaxrs
Rest Assured With Jaxrs
of Contents
Introduction 1.1
Warm up 1.2
JAX-RS Core Part I 1.3
JAX-RS Core Part II 1.4
JAX-RS Core Part III 1.5
JAX-RS Providers Part I 1.6
JAX-RS Providers Part II 1.7
JAX-RS Providers Part III 1.8
JAX-RS for Power Users Part I 1.9
JAX-RS for Power Users Part II 1.10
Asynchronous JAX-RS 1.11
JAX-RS 2.1 - the latest & greatest 1.12
1
Introduction
The contents of this book are divided into distinct sections which cover the bare basics, core
components and some of the advanced framework features as well. Here is an overview
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives
4.0 International License.
2
Warm up
Warm Up
This lesson is to get you started and give you an idea about JAX-RS in general. Feel free to
skip this if you're not new to the framework.
History
Here is a historical snapshot
Implementations
3
Warm up
Glassfish,
Jersey jersey.java.net 2.2.22
Weblogic
RESTEasy http://resteasy.jboss.org/ 3.0.12 Wildfly, JBoss
Apache
https://cxf.apache.org/ 3.1.6 Apache TomEE
CXF
REST stands for Representational State Transfer. Let's quickly zip through some of the
important good-to-know-terminologies associated with REST
Resources - They are the cornerstone of REST based services. Server and client
exchange representations of the resource. REST is not about making RPC based calls
by simply using XML/JSON payloads (of course no one cab prevent you from doing so -
but that cannot be called RESTful)
Addressable - The resource(s) should be reachable via a URI e.g.
https://api.github.com/search/repositories
HTTP based - One must be able to interact with resources (CRUD) using standard
HTTP (GET,PUT,POST, DELETE etc) e.g. Issue a HTTP GET to the URI
https://api.github.com/search/repositories?q=user:abhirockzz
Stateless - HTTP (which is the cornerstone of REST) is inherently stateless protocol
and each RESTful payload should contain the related state. There is no co-relation b/w
two subsequent requests even if they are the same. Also, a RESTful API should not
depend on previous state related data to be sent by the client
Interaction via Hyperlinks - Let hyperlinks drive the application. Client friendly practice
popularly known as HATEOAS (Hypermedia As The Engine Of Application State)
What's important to know is that the JAX-RS framework embraces these principles and
helps you create RESTful services and APIs
4
Warm up
5
JAX-RS Core Part I
Resources
From a JAX-RS standpoint, a Resource is nothing but a class. It decorated with metadata
(annotation) to further define RESTful behavior
@Path
The @javax.ws.rs.Path annotation defined by the JAX-RS specification is used to map the
URI of the incoming HTTP request to RESTful Resources (Java classes and methods). It
supports both simple strings as well as regular expressions as values
@Path("/books")
public class BooksResource {
@GET
public Response all(){
//send HTTP 200 as response
return Response.ok().build();
}
}
6
JAX-RS Core Part I
//Maps a HTTP GET /conferences/42 to the get() method with 42 as the input parameter
@Path("/conferences")
public class ConferencesResource {
@GET
@Path("{id}")
public Response get(@PathParam("id") String id){
Conference conf = getConf(id);
return Response.ok(conf).build();
}
}
We'll cover @PathParam (and other related annotations) in the next chapter
A specific set of JAX-RS annotations are used to map standard HTTP operations to
Java methods which contain the business logic to handle the request.
These cannot be applied to Java classes.
Only one such annotation is allowed on a particular method
@javax.ws.rs.POST POST
@javax.ws.rs.DELETE DELETE
@javax.ws.rs.HEAD HEAD
@javax.ws.rs.OPTIONS OPTIONS
7
JAX-RS Core Part I
@Path("/conferences")
public class ConferencesResource {
@POST
public Response create(Conference conf){
Conference conf = createNewConf(conf);
return Response.ok(conf.getSchedule()).build();
}
@DELETE
@Path("{id}")
public Response delete(@PathParam("id") String id){
Conference conf = removeConf(id);
return Response.ok().build();
}
The above example demonstrates how different HTTP verbs (on the same URI) result in
different methods being invoked on your JAX-RS resource class
8
JAX-RS Core Part I
@Path("user")
public class UserResource {
@GET
@Path("{id}")
public Response find(@PathParam("id") String id) {
return Response.ok().build();
}
@PUT
public Response create(/*User user*/) {
return Response.ok().build();
}
@DELETE
@Path("{id}")
public Response delete(@PathParam("id") String id) {
return Response.ok().build();
}
}
For the JAX-RS resource represented in the above example below, if you execute a HTTP
OPTIONS command against the root resource URI, you will get back the WADL as a
response
9
JAX-RS Core Part I
They enable
Use URI mapping to execute business logic to dispatch control to specific JAX-RS
resource
Once the control is transferred, the HTTP request method matching takes place and the
specific method in the resource implementation class is invoked
10
JAX-RS Core Part I
@Path("conferences")
public class ConferencesResourceLocator{
@Path("{id}")
public ConferenceResource pick(@PathParam("id") String confID){
if(confID.equals("devoxx")){
return DevoxxResourceFactory.get(confID);
}else if(confID.equals("JavaOne")){
return JavaOneResourceFactory.get(confID);
}
}
@Path("{location}/{year}/{talkID}")
@GET
public Response get(@PathParam("year") String year,
@PathParam("location") String location,
@PathParam("talkID") String talkID){
@Path("{year}/{talkID}")
@GET
//JavaOne location is always San Francisco :-)
public Response get(@PathParam("year") String year,
@PathParam("talkID") String talkID){
11
JAX-RS Core Part I
Runtime behaviour
invoked
The ConferencesResourceLocator will dispatch to the JavaOneResource sub resource
implementation in case the first URI path parameter begins with 'JavaOne' e.g.
/conferences/JavaOne/2014/42-java8rocks
This section briefly talks about which classes and annotations are required to configure a
JAX-RS powered REST service in a Java EE (Java EE 7 to be specific) container. These
API components provide the necessary information to the JAX-RS container to be able to
complete the application deployment/bootstrap process
Application
@ApplicationPath
Application
A JAX-RS application needs to subclass this (abstract) class. It defines the components of
the application and supplies additional metadata. It has a couple of method which can be
overridden to return the application resource class
Abstract
Description
Method
Set
Returns a set of root resource and provider classes
getClasses()
Set The returned set of instances of root resource and provider classes
getSingletons() are only instantiated once
@ApplicationPath
12
JAX-RS Core Part I
This annotation helps define the root of your JAX-RS service i.e. it defines the base URI of
the application. It can only be applied to classes which extend the Application class
@ApplicationPath("rest")
public class RESTConfig extends Application{
//empty . . . .
}
It's perfectly valid to have an empty implementation if custom configurations are not
required
13
JAX-RS Core Part II
We use the word injection simply because it's the JAX-RS container, which does the
heavy lifting of passing on the values of the individual components to our code (based
on the metadata/annotations discussed in this chapter) - inversion on control at its best
The JAX-RS API has standard annotations in order to easily inject the values of these
URI parameters into your business logic
These annotations are applicable on methods, instance variables and method
parameters
14
JAX-RS Core Part II
@Path("/conferences")
public class ConferencesResource {
@GET
@Path("{category}")
public Response get(@PathParam("category") String category,
@QueryParam("rating") int rating){
ConferenceSearchCriteria criteria = buildCriteria(category, rating);
Conferences result = search(criteria);
return Response.ok(result).build();
}
}
These too are applicable on methods, instance variables and method parameters
15
JAX-RS Core Part II
@Path("{user}/approvals")
public class UserApprovalsResource {
@HeaderParam("token")
private Token authT;
@CookieParam("temp")
private String temp;
@GET
public Response all(@PathParam("user") String user){
checkToken(user, authT);
Logger.info("Cookie 'temp' -> "+ temp);
Approvals result = searchApprovalsForUser(user);
return Response.ok(result).build();
}
}
The @BeanParam annotation was introduced in JAX-RS 2.0. Let's look at why it's so useful
@BeanParam in action
JAX-RS allows you to encapsulate the information injected via the above mentioned
annotations ( @PathParam , @QueryParam , @MatrixParam , @FormParam , @HeaderParam and
@CookieParam ) within simple POJOs.
Here is an example
//getters . . .
}
16
JAX-RS Core Part II
@Path("/conferences")
public class ConferencesResource {
//triggered by HTTP GET to /conferences?s=1&e=25
@GET
public Response all(@BeanParam Pagination pgSize){
Conferences result = searchAllWithPagination(pgSize);
//send back 25 results
return Response.ok(result).build();
}
}
Modus operandi
Annotate the fields of the model (POJO) class with the @Param annotations
Inject custom value/domain/model objects into fields or method parameters of JAX-RS
resource classes using @BeanParam
JAX-RS provider automatically constructs and injects an instance of your domain object
which you can now use within your methods
17
JAX-RS Core Part III
Injection part II
In the previous chapter, we saw how we used specific annotations to inject HTTP URI
parameters, headers, cookies etc. We'll take this one step further and see what else JAX-RS
has in store for us in terms of useful injectables
JAX-RS provides the @Context annotation is used as a general purpose injection to inject a
variety of resources in your RESTful services. Some of the most commonly injected
components are HTTP headers, HTTP URI related information. Here is a complete list (in no
specific order)
HTTP headers
Although HTTP headers can be injected using the @HeaderParam annotation, JAX-RS also
provides the facility of injecting an instance of the HttpHeaders interface (as an instance
variable or method parameter). This is useful when you want to iterate over all possible
headers rather than injecting a specific header value by name
@Path("testinject")
public class InjectURIDetails{
//localhost:8080/<root-context>/testinject/httpheaders
@GET
@Path("httpheaders")
public void test(@Context HttpHeaders headers){
log(headers.getRequestHeaders().toString());
log(headers.getHeaderString("Accept"));
log(headers.getCookies().get("TestCookie").getValue());
}
}
18
JAX-RS Core Part III
variable or method parameter). Use this instance to fetch additional details related to the
request URI and its parameters (query, path)
@Path("testinject")
public class InjectURIDetails{
//localhost:8080/<root-context>/testinject/uriinfo
@GET
@Path("uriinfo")
public void test(@Context UriInfo uriDetails){
log("ALL query parameters -- "+ uriDetails.getQueryParameters().toString());
log("'id' query parameter -- "+ uriDetails.getQueryParameters.get("id"));
log("Complete URI -- "+ uriDetails.getRequestUri());
}
}
Resource Context
It can be injected to help with creation and initialization, or just initialization, of instances
created by an application.
@Path("testinject")
public class InjectRctx{
@Context
ResourceContext rctx;
//localhost:8080/<root-context>/testinject/rctx
@GET
@Path("rctx")
public MyResource test(){
//sub resource locator logic
rctx.initResource(new MyResource());
}
}
Request
19
JAX-RS Core Part III
//Inject Request
@Path("testinject")
public class InjectRequestObj{
//localhost:8080/<root-context>/testinject/req
@GET
@Path("req")
public Response test(@Context Request req){
Date lastUpdated = …;
req.evaluatePreConditions(lastUpdated);
//continue further . . .
}
}
More on practical usage of the Request instance in the chapter JAX-RS for Power
Users: Part II
Configuration
Used to inject the configuration data associated with a Configurable component (e.g.
Resource , Client etc.)
@Path("testinject")
public class InjectConfigDetails{
@Context
Configuration config;
//localhost:8080/<root-context>/testinject/config
@GET
@Path("config")
public void test(){
log(config.isEnabled(MyFeature.class));
}
}
Application
JAX-RS allows injection of Application subclasses as well
20
JAX-RS Core Part III
@Path("testinject")
public class InjectApplicationImpl{
@Context
Application theApp;
//localhost:8080/<root-context>/testinject/app
@GET
@Path("app")
public void test(){
log(theApp.getClasses());
}
}
Providers
An instance of the Providers interface can be injected using @Context . One needs to be
aware of the fact that this is only valid within an existing provider. A Providers instance
enables the current Provider to search for other registered providers in the current JAX-RS
container
Please do not get confused between @Provider (the annotation) and Providers (the
interface). More on this in the chapter `JAX-RS Providers Part III
Security Context
Inject an instance of the javax.ws.rs.core.SecurityContext interface (as an instance
variable or method parameter) if you want to gain more insight into identity of the entity
invoking your RESTful service.
Client API
Prior to the addition of a full-fledged Client API, developers had to resort to third party
implementations or interact with the HTTPUrlConnection API in the JDK to interact with HTTP
oriented (REST) services. The Client API (part of javax.ws.rs.client package) is fairly
compact, lean and fluent. It's classes and interfaces have been discussed below, followed by
some code examples
21
JAX-RS Core Part III
A ClientBuilder allows you to initiate the invocation process by providing an entry point via
its overloaded newClient methods and the build method. An instance of Client helps
create WebTarget instance with the help of overloaded target methods. WebTarget is a
representation of the URI endpoint for HTTP request invocation. It helps configure various
attributes such as query, matrix and path parameters and exposes overloaded request
methods to obtain an instance of Invocation.Builder. Invocation.Builder is responsible for
further building the HTTP request and configuring attributes such as headers, cookies,
cache control along with content negotiation parameters like media types, language and
encoding. Finally, it helps obtain an instance of the Invocation object by using one of its
build methods. An instance of Invocation encapsulates a HTTP request and allows
synchronous and asynchronous request submission via the overloaded versions of the
invoke and submit method respectively.
22
JAX-RS Core Part III
//Basic example
Let's dissect the code snippet to gain a better understanding of what's going on
components such as filters, interceptors, entity providers (message readers and writers) etc.
This is made possible using the overloaded versions of the register method [more on this
later]
This is applicable to server side JAX-RS components (filters, interceptors etc) as well
Security
This section explores security aspects of the JAX-RS API
23
JAX-RS Core Part III
JAX-RS specification does not define dedicated security related features except for a few
API constructs (which act as high level abstractions). For server side JAX-RS users (Java
EE) it's critical to understand that the JAX-RS framework leverages the security capabilities
of the container itself. To be specific, since JAX-RS is built on top the Servlet API, it has
access to all the security features defined by the specification
If you're using JAX-RS, you do not need to reinvent the wheel for securing your
application
You're free to use both declarative and programmatic security (or combination of both)
It is flexible enough to accommodate usage of custom security frameworks if needed
Authentication & authorisation are familiar terms, so let's go over them briefly and then delve
into how they can be enforced
Another vital security measure, Transport Layer Security can be enforced using HTTPS
Declarative
Declarative security can be configured by using
web.xml
web.xml is the standard deployment descriptor used by the Servlet specification. It's
contained within a WAR (inside the WEB-INF folder). Amongst other parameters, it contains
elements which help configuring authentication as well as role based authorization.
24
JAX-RS Core Part III
<web-app>
<security-constraint>
<web-resource-collection>
<web-resource-name>New Book Creation</web-resource-name>
<url-pattern>/rest/books</url-pattern>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Book Details</web-resource-name>
<url-pattern>/rest/books</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>dev-ldap</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
</web-app>
25
JAX-RS Core Part III
@Path("books")
public class BooksResource{
Authentication: Enforced using element. Users will need to use their credentials and will
be authenticated against the realm dev-ldap (imaginary LDAP directory) specified using
Authorisation (role based): Any authenticated user (in any role) is allowed to fetch
(using GET) book details. This is specified by the element. However, the book creation
(using POST) service is restricted to users in admin role only, thanks to once again
Transport Layer (encryption): enforced by . Both GET and POST can be invoked over
HTTPS only
Annotation based
All the annotations in this section belong to the Common Annotations specification. The
container/environment where they execute (in this case its the Servlet container)
defines their expected behaviour and implements these annotations in a way that they
are honoured at runtime.
@DeclareRoles
As the name itself indicates, this Class level annotation is used to declare a list of roles
available within the application. Specifically, it comes into picture when programmatic (API
based) authorization check is initiated by the SecurityContext.isUserInRole(String role)
method
@RolesAllowed
26
JAX-RS Core Part III
This annotation can be used on classes, individual methods or both. It specifies one or more
roles which are permitted to invoke bean methods. In case the annotation is used on both
class and individual methods of the bean class, the method level annotation takes
precedence
@PermitAll
It can be used on both class and individual methods. If applied on a class, this annotation
allows all its methods to be executed without any restrictions unless a method is explicitly
annotated using @RolesAllowed
@DenyAll
This can be applied on a class or on specific methods. It instructs the container to forbid
execution of the particular method guarded by this annotation. Please note that the method
can still be used internally within the bean class
@RunAs
The use of this annotation helps impersonate a user identity (on purpose) i.e. it allows a
bean method to be invoked under the context of a specific role by. This annotation can only
be used on a class and is implicitly enforced on all the its methods
Programmatic
SecurityContext is a JAX-RS abstraction over HTTPServletRequest for security related
information only
the web container is not be aware of the authentication detail. Hence, the
SecurityContext instance will not contain the subject, role and other details
the JAX-RS request pipeline needs to be aware of the associated security context &
make use of it within its business logic
27
JAX-RS Core Part III
SecurityContext is an interface after all. All you need to do is just implement, inject (using
@Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
@Context
UriInfo uriInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Auth-Token");
//execute custom authentication
String subject = validateToken(authHeaderVal);
if (subject!=null) {
final SecurityContext securityContext = requestContext
.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return subject;
}
};
}
@Override
public boolean isUserInRole(String role) {
List<Role> roles = findUserRoles(subject);
return roles.contains(role);
}
@Override
public boolean isSecure() {
return uriInfo.getAbsolutePath().toString()
.startsWith("https");
}
@Override
public String getAuthenticationScheme() {
return "Token-Based-Auth-Scheme";
}
});
}
}
}
28
JAX-RS Core Part III
The jose4j library was used for JWT creation and validation
Anatomy of a JWT
It consists of three parts
29
JAX-RS Core Part III
//header
{
"alg": "HS256",
"typ": "JWT"
}
//payload/claims
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
//the formula
signature = signedUsingHS256WithSecret(encoded_part)
JWT = encoded_part + "." + sigature
30
JAX-RS Core Part III
JWT creation
31
JAX-RS Core Part III
eyJhbGciOiJSUzI1NiJ9
.eyJzdWIiOiJ1c2VyMSJ9
.HG9GCQPuC6w6pulbYE2uurCzpEwoWvz_8Ps5ZjgtfomyY4LWacDEzlHLnyMj9H7aqgcePC7_4l2wDXQV-S0BQ
RsIZfJeUUmWxlTlLzvKZr_2eEx00YZPPFZNoFCfwB-ajLHLLenROy4aSjPo_Vg9o7N-p0DZ1yZQoJhkvoVJgkh
X9FeAf65kIZkbuJC9dmVkzXSOpVf4GZeCpNDJJYSo6IAnL3UEoWek6V9BtWgV-a4xvydp7vxkdDXmzmalGLYuW
buVG7rWcbWwSfsg38iEG-mqptqA_Kzk1VmjwWNo_BfvLuzjzuosqi732-5SRzBP-2zqGghBqMYsGgkqkH2n7A
{
"alg": "RS256" //header
}
{
"sub": "user1" //claim payload
}
32
JAX-RS Core Part III
@Priority(Priorities.AUTHENTICATION)
public class JWTAuthFilter implements ContainerRequestFilter{
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Authorization");
JWT verification
//should be the same as the one used to build the JWT previously
RsaJsonWebKey rsaJsonWebKey = getCachedRSAKey();
It allows the request to go through in case of successful verification, otherwise, the filter
returns a HTTP 401 Unauthorized response to the client
A container response filter ensures that the JWT is added as a part of the response
header again. It only does so when the JWT verification was successful - this is made
possible using the contextual state/information sharing feature provided by JAX-RS
Request Filters
33
JAX-RS Core Part III
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContex
t responseContext) throws IOException {
System.out.println("response filter invoked...");
if (requestContext.getProperty("auth-failed") != null) {
Boolean failed = (Boolean) requestContext.getProperty("auth-failed");
if (failed) {
System.out.println("JWT auth failed. No need to return JWT token");
return;
}
}
}
}
Other considerations
Choice of claim attributes
In this example, we just used the standard sub (subject) attribute in the claim. You are free
to use others. I would highly recommend reading section 4 of the JWT RFC for deeper
insight
JWT expiration
One should also consider expiring the JWT token after a finite time. You would need to
34
JAX-RS Core Part III
Although the initial authentication was executed using HTTP Basic, the application does
not rely on a Session ID for authorising subsequent requests from the same user. This
has the following implications
As stated above, JWT is helping us with Stateless authentication (it is not very different from
the HTTP protocol itself)
Our JWT contains all the required data (claim) for the conversation (in this case
authentication)
We pass the token with each HTTP request (only to access resources which are
protected by the JWT to begin with)
The application does not need to repetitively authenticate the user (via the username-
password combo)
Now we can scale ! You can have multiple instances (horizontally scaled across
various nodes/clusters) of your JAX-RS service and yet you need not sync the state of
the token between various nodes. If a subsequent request goes to different node than
the previous request, the authentication will still happen (provided you pass the JWT
token)
35
JAX-RS Providers Part I
Providers
They are nothing but implementations of specific JAX-RS interfaces which provide flexibility
and extensibility. They either need to be annotated with the @Provider annotation for
automatic detection by the JAX-RS container or need to explicitly configured
Default support
Every JAX-RS implementation provides out-of-the-box support for existing data types
(i.e. a default Message Body Reader is provided) such as String, primitives,
InputStream , Reader , File , byte array (byte[]), JAXB annotated classes, JSON-P
objects. The same is applicable for Message Body Writers as well (see the next topic)
36
JAX-RS Providers Part I
@Provider
@Consumes(MediaType.APPLICATION_XML)
public class CustomerDataToCustomerJPAEntity
implements MessageBodyReader<CustomerJPA> {
@Override
public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType
mt) {
@Override
public LegacyPOJO readFrom(Class<LegacyPOJO> type, Type type1, Annotation[] antns,
MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException,
WebApplicationException {
try {
// unmarshall from XML/JSON to Java object
context = JAXBContext.newInstance(CustomerJAXB);
toJava = context.createUnmarshaller();
jaxbCust = (CustomDomainObj) toJavaObj.unmarshal(in);
37
JAX-RS Providers Part I
@Stateless
@Path("customers")
public class CustomersResource{
@PersistenceContext
EntityManager em;
@POST
@Consumes(MediaType.APPLICATION_XML)
//XML payload -> JAXB -> JPA
public Response create(CustomerJPA cust){
//create customer in DB
em.persist(cust);
//return the ID of the new customer
return Response.created(cust.getID()).build();
}
}
As per our requirement, we first transform the raw payload into an instance of our JAXB
annotated model class ( CustomerJAXB ) and then build an instance of our custom JPA
entity ( CustomerJPA )
Benefits
Separates the business logic from data transformation code - the conversion is
transparent to the application
One can have different implementations for conversion of different on-wire
representations to their Java types and qualify them at runtime by specifying the media
type in the @Produces annotation e.g. you can have separate reader implementations
for a GZIP and a serialised (binary) representation to convert them to the same Java
type
38
JAX-RS Providers Part I
The below example, is the exact opposite (mirror image) of what was demonstrated earlier.
This time, we are returning an instance of our custom class without including any
transformation logic in our JAX-RS resource classes - it's encapsulated within our
MessageBodyWriter implementation
39
JAX-RS Providers Part I
@Provider
@Produces(MediaType.APPLICATION_XML)
public class CustomerJPAEntityToCustomerData
implements MessageBodyWriter<CustomerJPA> {
@Override
public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaTyp
e mt) {
//return true unconditionally - not ideal. one can include checks
return true;
}
@Override
public long getSize(CustomerJPA t, Class<?> type, Type type1, Annotation[] antns,
MediaType mt) {
return -1;
}
@Override
public void writeTo(CustomerJPA t, Class<?> type, Type type1, Annotation[] antns,
MediaType mt, MultivaluedMap<String, Object> mm, OutputStream out)
throws IOException, WebApplicationException {
jaxbCust(t.getId());
jaxbCust(t.getName());
try {
// marshall from XML/JSON to Java object
context = JAXBContext.newInstance(CustomerJAXB.class);
toXML = context.createMarshaller();
40
JAX-RS Providers Part I
@Stateless
@Path("customers")
public class CustomersResource{
@PersistenceContext
EntityManager em;
@GET
@Produces(MediaType.APPLICATION_XML)
@Path("{id}")
//JPA -> JAXB -> XML payload
public Response get(@PathParam("id") String custID){
//search customer in DB
CustomerJPA found = em.find(CustomerJPA.class,custID);
return Response.ok(found).build();
}
}
JSON-P
The JSON Processing API (JSON-P) was introduced in Java EE 7. It provides a standard
API to work with JSON data and is quite similar to its XML counterpart - JAXP. JSON-B
(JSON Binding) API is in the works for Java EE 8.
41
JAX-RS Providers Part I
@GET
public JsonArray buildJsonArray(){
return Json.createArrayBuilder().add("jsonp").add("jaxrs").build();
}
@POST
public void acceptJsonObject(JsonObject payload){
System.out.println("the payload -- "+ payload.toString());
}
These are pretty simple examples, but I hope you get the idea....
42
JAX-RS Providers Part II
Filters: one of the foremost components of the JAX-RS request processing chain
Interceptors: work in tandem with (intercept) Message Body Readers and Writers
Filters
Filters provide AOP (Aspect Oriented Programming) like capabilities within JAX-RS
applications and allow developers to implement cross cutting application specific concerns
which ideally should not be sprinkled all over the business logic e.g. authentication,
authorization, request/response validation etc. The AOP-based programming model involves
interposing on methods of JAX-RS resource classes and dealing with (or mutating)
components of HTTP request/response - headers, request URIs, the invoked HTTP method
(GET, POST etc)
43
JAX-RS Providers Part II
Pre-matching filters
As the name indicates, Pre-matching filters are executed before the incoming HTTP request
is mapped/dispatched to a Java method. Use the javax.ws.rs.container.PreMatching
annotation on the filter implementation class
@Provider
@PreMatching
public class PreMatchingAuthFilter{
public void filter(ContainerRequestContext crc)
throws IOException{
if(crc.getHeaderString("Authorization") == null){
crc.abortWith(Response.status(403).build());
}else{ //check credentials.... }
}
}
Post-matching filters
A Post-matching filter is executed by the JAX-RS container only after the completion of
method dispatch/matching process. Unlike, pre-matching filters, these filters do not need an
explicit annotation i.e. filter classes without the @PreMatching annotation are assumed to be
post-matching by default
44
JAX-RS Providers Part II
@Provider
public class PostMatchingFilterExample{
public void filter(ContainerRequestContext crc)
throws IOException{
System.out.println("Referrer: "+
crc.getHeaderString("referrer"));
System.out.println("Base URI: "+
crc.getUriInfo().getBaseUri());
System.out.println("HTTP Request method: "+
crc.getMethod());
}
}
A JAX-RS application can have multiple filters (in a chain like structure) which are
executed as per user defined order (more on this later) or a default one (container
driven). However, it possible to break the chain of processing by throwing an exception
from the filter implementation logic or by calling the abortWith method. In either cases,
the other request filters in the chain are not invoked and the control is passed on to the
Response Filters (if any).
45
JAX-RS Providers Part II
@Provider
public class CustomerHeaderResponseFilter{
public void filter(ContainerRequestContext crc,
ContainerResponseContext resCtx)
throws IOException{
System.out.println("Request URI: "+
crc.getUriInfo().getAbsolutePath().toString());
//adding a custom header to the response
resCtx.getHeaders().add("X-Search-ID",
"qwer1234-tyuio5678-asdfg9876");
}
}
46
JAX-RS Providers Part II
The same capability is available in the Client side JAX-RS filters as well. The only
difference is that you would be interacting with an instance of the ClientRequestContext
{title="",lang=java}
47
JAX-RS Providers Part II
@Override
public void filter(ContainerRequestContext cReqCtx) throws IOException {
cReqCtx.setProperty("prop1", "value1");
}
}
@Override
public void filter(ContainerRequestContext cReqCtx) throws IOException {
String val1 = (String) cReqCtx.getProperty("prop1");
cReqCtx.setProperty("prop1", "value1");
cReqCtx.setProperty("prop2", "value2");
}
}
@Override
public void filter(ContainerRequestContext cReqCtx) throws IOException {
String val1 = (String) cReqCtx.getProperty("prop1");
String val2 = (String) cReqCtx.getProperty("prop2");
Collection<String> customProperties = cReqCtx.getPropertyNames();
}
}
48
JAX-RS Providers Part II
@Priority(Priorities.AUTHENTICATION)
public class ReqFilter_1 implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext cReqCtx) throws IOException {
//generated and used internally
cReqCtx.setProperty("random-token", "token-007");
}
}
@Override
public void filter(ContainerRequestContext cReqCtx, ContainerResponseContext cRespCt
x) throws IOException {
//get the property
String responseToken = (String) cReqCtx.getProperty("random-token");
if(responseToken!=null){
//set it to HTTP response
cRespCtx.getHeaders.put("random-token-header" , responseToken); header
}
}
}
Interceptors
Interceptors are similar to filters in the sense that they are also used to mutate HTTP
requests and responses, but the major difference lies in the fact that Interceptors are
primarily used to manipulate HTTP message payloads. They are divided into two categories
- javax.ws.rs.ext.ReaderInterceptor and javax.ws.rs.ext.WriterInterceptor for HTTP
requests and responses respectively.
The same set of interceptors are applicable on the client side as well (unlike filters)
49
JAX-RS Providers Part II
Reader Interceptor
A ReaderInterceptor is a contract (extension interface) provided by the JAX-RS API. On the
server side, a Reader Interceptor acts on HTTP payloads sent by the client while the client
side reader interceptors are supposed to act on (read/mutate) the request payload prior to it
being sent to the server
Writer Interceptor
On the server side, a WriterInterceptor act on HTTP payloads produced by the resource
methods while the client side writer interceptors are supposed to act on (read/mutate) the
payload sent by the server prior to it being dispatched to the caller
Interceptors are invoked in a chain like fashion (similar to filters). They are only
triggered when entity providers ( MessageBodyReader and MessageBodyWriter ) are
required to convert HTTP message to and from their Java object representations. Both
ReaderInterceptor and WriterInteceptor wrap around MessageBodyReader and
50
JAX-RS Providers Part II
Global Binding
Named Binding
Dynamic Binding
Global Binding
By default, JAX-RS filters and interceptors are bound to all the methods of resource classes
in an application. That is, both request (pre and post) and response filters will be invoked
whenever any resource method is invoked in response to a HTTP request by the client. This
convention can be overridden using named binding or dynamic binding.
Named Binding
Filters and interceptors scoping can be handled in a fine-grained manner (based on per
resource class/method)
51
JAX-RS Providers Part II
@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Audited { }
@Provider
@Audited
public class AuditFilter implements ContainerRequestFilter {
//filter implementation....
}
//Step 3: Apply the same annotation to the required resource class or method
@GET
@Path("{id}")
@Produces("application/json")
@Audited
public Response find(@PathParam("id") String custId){
//search and return customer info
}
If it is applied to a class, the filter/interceptor will be bound to all its resource methods
Dynamic Binding
JAX-RS provides the DynamicFeature interface to help bind filters and interceptors
dynamically at runtime. They can be used in tandem with the more static way of binding
made possible using @NamedBinding . The injected instance of the ResourceInfo interface
helps you choose the resource method in dynamic fashion by exposing various methods and
the FeatureContext interface allows us to register the filter or interceptor once the resource
method has been selected.
52
JAX-RS Providers Part II
@Provider
public class AuthFilterDynamic implements DynamicFeature {
@Override
public void configure(ResourceInfo resInfo, FeatureContext ctx) {
if (UserResource.class.equals(resInfo.getResourceClass()) &&
resInfo.getResourceMethod().getName().contains("PUT")) {
ctx.register(AuthenticationFilter.class);
}
}
}
53
JAX-RS Providers Part III
Exception Mappers: how to transform low application level exceptions to logical HTTP
reponses
Context Providers: provides context (of other classes) to resources and providers
Exception Mapper
The javax.ws.rs.core.Response object allows developers to wrap HTTP error state and
return it to caller. A javax.ws.rs.WebApplicationException (unchecked exception) can be
used as a bridge between the native business/domain specific exceptions and an equivalent
HTTP error response. A javax.ws.rs.ext.ExceptionMapper represents a contract (interface)
for a provider that maps Java exceptions to Response objects.
helps embrace “don’t repeat yourself” (DRY) principle. your exception detection
and transformation logic is not repeated across all of your service methods
allows flexible mappings between business logic exceptions and the desired HTTP
response
in addition to returning HTTP status codes, you can choose to return HTTP
payload in the message body as well
54
JAX-RS Providers Part III
The new set of exceptions are intuitively named and each of them maps to a specific HTTP
error scenario (by default) – this means that, throwing any of these exceptions from your
resource classes would result in a pre-defined (as per mapping) HTTP error response being
sent to the client e.g. the client would receive a HTTP 403 in case you throw a
NotAuthorizedException . The exceptions’ behavior can also be modified at runtime by using
55
JAX-RS Providers Part III
BadRequestException 400
ForbiddenException 403
InternalServerErrorException 500
NotAcceptableException 406
NotAllowedException 405
NotAuthorizedException 401
NotFoundException 404
NotSupportedException 415
ServiceUnavailableException 503
Context Provider
Context services are provided by the implementation of ContextResolver interface (which
has just one method).
Think of context as configuration metadata which can help with various aspects
(instance creation etc.) and
the Context Provider as a factory for these contexts
@Provider
@Produces("application/json")
public class MyCustomContextProvider implements ContextResolver<MyCustomContext> {
56
JAX-RS Providers Part III
@Context ctx
Providers providers;
@GET
@Produces("application/json")
public Response get(){
//fetch it
MyCustomContext customCtx = providers.
getContextResolver(MyCustomContext.class
,MediaType.APPLICATION_JSON);
//use it
MyCustomClass clazz = customCtx.create();
}
57
JAX-RS for Power Users Part I
So I thought of using this diagram to provide a brief overview of the different JAX-RS
components and how they orchestrate with each other. This chapter will make sense, now
that we understand Entity Providers ( Filters , Interceptors etc.)
58
JAX-RS for Power Users Part I
What's discussed here is the server side processing pipeline i.e. the sequence of
actions which are triggered after the client sends an HTTP request (GET, POST, PUT
etc). It all begins when the client (browser or custom REST client) sends an HTTP
request to a REST endpoint
The goal is to educate you in terms of what happens behind the scenes and more
importantly, in which order. The intricate details of components like filters, interceptors etc.
have not been repeated since they were covered in the previous chapter(s)
Request chain
Request Filters
JAX-RS Filters are the first in a series of components which handle the HTTP request
Method matching
After (successful) filter execution, the JAX-RS run time initiates the resource method
matching process.
This chapter has a sub-section dedicated to this topic in case you want to explore it in
details
The exact method to be invoked is based on the algorithm outlined by the specification
(although JAX-RS providers are not bound by it)
It's determined by a combination of below mentioned annotations
59
JAX-RS for Power Users Part I
@GET , @PUT , @POST , @DELETE etc - these are the annotations which should
match up to the actual HTTP operation (the mapping of the annotation to the HTTP
verb is rather obvious)
@Path - its value (relative to the context root) is used to map the request URI e.g.
/tweeters/all
@Consumes - its values should match the Content-Type header value sent in the
HTTP request
@Produces - its values should match the Accept header value sent in the HTTP
request
After the method matching is complete, the required HTTP components get injected into
JAX-RS Resource classes (if configured) by the the JAX-RS run time. All we need to do is
use the appropriate annotation
Request Interceptors
Interceptors come into play in case the HTTP payload is transformed by custom entity
providers (Message Body Readers)
Entity Providers help in conversion of HTTP message payload to its appropriate Java type
(for injection into the method parameters of JAX-RS resource classes) and vice versa
Response chain
Response Filter
60
JAX-RS for Power Users Part I
They are invoked only when a MessageBodyWriter (see next topic) is registered to handle
outgoing HTTP payload
Entity Providers
They deal with conversion of Java objects (within the application code) to HTTP response
payloads
61
JAX-RS for Power Users Part I
coming! Just in case the term request to resource method matching is new to you - it's
nothing but the process via which the JAX-RS provider dispatches a HTTP request to a
particular method of your one of your resource classes (decorated with @Path )
What are the factors taken into consideration during the request matching process ?
Narrow down the possible matching candidates to a set of resource classes. This is
done by matching the HTTP request URI with the value of the @Path annotation on the
resource classes
From the set of resource classes in previous step, find a set of methods which are
possible matching candidates (algorithm is applied to the filtered set of resource
classes)
The HTTP request verb is compared against the HTTP method specific annotations
( @GET , @POST etc), the request media type specified by the Content-Type header is
compared against the media type specified in the @Consumes annotation and the
response media type specified by the Accept header is compared against the media
type specified in the @Produces annotation.
Boil down to the exact method which can serve the HTTP request
The above mentioned process is known as Content Negotiation and this topic is
covered in details in another chapter
Further exploration
I would highly recommend looking at the Jersey server side logic for implementation classes
in the org.glassfish.jersey.server.internal.routing package to get a deeper understanding.
Some of the classes/implementation which you can look at are
MatchResultInitializerRouter
SubResourceLocatorRouter
MethodSelectingRouter
PathMatchingRouter
62
JAX-RS for Power Users Part II
Content Negotiation
Caching: how does the JAX-RS framework help leverage the HTTP caching
mechanism
Conditional access: criteria based GET and PUT
Must know
Criteria (characteristics): the client can negotiate on the following properties of the
HTTP response - encoding, media type, language
Uses standard HTTP headers: Accept (media type), Accept-Language (language),
Accept-Encoding (encoding)
Preferential selection for Media types: uses additional metadata (q) to specify
affinity for a particular media type from a list of multiple choices
63
JAX-RS for Power Users Part II
//limited negotiation
@Path("/books")
public class BooksResource {
@Context
HTTPHeaders headers;
@GET
@Produces("application/json", "application/xml")
public Response all(){
MediaType mType = headers.getAcceptableMediaTypes().get(0);
Locale lang = headers.getAcceptableLanguages().get(0);
Books books = getAll();
return Response.ok(books).type(mType).language(lang).build();
}
}
the JAX-RS resource offers JSON & XML media types (as per @Produces )
the HTTP request headers are injected and the getAcceptableMediaTypes extracts the
information from the Accept header
getAcceptableLanguages does the same for language
the extracted language and media type are set in the HTTP response as well
Drawbacks
the client provides multiple choices e.g. more than one media type
it specifies their weightage as well i.e. which one does it prefer more
64
JAX-RS for Power Users Part II
@Path("/books")
public class BooksResource {
@Context
Request req;
@GET
@Produces("application/json", "application/xml")
public Response all(){
List<Variant> variants = Arrays.asList(
new Variant(MediaType.APPLICATION_XML_TYPE,"en", "deflate"),
new Variant(MediaType.APPLICATION_JSON_TYPE,"en", "deflate")
);
return Response.ok(books)
.type(selectedMType).language(lang).build();
}
}
The above logic can handle complex preferential negotiation requests e.g. Accept:
application/xml;q=1.0, application/json;q=0.5. This actually means, I prefer XML but
JSON would work (in case you don't have XML data)
The calculation is done by selectVariant method in Request API - all you need to do
is give it probable list from which to choose from
At its very core, JAX-RS drives media type based content negotiation on the basis of
the Accept header sent by the client and the media type specified by @Produces
annotation in the JAX-RS methods
Caching in JAX-RS
Caching is not a new concept. It is the act of storing data temporarily in a location from
where it can be accessed faster (e.g. in-memory) as compared to it's original source (e.g. a
database)
65
JAX-RS for Power Users Part II
Before we dive in further, it's very important that we understand the following
From a JAX-RS perspective, caching does not imply a server side cache
It just provides hints to the client in terms of the durability/validity of the resource data
It does not define how the client will use this hint. It ensures that it sticks to the HTTP
semantics and assumes that the client (e.g. a browser, programmatic API based client
etc.) understands the HTTP protocol
JAX-RS has had support for the Cache-Control header was added in HTTP 1.1 since its
initial (1.0) version. The CacheControl class is an equivalent of the Cache-Control header in
the HTTP world. It provides the ability to configure the header (and its different attributes) via
simple setter methods.
//Create a CacheControl instance manually and send it along with the Response
@Path("/testcache")
public class RESTfulResource {
@GET
@Produces("text/plain")
public Response find(){
CacheControl cc = new CacheControl();
cc.setMaxAge(20);
return Response.ok(UUID.randomUUID().toString()).cacheControl(cc).build();
}
}
At runtime
The caller receives the Cache-Control response header and is free to use the
information therein in order to decide when to fetch a new version of the resource
If the max-age attribute has a value of 3600 seconds, it means that the client is free to
cache the resource representation for the next one minute (and does not need to call
upon the server)
Please note that the JAX-RS framework does not define how the client will actually
'cache' the response
CDI Producers
66
JAX-RS for Power Users Part II
We can use CDI to enforce caching semantics in a declarative manner. CDI Producers can
help inject instances of classes which are not technically beans (as per the strict definition)
or for classes over which you do not have control as far as decorating them with scopes and
qualifiers are concerned. The idea is to
parameters
Just inject the CacheControl instance in your REST resource class and use it in your
methods
@Retention(RUNTIME)
@Target({FIELD, PARAMETER})
public @interface CachControlConfig {
67
JAX-RS for Power Users Part II
@Produces
public CacheControl get(InjectionPoint ip) {
CacheControl cc = null;
if (ccConfig != null) {
cc = new CacheControl();
cc.setMaxAge(ccConfig.maxAge());
cc.setMustRevalidate(ccConfig.mustRevalidate());
cc.setNoCache(ccConfig.noCache());
cc.setNoStore(ccConfig.noStore());
cc.setNoTransform(ccConfig.noTransform());
cc.setPrivate(ccConfig.isPrivate());
cc.setProxyRevalidate(ccConfig.proxyRevalidate());
cc.setSMaxAge(ccConfig.sMaxAge());
}
return cc;
}
}
//Good to go!
@Path("/testcache")
public class RESTfulResource {
@Inject
@CachControlConfig(maxAge = 20)
CacheControl cc;
@GET
@Produces("text/plain")
public Response find() {
return Response.ok(UUID.randomUUID()
.toString()).cacheControl(cc).build();
}
}
68
JAX-RS for Power Users Part II
Additional thoughts
In this case, the scope of the produced CacheControl instance is @Dependent i.e. it
will live and die with the class which has injected it. In this case, the JAX-RS
resource itself is RequestScoped (by default) since the JAX-RS container creates a
new instance for each client request, hence a new instance of the injected
CacheControl instance will be created along with each HTTP request
You can also introduce CDI qualifiers to further narrow the scopes and account for
corner cases
You might think that the same can be achieved using a JAX-RS filter. That is
correct. But you would need to set the Cache-Control header manually (within a
mutable MultivaluedMap ) and the logic will not be flexible enough to account for
different Cache-Control configurations for different scenarios
Last-Modified
If-Modified-Since
If-Unmodified-Since
ETag
If-None-Match
It would be niceif you have a basic understanding of (at least some of) these headers.
These are best referred from the official HTTP specification document.
69
JAX-RS for Power Users Part II
Before we proceed...
Here is a quickie on the two important JAX-RS APIs which we will be discussing here
Cache-Control defines the expiration semantics (along with other fine grained details) for
revalidate it's cache i.e. invoke the GET operation for same resource (again)
make sure it does so in an efficient/scalable/economic manner i.e. not repeat the
same process of exchanging data (resource info) if there are no changes to the
information that has been requested
Common sense stuff right ? Let's look at how we can achieve this
70
JAX-RS for Power Users Part II
//Improving GET request performance in JAX-RS by using the Last-Modified and If-Modifi
ed-Since headers
@Path("books")
@Stateless
public class BooksResource_1{
@PersistenceContext
EntityManager em;
@Context
Request request;
@Path("{id}")
@GET
@Produces("application/json")
public Response getById(@PathParam("id") String id){
//get book info from backend DB
Book book = em.find(Book.class, id);
//get last modified date
Date lastModified = book.getLastModified();
if(evaluationResultBuilder == null){
//resource was modified, send latest info (and HTTP 200 status)
evaluationResultBuilder = Response.ok(book);
}else{
System.out.println("Resource not modified - HTTP 304 status");
}
CacheControl caching = ...; //decide caching semantics
//add metadata
evaluationResultBuilder.cacheControl(caching)
.header("Last-Modified",lastModified);
return evaluationResultBuilder.build();
}
}
Server sends the Cache-Control & Last-Modified headers as a response (for a GET
request)
In an attempt to refresh/revalidate it's cache, the client sends the value of the Last-
Modified header in the If-Modified-Since header when requesting for the resource in a
subsequent request
Request.evaluatePreconditions(Date) determines whether or not the value passed in
the If-Modified-Since header is the same as the date passed to the method (ideally the
modified date would need to extracted from somewhere and passed on this method)-
71
JAX-RS for Power Users Part II
the If-Modified-Since header is the same as the date passed to the method (ideally
the modified date would need to extracted from somewhere and passed on this method)
ETag in action
@Path("books")
@Stateless
public class BooksResource_2{
@PersistenceContext
EntityManager em;
@Context
Request request;
@Path("{id}")
@GET
@Produces("application/json")
public Response getById(@PathParam("id") String id){
Book book = em.find(Book.class, id); //get book info from backend DB
//calculate tag value based on your custom implementation
String uniqueHashForBook = uniqueHashForBook(book);
//instantiate the object
EntityTag etag = new EntityTag(uniqueHashForBook)
if(evaluationResultBuilder == null){
//resource was modified, send latest info (and HTTP 200 status)
evaluationResultBuilder = Response.ok(book);
}else{
System.out.println("Resource not modified - HTTP 304 status");
}
CacheControl caching = ...; //decide caching semantics
evaluationResultBuilder.cacheControl(caching)
.tag(etag); //add metadata
return evaluationResultBuilder.build();
}
}
In addition to the Last-Modified header, the server can also set the ETag header value
to a string which uniquely identifies the resource and changes when it changes e.g. a
hash/digest
72
JAX-RS for Power Users Part II
client sends the value of the ETag header in the If-None-Match header when
requesting for the resource in a subsequent request
and then its over to the Request.evaluatePreconditions(EntityTag)
if the Request.evaluatePreconditions method returns null, this means that the pre-
conditions were met (the resource was modified since a specific time stamp and/or the
entity tag representing the resource does not match the specific ETag header) and the
latest version of the resource must be fetched and sent back to the client
otherwise, a HTTP 304 (Not Modified) response is automatically returned by the
method, and it can be returned as is
Choice of ETag: this needs to be done carefully and depends on the dynamics of
your application. What are the attributes of your resource whose changes are
critical for your clients ? Those are the ones which you should use within your ETag
implementation
Not a magic bullet: based on the precondition evaluation, you can help prevent
unnecessary exchange of data b/w client and your REST service layer, but not
between your JAX-RS service and the backend repository (e.g. a database). It's
important to understand this
73
JAX-RS for Power Users Part II
//Improving PUTs
@Path("books")
@Stateless
public class BooksResource_3{
@PersistenceContext
EntityManager em;
@Context
Request request;
@Path("{id}")
@PUT
public Response update(@PathParam("id") String id, Book updatedBook){
Book book = em.find(Book.class, id); //get book info from backend DB
Date lastModified = book.getLastModified(); //get last modified date
//let JAX-RS do the math!
ResponseBuilder evaluationResultBuilder = request.evaluatePreconditions(lastModifi
ed);
if(evaluationResultBuilder == null){
em.merge(updatedBook); //no changes to book data. safe to update book info
//(ideally) nothing needs to sent back to the client in case of successful
evaluationResultBuilder = Response.noContent(); update
}else{
System.out.println("Resource was modified after specified time stamp - HTTP 412
status");
}
return evaluationResultBuilder.build();
}
Server sends the Cache-Control & Last-Modified headers as a response (for a GET
request)
In an attempt to send an updated value of the resource, the client sends the value of the
Last-Modified header in the If-Unmodified-Since header
Request.evaluatePreconditions(Date) method determines whether or not the value
passed in the If-Unmodified-Since header is the same as the date passed to the
method (in your implementation)
74
JAX-RS for Power Users Part II
(HTTP 412) i.e. the resource was in fact modified after the time stamp sent in the If-
Unmodified-Since header, which of course means that the caller has a (potentially) stale
75
Asynchronous JAX-RS
Asynchronous JAX-RS
This chapter covers asynchronous programming support in JAX-RS and some of its
potential gotchas
From a client perspective, it prevents blocking the request thread since no time is spent
waiting for a response from the server.
Similarly, asynchronous processing on the server side involves suspension of the
original request thread and initiation of request processing on a different thread, thereby
freeing up the original server side thread to accept other incoming requests.
76
Asynchronous JAX-RS
@Path("async")
@Stateless
public class AsyncResource {
@Resource
ManagedExecutorService mes;
@GET
public void async(@Suspended AsyncResponse ar) {
mes.execute(new Runnable() {
@Override
public void run() {
try {
String processingThread = Thread.currentThread().getName();
System.out.println("Processing thread: " + processingThread);
Thread.sleep(5000);
String respBody = "Process initated in "
+ initialThread + " and finished in " + processingThread;
ar.resume(Response.ok(respBody).build());
}
catch (InterruptedException ex) {
//ignored. . . don't try this in production!
}
}
});
Since we have clearly expressed our asynchronous requirements, the container will ensure
that
77
Asynchronous JAX-RS
Although the calling (request) thread is released, the underlying I/O thread still blocks until
the processing in background thread continues.In the above example, the caller would have
to wait 5 seconds since that's the delay we have purposefully introduced within the code. In
simple words, the client (e.g. browser executing a HTTP GET) keeps waiting until the
business logic execution is finished by calling the resume method of the injected
AsyncResponse object
@Path("async")
@Stateless
public class AsyncResource {
@Resource
ManagedExecutorService mes;
@GET
public void async(@Suspended AsyncResponse ar) {
ar.setTimeout(3, TimeUnit.SECONDS); //setting the time out to 3 seconds
String initialThread = Thread.currentThread().getName();
......
}
}
78
Asynchronous JAX-RS
@GET
public void async(@Suspended AsyncResponse ar) {
ar.setTimeout(3, TimeUnit.SECONDS);
ar.setTimeoutHandler(new TimeoutHandler() {
@Override
public void handleTimeout(AsyncResponse asyncResponse) {
//sending HTTP 202 (Accepted)
asyncResponse.resume(Response
.accepted(UUID.randomUUID().toString())
.build());
}
});
79
Asynchronous JAX-RS
Future<Response> future2 =
invocation.submit(
new InvocationCallback<Customer>(){
public void completed(Customer cust){
System.out.println(
"Customer ID:" + cust.getID());
}
public void failed(Throwable t){
System.out.println(
"Unable to fetch Cust details: " +
t.getMessage());
}
});
Do not remain under the impression that your client thread will return immediately just
because the server API is async
80
Asynchronous JAX-RS
The call to async returns an instance of Future object - and you might already know, the get)
method (of Future ) blocks. So use it with care and at the correct point in your application
logic
81
JAX-RS 2.1 - the latest & greatest
JAX-RS 2.1
Version 2.1 (JSR 370) is the current release for the JAX-RS specification (at the time of
writing). It is a part of the Java EE 8 Platform as well. This chapter will give you an overview
(along with code examples) of all the new features in this release.
respectively
Use OutboundSseEvent.Builder to create instance of an OutboundSseEvent
SseEventSink can be used to send individual OutboundSseEvent s and a
abstraction
A SseEventSource.Builder is used to create a SseEventSource which is a client side
handle to process incoming SSE events ( InboundSseEvent s)
Last but not the least, we have Sse - the API construct which provides factory methods
to create SseBroadcaster and OutboundSseEvent instances
82
JAX-RS 2.1 - the latest & greatest
Server API
On the server side, you can generate SSE events which clients (browser based or
programmatic) can consume. Here is the high level flow to generate an event
@GET //1
@Produces(MediaType.SERVER_SENT_EVENTS)
public void emit(@Context SseEventSink eventSink, @Context Sse util) { //2
OutboundSseEvent sseEvent = util.newEvent("abhirockzz", new Date().toString()); //
3
eventSink.send(sseEvent); //4
eventSink.close(); //5
}
There is more!
Sending a single event (or maybe a few) and saying goodbye to the client is fine, but its not
what SSE is used for in general. It's used to transmit (real time) information like stock prices,
game scores etc. The client is not required to ping/ask/inquire/poll the server repeatedly -
instead, the server sends data (whenever available) on the (SSE) channel which is already
open. This sounds like broadcast, doesn't it ? Its not a surprise that the JAX-RS API models
it using SseBroadcaster
83
JAX-RS 2.1 - the latest & greatest
//registration process
...
private SseBroadcaster channel; //get handle to SseBroadcaster
@Path("subscribe")
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
public void subscribe(@Context SseEventSink eventSink, @Context Sse util){
eventSink.send(util.newEvent("Subscription accepted. ID - "+ UUID.randomUUID().toS
tring()));
channel.register(eventSink);
}
Information sent using the JAX-RS 2.1 SSE support does not only have to be of type
java.lang.String – it supports Java primitives ( Integer , Long etc.), JSON-B & JAX-B
annotated types as well as custom objects whose encoding process (Java object to on-wire
format) is defined using a MessageBodyWriter implementation
84
JAX-RS 2.1 - the latest & greatest
@GET
@Produces("text/event-stream")
public void fetch(@Context Sse sse, @Context SseEventSink eSink) {
System.out.println("events sent");
eSink.close();
System.out.println("sink closed");
}
In this example
Multiple OutboundSseEvent s are created – each differing in the data/media type (text,
json, xml etc.)
the default SSE media type is TEXT_PLAIN , hence does not need to be explicitly
specified when dealing with String data type
Employee class is a JSON-B annotated class
85
JAX-RS 2.1 - the latest & greatest
@Provider
@Produces(MediaType.TEXT_PLAIN)
public class StudentEncoder implements MessageBodyWriter<Student> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotatio
ns, MediaType mediaType) {
return type.isAssignableFrom(Student.class);
}
@Override
public long getSize(Student t, Class<?> type, Type genericType, Annotation[] annot
ations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(Student t, Class<?> type, Type genericType, Annotation[] annot
ations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream
entityStream) throws IOException, WebApplicationException {
String info = t.getName()+ "," + t.getEmail();
entityStream.write(info.getBytes());
}
public Student() {
}
Accessing the REST endpoint (e.g. http://localhost:8080/ ) will produce an output similar
to the following i.e. you will get a SSE event with heterogeneous data types
86
JAX-RS 2.1 - the latest & greatest
event: stringEvent
data: Thu Aug 17 02:30:01 GMT 2017
event: primitiveTypeEvent
data: 1502937001704
event: jsonbType
data: {"name":"test","salary":42,"emp_email":"test@test"}
event: jaxbType
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><customer><email>testcut@
test</email><name>king</name></customer>
event: customObjectWithMessageBodyWriter
data: stud-007,stud@test
Client API
You can use the JAX-RS SSE Client API to programatically access other SSE endpoints.
The high level flow is as follows
87
JAX-RS 2.1 - the latest & greatest
....
WebTarget target = ClientBuilder.newClient().target("https://sse.now.sh"); //1
SseEventSource eventSource = SseEventSource.target(target).build(); //2
eventSource.register(new Consumer<InboundSseEvent>() {
@Override //3
public void accept(InboundSseEvent sseEvent) {
System.out.println("Events received in thread " + Thread.currentTh
read().getName());
System.out.println("SSE event recieved ----- " + sseEvent.readData
());
}
},
new Consumer<Throwable>() { //4
@Override
public void accept(Throwable t) {
t.printStackTrace();
}
}, new Runnable() { //5
@Override
public void run() {
System.out.println("process complete");
}
});
eventSource.open(); //6
Thread.sleep(10000);
eventSource.close(); //7
88
JAX-RS 2.1 - the latest & greatest
established Entity Provider based feature which supports a variety of Java types (e.g.
primitive types, Reader , File etc.) including JSON-P objects (e.g. JsonValue )
A detailed disussion on JSON-B is out of scope of this book/chapter, but here is a high level
overview. I would encourage you to dig into the JSON-B specification for more details
JSON-B quickie
It's a standard specification which defines a binding (serialization and deserialization) API
between Java objects and JSON documents (RFC 7159 compatible)
Default mapping
JSON-B spec defines default mapping of Java classes and instances to equivalent JSON
document components. It covers Java primitives ( String , Boolean , Long etc.) and other
types such as BigInteger , URL , Date etc.
For a simple POJO ( Employee ), the JSON-B API can be used as follows (in default mapping
mode)
89
JAX-RS 2.1 - the latest & greatest
@JsonbProperty("emp_email") //2
@Id
private String email;
private String name;
@JsonbTransient //3
private int salary;
public Employee() {
}
attributes i.e. Employee JSON form will have name followed by email
2. @JsonbProperty is used to modify the name of the JSON attribute i.e. its not the same
as the POJO field/variable name. In this case, the JSON representation for Employee
will have the emp_email attribute instead of email
3. @JsonbTransient tells the JSON-B runtime to ignore (not process) the specific
How will these annotations be used at runtime within a JAX-RS application ? Another
example to illustrate this
90
JAX-RS 2.1 - the latest & greatest
@Stateless
@Path("employees")
public class EmployeesResource {
@PersistenceContext
EntityManager em;
@GET
@Path("{email}")
public Response test(@PathParam("email") String email) {
2. Once found, the POJO representation is returned by the method. Thanks to the JSON-B
integration, a JSON representation of Employee is returned to the caller
Note: in a situation where an entity can be treated as both JSON-B and JSON-P, the entity
providers for JSON-B will take precedence over those for JSON-P unless the object is of
JsonValue and its sub-types
91
JAX-RS 2.1 - the latest & greatest
This is great, but its hard to chain API calls in a pipeline fashion e.g. do this, and when it
finishes, do that - all asynchronously. JAX-RS does provide the InvocationCallback to
handle this - but it's not a great fit for complex logic as it leads to a callback hell problem
return company;
92
JAX-RS 2.1 - the latest & greatest
Others
Here are some of the other smaller but important enhancements to the API
this approach has over the AsyncResponse based API is that it is richer and allows you to
create asynchronous pipelines
@Path("cabs")
public class CabBookingResource {
@Resource
ManagedExecutorService mes;
@GET
@Path("{id}")
public CompletionStage<String> getCab(@PathParam("id") final String name) {
System.out.println("HTTP request handled by thread " + Thread.currentThread().
getName());
mes.execute(new Runnable() {
@Override
public void run() {
try {
validateUserTask.complete(validateUser(name));
} catch (Exception ex) {
Logger.getLogger(CabBookingResource.class.getName()).log(Level.SEV
93
JAX-RS 2.1 - the latest & greatest
return notifyUserTask;
}
String searchDriver() {
System.out.println("searchDriverTask handled by thread " + Thread.currentThrea
d().getName());
try {
Thread.sleep(2500);
} catch (InterruptedException ex) {
Logger.getLogger(CabBookingResource.class.getName()).log(Level.SEVERE, nul
l, ex);
}
return "johndoe";
}
return "Your driver is " + info + " and the OTP is " + (new Random().nextInt(9
99) + 1000);
}
It starts with a HTTP GET to /booking/cabs/<user> which invokes the getCab method
the method returns a CompletionStage and returns immediately
the thread which served the request is now freed up
and then its about creating the asynchronous pipeline
we orchestrate the tasks for user validation and driver search using
thenComposeAsync – this gives a CompletableFuture i.e. the searchDriverTask
94
JAX-RS 2.1 - the latest & greatest
we then supply a Function which takes the driver (returned by the above step)
and invokes the notifyUser method – this is the CompletionStage which we
actually return i.e. notifyUserTask – this is obviously executed later on, all we did
was compose the sequence
once the process is completed (delays are introduced using Thread.sleep() ), the
response is sent back to the user – internally, our CompletableFuture finishes
requests will be executed in the thread pool defined by the executor service
The above is example for a standalone environment where a fixed pool of 10 threads will
take care of the submitted tasks ( Runnable , Callable ). If you are in a Java EE
environment ( Java EE 7 and above), the container managed executor service (Java EE
Concurrency Utilities) should be used
@Resource
ManagedScheduledExecutorService managedPool; //container in action
95
JAX-RS 2.1 - the latest & greatest
@Path("conferences")
public class ConferencesResourceLocator{
@Path("{id}")
public Class pick(@PathParam("id") String confID){
if(confID.equals("devoxx")){
return DevoxxResource.class;
}else if(confID.equals("JavaOne")){
return JavaOneResource.class;
}
}
In the above example, rather than returning instances of our resource classes, the explicit
class is returned - the JAX-RS runtime takes care of creating an instance as per existing
rules laid out by the specification
supported sub-type i.e. JAX-RS now has entity providers for them as well
Provider @Priority
You can now decorate your custom provider imeplemtations with @Priority to help JAX-RS
runtime choose the appropriate one at runtime - given you have multiple such providers.
Points to note
lower priority will be preferred e.g. @Priority(2) will be chosen over @Priority(5)
in a scenario where two or more providers have the same priority, then its upto the
implementation to define which gets chosen
96