4

I'm using Spring Data REST 2.5.1, Jackson 2.8.0, Spring Boot 1.3.6.

I'm trying to retrieve a simple list of entities from my Repository via RestTemplate. I can hit the end point in a browser, and get the expected HAL data. Retrieving a single Entity works fine as below. These are all using the default SDR endpoints (e.g. localhost:{port}/myEntity).

    ResponseEntity<Resource<MyEntity>> responseEntity =
    new RestTemplate()
        .exchange(
        uri + "/1",
            HttpMethod.GET,
            HttpEntity.EMPTY,
        new ParameterizedTypeReference<Resource<MyEntity>>() {}, port
        )

Or new RestTemplate().getForEntity(uri + "/1", MyEntity.class, port)

As a lot of SO questions seem to indicate, finding examples of retrieving a list is a problem. I've tried the ParameterizedTypeReference with Resources,Resource, MyEntity, an array, List. All with no luck.

        ResponseEntity<Resources<Resource<MyEntity>>> responseEntity =
            new RestTemplate()
                    .exchange(
                    uri,
                    HttpMethod.GET,
                    HttpEntity.EMPTY,
                    new ParameterizedTypeReference<Resources<Resource<MyEntity>>>() {}
            , port
            )

When called like above with pretty much any variety of Resources, Resource, List<MyEntity>, MyEntity, etc., the ResponseEntity is empty. Like:

<200 OK,Resources { content: [], links: [] },{Server=[Apache-Coyote/1.1], Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[...]}>

The string JSON looks like below in browser.

{
"_embedded" : {
"myEntities" : [ ... ]
},
"_links" : {
"self" : {
  "href" : "http://localhost:8080/myEntity"
},
"profile" : {
  "href" : "http://localhost:8080/profile/myEntity"
},
"search" : {
  "href" : "http://localhost:8080/myEntity/search"
}
},
"page" : {
  "size" : 20,
  "totalElements" : 10,
  "totalPages" : 1,
  "number" : 1
 }
}

Repository Definition:

@RepositoryRestResource(collectionResourceRel = "myEntities", path = "myEntity")
public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, Long>
, QueryDslPredicateExecutor<MyEntity>
, QuerydslBinderCustomizer<QMyEntity> { }

Any thoughts on what I am missing?

  • If you look at the structure of `Resources` you will notice that it's different from the structure of the JSON document. It's like trying to open your house with your car key. – a better oliver Jul 20 '16 at 08:35
  • Yeah, I get that the way I'm trying isn't working. I'm trying to find out what does work, not what doesn't. If SDR is creating this format, there has to be a Spring class that expects to consume it. I shouldn't have to roll my own for its standard format. – JudgingNotJudging Jul 20 '16 at 12:25
  • 1
    The basis for the document in your example is `PagedResources`. `_embedded` relies on `EmbeddedWrapper`. But there is no class that directly represents the document. The document is built dynamically because the `_embedded` property is dynamic. – a better oliver Jul 20 '16 at 13:08
  • I added the example repository definition. I get that it is a `PagedResource` because I'm extending the `PagingAndSortingRepository`, but is there no support for Java clients? I'm not doing anything specific to create an `_embedded` section. This is what SDR is giving me by default. Do I need to create a MyEntityResource? – JudgingNotJudging Jul 20 '16 at 15:42

2 Answers2

8

I solved this by doing a few things.

  1. I had to update to spring-hateoas:0.20.0.RELEASE from 0.19.0. spring-hateoas:0.19.0 did not support jackson 2.7+ as specified here.

  2. I updated my Client to call as below.

    ObjectMapper mapper = builder.build()
    MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    
    messageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"))
    messageConverter.setObjectMapper(mapper)
    
    ResponseEntity<PagedResources<MyEntity>> responseEntity =
            new RestTemplate(Arrays.asList(messageConverter))
                    .exchange(
                    uri,
                    HttpMethod.GET,
                    HttpEntity.EMPTY,
                    new ParameterizedTypeReference<PagedResources<MyEntity>>() {}, port
            )
    

PagedResources now looks like this:

<200 OK,PagedResource { content: [{<List of MyEntities>}], metadata: Metadata { number: 0, total pages: 1, total elements: 10, size: 20 }, links: [<List of hateoas links for MyEntities>] },{Server=[Apache-Coyote/1.1], Content-Type=[application/hal+json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Thu, 21 Jul 2016 14:57:18 GMT]}>

@zeroflagL's comment got me looking more closely at the PagedResources implementation, which eventually led to the 'aha!' moment with this blog.

The relevant bit is that the default RestTemplate doesn't set the accept header to application/hal+json. Instead, the default is application/x-spring-data-compact+json;charset=UTF-8 which has not content and only links. This is why I was getting empty content for my Resources types. Explicitly setting the MediaType as above fixed the issue.

kryger
  • 12,906
  • 8
  • 44
  • 65
1

Here is my solution:

    ParameterizedTypeReference<PagedResources<EntityObject>> responseType;
    responseType = new ParameterizedTypeReference<PagedResources<EntityObject>>() { };

    ResponseEntity<PagedResources<EntityObject>> pageResources;
    pageResources = restTemplate
            .withBasicAuth(username, password)
            .exchange(
                    UriComponentsBuilder.fromPath("/your/api/path").build().toString(),
                    GET,
                    null,
                    responseType
            );

    PagedResources<EntityObject> resource = pageResources.getBody();
    assertTrue(resource.getContent().isEmpty());
Bofeng
  • 3,174
  • 1
  • 8
  • 6
  • This approach worked for me in spring-hateoas/0.25.1.RELEASE (org.springframework.boot 2.1.3.RELEASE), which appears to use "application/hal+json;charset=UTF-8" as the content-type. Also, I'm using TestRestTemplate, since this is for a test case, but I'm not sure if that matters or not. – Jesse S Mar 27 '19 at 18:47