Spring HATEOAS

In this POST we try to focus on one of the primary priciples of REST i.e. HATEOAS - (H)ypermedia (A)s (T)he (E)ngine (O)f (A)pplication (S)tate and the way to implement HATEOAS with HAL - (H)ypertext (A)pplication (L)anguage

This principle states that, the user's next course of action depends on the Hypermedia Links or the Hypertext included in the response which is dynamic and provides an uniform interface with REST architecture i.e. all the action that the user can perform is being contrained by the hyperlinks provided in the response i.e the user need not have any previous knowledge of the application.

And HAL is basically a convention or a protocol for expressing Hyperlinks in either XML or JSON format.
In this post we will concentrate only in JSON format.
HAL with all its conventions is very interesting and systematic and the details can be explored HERE

In this post I am going to describe my involvement with HATEOAS using spring and also implementing HAL contraints. Enough of theory!!!!!, lets browse through some code.

Lets dive into the configuration:
The whole application configuration is java based so we have one for web.xml and another for Spring HATEOAS application context.

1) SpringHateosApplicationConfig.java (for web.xml)

public class SpringHateosApplicationConfig implements WebApplicationInitializer {

 public void onStartup(ServletContext servletContext) throws ServletException {
  // TODO Auto-generated method stub
  
  /**
   * Adding configuration to the Root Context
   */
  
  AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  context.register(SpringHateosWebApplicationContext.class);
  
  /**
   * Registers Spring Dispatcher Servlet 
   * 
   */
  
  ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
  dispatcher.setLoadOnStartup(1);
  dispatcher.setAsyncSupported(true);
  dispatcher.addMapping("/hateos/*");
  
  
  /**
   * Register Character Encoding Filter
   */
  
  FilterRegistration.Dynamic characterEncodingFilter =  
    servletContext.addFilter("CharacterEncodingFilter", CharacterEncodingFilter.class); 
  characterEncodingFilter.setInitParameter("encoding", "UTF-8");
  characterEncodingFilter.setInitParameter("forceEncoding", "true");
  characterEncodingFilter.addMappingForUrlPatterns(null, false, "/*"); 
  characterEncodingFilter.setAsyncSupported(true);
  
  /**
   * Registration of Context Loader Listener
   */
  
  servletContext.addListener(new ContextLoaderListener(context));
  
  
 }

}


The above configuration is very simple, it registers a Spring DispatcherServlet and maps it to URL /hateos/* , registers characterEncodingFilter along with SpringHateosWebApplicationContext which contains all the HATEOAS specific configuration and registers all the beans of that configuration in the Application context, which we will explore below.

2) SpringHateosWebApplicationContext.java

@Configuration
@EnableWebMvc
@ComponentScan(basePackages="com.hateos.controller,com.hateos.component,com.hateos.service")
@EnableEntityLinks
@EnableHypermediaSupport(type = HypermediaType.HAL)
public class SpringHateosWebApplicationContext extends WebMvcConfigurerAdapter {
 
 @Override
 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  
  /*Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter =  new Jaxb2RootElementHttpMessageConverter();
  jaxb2RootElementHttpMessageConverter.setProcessExternalEntities(true);
  jaxb2RootElementHttpMessageConverter.canRead(Resource.class, MediaType.ALL);
  jaxb2RootElementHttpMessageConverter.canWrite(Resource.class, MediaType.ALL);
  
  
  MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter();
  marshallingHttpMessageConverter.setMarshaller(jaxb2Marshaller());
  marshallingHttpMessageConverter.setUnmarshaller(jaxb2Marshaller());
  
  converters.add(marshallingHttpMessageConverter);*/
  
  MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter =  new MappingJackson2HttpMessageConverter();
  mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper());
  converters.add(mappingJacksonHttpMessageConverter);
  
  
 }
 
 private ObjectMapper objectMapper()
 {
  return new ObjectMapper();
 }
 
 /*@Override
    public void configureContentNegotiation(ContentNegotiationConfigurer c) {
        c.defaultContentType(MediaTypes.HAL_JSON);
    }*/

  @Bean
   public CurieProvider curieProvider() {
     return new DefaultCurieProvider("ex", new UriTemplate("http://localhost:8080/SpringHATEOS/hateos/rels/{rel}"));
   }

  /**
   * @return RelProvider
   * Registering Custom Rel Provider as Bean 
   * in Spring Application Context
   * 
   */
  
 
  @Bean
   RelProvider relProvider() {
       return  new CustomRelProvider();
     }

  
 /*private Jaxb2Marshaller jaxb2Marshaller()
 {
  Jaxb2Marshaller jaxb2Marsler = new Jaxb2Marshaller();
  jaxb2Marsler.setClassesToBeBound(Resource.class);
  return jaxb2Marsler;
 }*/
 
}



The above class is very much similar to any other Java based Spring configuration class (Web) as it extends WebMvcConfigurerAdapter.
The noticeable annotations are:

1) @EnableEntityLinks - Enable Links for JPA entities (We will see later)
2) @EnableHypermediaSupport(type = HypermediaType.HAL) - Enables HAL support in the response, i.e. the response is generated satisfying the HAL principles.
We have registered a message coverter for JSON, as we will be implementing HAL in JSON format. Next we have registered two beans in the application Context one is for CurieProvider (Compact URI) and another is for custom RelProvider .

While implementing HATEOAS, the primary element that we should be centering is:Resource. Now, the first question arises, what is Resource Lets describe it with the help of a simple example:

Say, for example if we are querying for a Book in a Online Bookstore, each and every entities that connects to a Bookstore either actively or passivly comprises the Resource. Here we are going by the Book Resource. What I mean is the entities related with Book Like Author is also a Resource( along with the Book ), and accoring to REST pronciple we are going to perform actions centering each of these Resources within a Spring Controller, (As denoted by different HTTP verbs) in them and the related resources will be linked with some relations with the primary resource like Author resource related with the primary Book resource.

Now, each of these Resources or Entities can be JPA entities or any other models fetched from different sources. So to do operations on these entities or to implement HAL constraint we need to transform these entities into HATEOAS RESOURCES, which we will see later.

So, the Spring MVC Controller, that we will be using here, is Resource centric, What I mean by that is, all the Handler Methods within the Controller will be dedicated to perform various operation pertaining to the resource only.

For example considering the Book Resource, operations can be fetching the Book availibility, placing order for the Book, querying authors of the Book, Now while Querying authors of the Book, if we also query of the other Books by the same Author(provided hyperlink is there), then the Resource will be changed from Book to Author and hence all such calls will be handled by the controller for Author HATEOAS resource.

I hope I'm little successful in making my point, and hope to establish myself in a better way the more we traverse through the examples:

Our Resource Class is :

public class Greeting extends ResourceSupport{
 private final String content;

 private final String greet;
 
 //@JsonCreator
    public Greeting(/*@JsonProperty("content")*/ String content, String greet) {
        this.content = content;
        this.greet = greet;
    }

    public String getContent() {
        return content;
    }

 public String getGreet() {
  return greet;
 }
}

Our Resource class is Greeting class which extends ResourceSupport . Now for this time being, please ignore the @JsonCreator and @JsonProperty annotation.

And our Controller class is:

@Controller
public class GreetingController {

    private static final String TEMPLATE = "Hello, %s!";

    @RequestMapping("/greeting/{id}")
    @ResponseBody
    public HttpEntity greeting(
            @RequestParam(value = "name", required = false, defaultValue = "World") String name,@PathVariable("id") String id) {

        Greeting greeting = new Greeting(String.format(TEMPLATE, name),"Hi");
        greeting.add(linkTo(methodOn(GreetingController.class).greeting(name,"2")).withSelfRel());

        return new ResponseEntity(greeting, HttpStatus.OK);
    }
    
    
}

The controller is a basic Spring MVC Controller, with various static methods of the ControllerLinkBuilder class used to implement the HATEOAS principle.


greeting.add(linkTo(methodOn(GreetingController.class).greeting(name,"2")).withSelfRel());

When we execute the controller method, we get an output something like:


{"content":"Hello, World!","greet":"Hi","_links":{"self":{"href":"http://localhost:8080/SpringHATEOS/hateos/greeting/2?name=World","templated":false}}}

Lets analyze the output:

The output is a JSON String, having the content of the Greeting object and along with that a link to itself with relation 'self', i.e. a self referential link, and the interesting point to note is we are adding this link to the Greeting object and ultimately setting the object in Response entity with Response status 'OK'.

Adding to the Link to Greeting is possible because, Greeting is HATEOAS resource now, as it implements ResourceSupport. The last part of the output i.e. "templated":false is something related to Compact URI's (CURIE), which we will discuss it in coming chapters. With the help of ControllerLinkBuilder API we can create links referring to different methods of different Controller classes with appropriate relations and hence implement HATEOAS.

This is a basic example depicting Spring HATEOAS implementation. In my next blog I will to go into the details for implementing HATEOAS Resources along with HATEOAS Relations using various API's.

Till then Keep coding and Keep Contributing.

Comments

Popular posts from this blog

Use of @Configurable annotation.

Spring WS - Part 5

Spring WS - Part 4