JAX RS: Sub Resources

In this post, I am going to explain about the JAX RS Subresources with an example.

JAX RS Resource classes can partially process a request and provide an another “sub” resource object that can process the remainder of the request.  Those classes will be called as Subresources.

Subresources are used to represent the relationships between the resources. If a resource is part of another resource, then we can use Subresources. An example for this is Employee and Address resources. Here the Employee object contains the Address object.

If you want to design a readable API, then Subresources is a perfect choice.

If you want to design a flexible API, then you should use fewer Subresources.

The Subresources can’t be used separately and also if you have more level, then its very hard to remember the URL path.

Now let’s see it with an example, Assume that we have a JAX RS web service which returns the “Employee” and “Address” details. Here we can design the Employee as root resource and the Address as Subresource.

Here are the value classes for Employee and Address.

Employee.java



public class Employee {

    private String id;

    private String firstName;

    private String lastName;

    private Address address;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Employee{");
        sb.append("id='").append(id).append('\'');
        sb.append(", firstName='").append(firstName).append('\'');
        sb.append(", lastName='").append(lastName).append('\'');
        sb.append(", address=").append(address);
        sb.append('}');
        return sb.toString();
    }
}

Address.java



public class Address {

    private String streetAddress;

    private String city;

    private String state;

    private String zip;

    public String getStreetAddress() {
        return streetAddress;
    }

    public void setStreetAddress(String streetAddress) {
        this.streetAddress = streetAddress;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Address{");
        sb.append("streetAddress='").append(streetAddress).append('\'');
        sb.append(", city='").append(city).append('\'');
        sb.append(", state='").append(state).append('\'');
        sb.append(", zip='").append(zip).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

The below things should be kept in mind while creating the Subresources.

  1. Sub Resource class should not have the @Path annotation. In our example, AddressResource is the Sub Resource class and it does contain the @Path annotation.
  2. Subresource method should not have the HTTP method and it should return an object of the subresource class. In this example, getEmployeeAddress is the subresource method and it returns the AddressResource Object

Refer below our EmployeeResource class. I use a HashMap to store the Employee details as this is just an example. The getEmployeeData() method return an employee object and the createEmployee create an employee object.

The method getEmployeeAddress() returns the AddressResource. So when we access ‘/api/employee/{ID}/address’ the JAX-RS runtime will map the request to AddressResource class and call the appropriate method.

EmployeeResource.java


import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

@Path("employee")
public class EmployeeResource {

    private static Map empLocalCache = new HashMap();

    static{
        //For testing purpose only.
        createTestData();
    }

    public static void createTestData() {
        Employee employee = new Employee();
        employee.setId("123");
        employee.setFirstName("test");
        employee.setLastName("test");
        Address address = new Address();
        address.setStreetAddress("streetAddress");
        address.setCity("Columbus");
        address.setState("OH");
        address.setZip("43202");
        employee.setAddress(address);
        empLocalCache.put("123", employee);
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Employee getEmployeeData(@PathParam("id") String id) {
        return empLocalCache.get(id);
    }

    @Path("{id}/address")
    public AddressResource getEmployeeAddress(@PathParam("id") String id) {
        return new AddressResource(id);
    }

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Employee createEmployee(Employee employee) {
        employee.setId(UUID.randomUUID().toString());
        empLocalCache.put(employee.getId(), employee);
        return employee;
    }

    public class AddressResource {

        private String id;

        AddressResource(String id){
            this.id = id;
        }

        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Address getAddress() {
            Optional optional = Optional.ofNullable(empLocalCache.get(id));
            return optional.isPresent()? optional.get().getAddress(): null;
        }
    }
}

Testing
The first step is to create an employee details with Address data. So we have to call /api/employee(POST) method with the employee data in the body.


URL: /api/employee
Method: POST
Header: Content-Type:application/json
Body:
{
    "firstName": "test",
    "lastName": "test",
    "address": {
        "streetAddress": "streetAddress",
        "city": "Columbus",
        "state": "OH",
        "zip": "43202"
    }
}

Response:

{
    "id": "cfd59155-a4cf-4fdd-83ae-5198c506d56a",
    "firstName": "test",
    "lastName": "test",
    "address": {
        "streetAddress": "streetAddress",
        "city": "Columbus",
        "state": "OH",
        "zip": "43202"
    }
}

Next step is to get the address of this employee with sub resource method.


URL: /api/employee/cfd59155-a4cf-4fdd-83ae-5198c506d56a/address
Method: GET
Response:

{
    "streetAddress": "streetAddress",
    "city": "Columbus",
    "state": "OH",
    "zip": "43202"
}

Advertisements

JAX-RS: How to use BeanParam

BeanParam annotation is available in JAX-RS 2.0. It allows us to aggregate the parameters into java bean.

In this post, we are going to see how we can use with an example,

Below is my Resource class.

I have specified the annotation @BeanParam for the SearchParamBean class. Hence the query parameters those are passed to this endpoint will be mapped to the SearchParamBean model class.

TestResource.java


import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("test")
public class TestResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("search")
    public Response performSearchWithMultiParameters(@BeanParam SearchParamBean searchParamBean) {
        return Response.ok().entity(searchParamBean).type(MediaType.APPLICATION_JSON).build();
    }

}

Below is my BeanParam class.

I have given the necessary annotation for all the fields of this class. I have used QueryParam here. Hence the query parameters ‘query’, ‘sort’ and ‘filter’ are mapped to fields of SearchParamBean class.

We can also use PathParam, HeaderParam, CookieParam, MatrixParam and FormParam.

SearchParamBean.java


import javax.ws.rs.QueryParam;

public class SearchParamBean {

    @QueryParam("query")
    public String query;

    @QueryParam("sort")
    public String sort;

    @QueryParam("filter")
    public String filter;

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("SearchParamBean{");
        sb.append("query='").append(query).append('\'');
        sb.append(", sort='").append(sort).append('\'');
        sb.append(", filter='").append(filter).append('\'');
        sb.append('}');
        return sb.toString();
    }

}

Here is my request and response.

URL: /api/test/search?query=test&sort=test&filter=test
Response:


 {
query: "test",
sort: "test",
filter: "test"
}

Most of the time, we don’t need to use this annotation instead we can use Jackson to map the query parameters directly to a bean class.

For example, When the service accepts more than one query parameters and we want to do any validation and need to use those values further in service layer or DAO layer, then we can make use of this annotation.

JAX-RS – How to bind Filters to JAX-RS resources with Name Binding

In my earlier post, I have given an example to know how to bind the Filters to JAX-RS resources with Dynamic Binding.

Refer it @ JAX-RS Dynamic Binding

In this post, I am going to show how we can do that with Name Binding.

Follow the below steps to do that.

1. Create a custom annotation which uses @NameBinding annotation.


package com.resource;

import javax.ws.rs.NameBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@NameBinding
@Target({ ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AppKeyVerifier {}

2. Create a filter and implement the app key verification logic. Then apply the custom annotation to it.


package com.resource;

import com.model.AppError;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
@AppKeyVerifier
public class AppKeyVerifierFilter implements ContainerRequestFilter {

    private final static Logger LOGGER = LoggerFactory.getLogger(AppKeyVerifierFilter.class);

    private final static String APP_KEY = "994f4fe4-APP-KEY-f3d5af27e1ee";

    @Context
    private HttpServletRequest httpServletRequest;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        String appKeyValueFromHeader = requestContext.getHeaderString("APP-KEY");
        LOGGER.info("APP Key:{}", appKeyValueFromHeader);
        if (!StringUtils.equals(APP_KEY, appKeyValueFromHeader) ) {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                                             .entity(new AppError("APP-KEY is not available or wrong"))
                                             .build());

        }
    }
}

3. Then apply the custom annotation to the target resource classes or methods.


package com.resource;

import com.model.User;
import com.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;

@AppKeyVerifier
@Path("users")
public class UserResource {

    private final static Logger LOGGER = LoggerFactory.getLogger(UserResource.class);

    @Inject
    UserService userService;

    @GET
    @Path("get/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User get(@PathParam("id") String id) throws Exception {
        LOGGER.debug("get method is called with id:{}", id);
        return userService.get(id);
    }
    @GET
    @Path("getAll")
    @Produces(MediaType.APPLICATION_JSON)
    public List getAll() throws Exception {
        LOGGER.debug("getAll method is called");
        return userService.getAll();
    }

    @POST
    @Path("add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String save(User user) throws Exception {
        LOGGER.debug("save method is called with user:{}", user);
        return userService.save(user);
    }

    @DELETE
    @Path("delete/{id}")
    @Produces(MediaType.TEXT_PLAIN)
    public String remove(@PathParam("id") String id) throws Exception {
        LOGGER.debug("remove method is called with id:{}", id);
        return userService.remove(id);
    }

    @POST
    @Path("update")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String update(User user) throws Exception {
        LOGGER.debug("update method is called with user:{}", user);
        return userService.update(user);
    }
}

Refer my code @https://github.com/dkbalachandar/java-rest-docker

JAX-RS – How to bind Filters to JAX-RS resources with Dynamic Binding

JAX-RS provides multiple ways using which server side filters and interceptors can be bound to their target components.

By default, JAX-RS filters and interceptors are applied to all the methods of a resource class. This can be overridden with Named Binding or Dynamic binding. In this post, I am going to give an example to show how to do that.

Assume that we have a JAX-RS application which has two end points. They are “greeting” and “users”. Now we want to protect the “users” service from unauthorized access. So we want to intercept all the “users” service request and do some validation and then allow it if it’s valid. Assume that the client has to pass through an APP-KEY in the request header.

Here are my resource classes.

UserResource.java


package com.resource;

import com.model.User;
import com.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Path("users")
public class UserResource {

    private final static Logger LOGGER = LoggerFactory.getLogger(UserResource.class);

    @Inject
    UserService userService;

    @GET
    @Path("get/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User get(@PathParam("id") String id) throws Exception {
        LOGGER.debug("get method is called with id:{}", id);
        return userService.get(id);
    }
    @GET
    @Path("getAll")
    @Produces(MediaType.APPLICATION_JSON)
    public List getAll() throws Exception {
        LOGGER.debug("getAll method is called");
        return userService.getAll();
    }

    @POST
    @Path("add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String save(User user) throws Exception {
        LOGGER.debug("save method is called with user:{}", user);
        return userService.save(user);
    }

    @DELETE
    @Path("delete/{id}")
    @Produces(MediaType.TEXT_PLAIN)
    public String remove(@PathParam("id") String id) throws Exception {
        LOGGER.debug("remove method is called with id:{}", id);
        return userService.remove(id);
    }

    @POST
    @Path("update")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String update(User user) throws Exception {
        LOGGER.debug("update method is called with user:{}", user);
        return userService.update(user);
    }
}

HelloWorldResource.java



package com.resource;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/greeting")
public class HelloWorldResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response sayHello(){
        System.out.println("sayHello");
        return Response.ok().entity("Hello World").build();
    }
}

Dynamic binding

It provides a way to add/bind the filters to resource methods/classes programmatically.

We have to add a Feature class which implements the DynamicFeature interface. Then override the configure method and provide our own logic to bind the filers to resource methods.

In my code, I am checking the resource class name with the APP_CLASS_LIST list. If that list contains the class name, then binding the AppRequestFilter to that class. I don’t want to filter the greeting service. Hence the APP_CLASS_LIST contains only the UserResource class name alone.

Refer the below example.


package com.resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
import java.util.Arrays;
import java.util.List;

@Provider
public class AppKeyFeature implements DynamicFeature {


    private final static Logger LOGGER = LoggerFactory.getLogger(AppKeyFeature.class);

    private static final List APP_CLASS_LIST = Arrays.asList(UserResource.class);

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        LOGGER.info("resourceInfo.getResourceClass():{}", resourceInfo.getResourceClass());
        if (APP_CLASS_LIST.contains(resourceInfo.getResourceClass())) {
            context.register(AppRequestFilter.class);
        }
    }
}

Refer below my filter code. This filter will validate the APP_KEY available in the request header with the default one. If it does not match, then it will abort the request and throw an error message. Make sure that you should not add @provider annotation for AppRequestFilter.


package com.resource;

import com.model.AppError;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;

public class AppRequestFilter implements ContainerRequestFilter {

    private final static Logger LOGGER = LoggerFactory.getLogger(AppRequestFilter.class);

    private final static String APP_KEY = "994f4fe4-APP-KEY-f3d5af27e1ee";

    @Context
    private HttpServletRequest httpServletRequest;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        String appKeyValueFromHeader = requestContext.getHeaderString("APP-KEY");
        LOGGER.info("APP Key:{}", appKeyValueFromHeader);
        if (!StringUtils.equals(APP_KEY, appKeyValueFromHeader) ) {
            requestContext.abortWith(Response.status(Status.UNAUTHORIZED)
                                             .entity(new AppError("APP-KEY is not available or wrong"))
                                             .build());

        }
    }
}

Refer my code @https://github.com/dkbalachandar/java-rest-docker

Jersey REST service and Thread Local usage

In this exercise, I am going to provide an example to show how we can use the ThreadLocal with the Jersey REST service.

Consider that it’s a REST application and we need the user details in the application flow many places. The User details will be retrieved with the header information availbale in the HttpServletRequest.
One solution is to pass the HttpServletRequest object whereever its required. But its a tedious job.
So an another way is to use the ContainerRequestFilter and processes the request, then extract out the header and call the back end to get the User details and put into the ThreadLocal storage so that it can be accessible anywhere in that thread flow.

Refer the below sample code to know how to do that. We don’t use the Spring Framework here.

DataHolder:
This class holds the static ThreadLocal variable and it has getter and setter for User object. We will be setting the User Object from the ContainerRequestFilter


package com.utils;

public class DataHolder{

    private static final ThreadLocal userThreadLocal = new ThreadLocal();

    public static void setUser(User user) {
        userThreadLocal.set(user);
    }

    public static User getUser() {
        return userThreadLocal.get();
    }

    public static void remove() {
        userThreadLocal.remove();
    }
}

UserDataCaptureRequestFilter
This filter will intercept the request and extract out the header and retrieve the user details and put it into the ThreadLocal.



package com.filters;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class UserDataCaptureRequestFilter implements ContainerRequestFilter {
    @Context
    private HttpServletRequest httpRequest;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        //Get the User data from the back end system with the request header/cookie. I have not given the getUser() method but assume it will get the user details 
        User user = getUser(httpRequest);
        DataHolder.setUser(user);
    }
}


Application
The ResourceConfig class where we can specify the packages to look into for the end points and context


public class Application extends ResourceConfig {
    public Application() {
        packages(true, "com.resource");
        packages(true, "com.filters");
    }
}

 
UserResource
This service returns the User details in JSON format. Here We have not retrived the User Details once again since we already retrieved the data in the filter itself. So the data would be available in the Thread scope. So we could use it


package com.resource;

import com.utils.DataHolder;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("user")
public class UserResource{

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUser() {
       
        return Response.ok().entity(DataHolder.getUser()).type(MediaType.APPLICATION_JSON).build();
    }
}

 

 

Test Secure REST services with Chrome Browser Plugin

Most of us want to test out the REST services via Advanced Rest Client or Postman for some reason or debug an issue.

But if the REST services are secure and protected by Ping Access or SiteMinder or any other tool, then we will get a login page. So we have to hard code the browser cookies to bypass the login page.

There is an another way to do that.

If you are using Advanced Rest Client(https://advancedrestclient.com), then you can use ARC cookie exchange plugin.
So this plugin helps ARC plugin to retrieve the browser cookies and send it in the request.

If you are using Postman(https://www.getpostman.com), then you can use Postman interceptor. So the Postman interceptor plugin helps the Postman plugin to use the browser cookies for each service call.

HTTP Caching in REST API

In this post, I am going to show an example which explains the implementation of HTTP caching in REST API.

As we know that REST API services can be cached in the browser whereas SOAP based services are not.

We have two kinds of cache control mechanism

1. Time based cache header
2. Conditional cache header

Time based cache header

Assume that we have a web service and you want to cache the response of that service in client’s browser for 5 min. So to achieve the same, we should have to set the cache control HTTP header appropriately


Cache-Control:private, max-age=300

Here “private” denotes that the response will be cached only in the client mostly by browser and not any intermediate proxy servers

max-age denotes that how long the resource is valid. The value should be given in seconds

Conditional cache header

With the conditional cache header approach, the browser will ask the server whether the response/content has been changed or not. Here the Browser sends out ETag and If-Modified-Since headers[Need to set If-Modified-Since at the client API] to the server and at the server side we should use these headers and implement our logic and send out the response only if its changed or the client side content has been expired.

You can note for the first time, we get the HTTP status 200 and for the subsequent request, we get the HTTP status 304 Not Modified

Please refer the below sample code which shows both approaches



import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;

@Path("/greeter")
public class GreetingResource {

    private static final String HELLO_MESSAGE= "Welcome %s";

    private static final String BIRTHDAY_MESSAGE= "Happy Birthday %s";

    @Context
    private Request request;

    @GET
    @Path("welcome")
    @Produces(MediaType.TEXT_HTML)
    public Response sayWelcomeMessage(@QueryParam("name") String name) {
        System.out.println("sayWelcomeMessage:::::"+ System.currentTimeMillis());
        CacheControl cacheControl = new CacheControl();
        cacheControl.setMaxAge(60);
        cacheControl.setPrivate(true);
        Response.ResponseBuilder builder = Response.ok(String.format(HELLO_MESSAGE, name));
        builder.cacheControl(cacheControl);
        return builder.build();
    }

    @GET
    @Path("bday")
    @Produces(MediaType.TEXT_HTML)
    public Response sayBdayWishesMessage(@QueryParam("name") String name) {

        System.out.println("sayGreetingMessage:::::"+ System.currentTimeMillis());
        CacheControl cacheControl = new CacheControl();
        //60 seconds
        cacheControl.setMaxAge(60);

        String message = String.format(BIRTHDAY_MESSAGE, name);
        EntityTag entityTag = new EntityTag(Integer.toString(message.hashCode()));
        //Browser sends ETag. So we can use this and
        // find out whether the response is expired or not
        Response.ResponseBuilder builder = request.evaluatePreconditions(entityTag);
        // If the build is null, then the content dont match, so the response should be sent
        if (builder == null) {
            builder = Response.ok(message);
            builder.tag(entityTag);
        } else {
            builder.cacheControl(cacheControl).tag(entityTag);
        }
        return builder.build();
    }
}