Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get Spring MVC+HATEOAS to encode a list of resources into HAL?

I have a Spring HATEOAS Resource such that ModelResource extends Resource<Model>.

In a @RestController I have a method to create new Models:

@RequestMapping(value = "", method = POST)
public ResponseEntity<ModelResource> postModel() {
    Model model = service.create().model;
    ModelResource resource = assembler.toResource(model);

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(URI.create(resource.getLink("self").getHref()));

    return new ResponseEntity<>(resource, headers, CREATED);
}

The created ModelResource returned from the above method is HAL-encoded:

$ curl -v -XPOST localhost:8080/models
> POST /models HTTP/1.1                                                                                                                                                       
> User-Agent: curl/7.32.0                                                                                                                                                     
> Host: localhost:8080                                                                                                                                                        
> Accept: */*                                                                                                                                                                 
>                                                                                                                                                                             
< HTTP/1.1 201 Created                                                                                                                                                        
< Date: Sun, 25 Jan 2015 11:51:50 GMT                                                                                                                                         
< Location: http://localhost:8080/models/0                                                                                                                                    
< Content-Type: application/hal+json; charset=UTF-8                                                                                                                           
< Transfer-Encoding: chunked
< Server: Jetty(9.2.4.v20141103)                                                                                                                                              
<                                                                                                                                                                             
{                                                                                                                                                                             
  "id" : 0,                                                                                                                                                                   
  "_links" : {                                                                                                                                                                
    "self" : {                                                                                                                                                                
      "href" : "http://localhost:8080/models/0"                                                                                                                               
    }                                                                                                                                                                        
  }                                                                                                                                                                           
}

In the same controller also have a method to list Models.

@RequestMapping(value = "", method = GET)
public List<ModelResource> getModels() {
    return service.find().stream()
        .map(modelProxy -> assembler.toResource(modelProxy.model))
        .collect(Collectors.toList());
}

For some reason, this method returns plain JSON, not HAL:

$ curl -v localhost:8080/models                                                                                                                                               
> GET /models HTTP/1.1                                                                                                                                                        
> User-Agent: curl/7.32.0                                                                                                                                                     
> Host: localhost:8080                                                                                                                                                        
> Accept: */*                                                                                                                                                                 
>                                                                                                                                                                             
< HTTP/1.1 200 OK                                                                                                                                                             
< Date: Sun, 25 Jan 2015 11:52:00 GMT                                                                                                                                         
< Content-Type: application/json;charset=UTF-8                                                                                                                                
< Transfer-Encoding: chunked                                                                                                                                                  
< Server: Jetty(9.2.4.v20141103)                                                                                                                                              
<                                                                                                                                                                             
[ {                                                                                                                                                                           
  "id" : 0,                                                                                                                                                                   
  "links" : [ {                                                                                                                                                               
    "rel" : "self",                                                                                                                                                           
    "href" : "http://localhost:8080/models/0"                                                                                                                                 
  } ]                                                                                                                                                                         
} ]
  • Why does the first method return HAL, and the second returns plain JSON?
  • How can I specify consistent behavior?

I've read about @EnableHypermediaSupport, but I don't have it set anywhere in my code.

like image 639
Dmitry Minkovsky Avatar asked Jan 25 '15 18:01

Dmitry Minkovsky


2 Answers

From this GitHub issue:

That works as expected. HAL defines the top level resource having to be a document. Thus a plain List by definition cannot be a HAL document. We've restricted the HAL customizations to only be applied if the root object to be rendered is ResourceSupport or a subtype of it to prevent arbitrary objects from getting HAL customizations applied. If you create a ResourceSupport instead of a List, you should see a correct HAL document be rendered.

The HAL Primer provides more detail and examples of what such a top-level wrapping resource looks like. In Spring HATEOAS, you use Resources<T> to represent such a wrapper, which is itself a Resource<T> with a self rel:

@RequestMapping(value = "", method = GET)                                                                                                                                     
public Resources<ModelResource> getModels() {
    List<ModelResource> models = service.find().stream()
        .map(modelVertex -> assembler.toResource(modelVertex.model))
        .collect(Collectors.toList());
    return new Resources<>(models, linkTo(methodOn(ModelController.class).getModels()).withRel("self"));
}

The returned Resources<T> type is then encoded into a document with embedded documents:

$ curl -v localhost:8080/models                                                                                                                                               
> GET /models HTTP/1.1                                                                                                                                                        
> User-Agent: curl/7.32.0                                                                                                                                                     
> Host: localhost:8080                                                                                                                                                        
> Accept: */*                                                                                                                                                                 
>                                                                                                                                                                             
< HTTP/1.1 200 OK                                                                                                                                                             
< Date: Sun, 25 Jan 2015 13:53:47 GMT                                                                                                                                         
< Content-Type: application/hal+json; charset=UTF-8                                                                                                                           
< Transfer-Encoding: chunked                                                                                                                                                  
< Server: Jetty(9.2.4.v20141103)                                                                                                                                              
<                                                                                                                                                                             
{                                                                                                                                                                             
  "_links" : {                                                                                                                                                                
    "self" : {                                                                                                                                                                
      "href" : "http://localhost:8080/models"                                                                                                                                 
    }                                                                                                                                                                         
  },                                                                                                                                                                          
  "_embedded" : {                                                                                                                                                             
    "modelList" : [ {                                                                                                                                                         
      "id" : 0,                                                                                                                                                               
      "_links" : {                                                                                                                                                            
        "self" : {                                                                                                                                                            
          "href" : "http://localhost:8080/models/0"                                                                                                                           
        },                                                                                                                                                                    
        "links" : {                                                                                                                                                           
          "href" : "http://localhost:8080/models/0/links"                                                                                                                     
        }                                                                                                                                                                     
      }                                                                                                                                                                       
    } ]                                                                                                                                                                       
  }                                                                                                                                                                           
}

As mentioned above, Resources<T> extends ResourceSupport just like Resource<T>. So you could create a ResourceAssembler for Resources<ModelResource> in order to avoid having to creating the self link by hand, and to generally encapsulate Resource<ModelResource> creation.


This answer suggests that HAL rendering is enabled by Spring Boot if it is available, which explains resources are being rendered HAL when possible.

like image 135
Dmitry Minkovsky Avatar answered Oct 05 '22 06:10

Dmitry Minkovsky


I expect Spring is automatically choosing application/hal+json as the default Content-Type for all ResponseEntity instances.

like image 28
Stephen Souness Avatar answered Oct 05 '22 06:10

Stephen Souness