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

2

8

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.

Slaughterhouse answered 25/1, 2015 at 18:25 Comment(0)
S
8

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.

Slaughterhouse answered 25/1, 2015 at 18:53 Comment(0)
H
1

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

Hyperbolism answered 25/1, 2015 at 18:53 Comment(1)
We wish.... but it doesn't. infact makes it really hard, I can't wrap my head around achieving the output of PagingAndSortingRepository with RestController class. (so that I've more control like versioning which p&srepo lacks.)Potboiler

© 2022 - 2024 — McMap. All rights reserved.