Consuming Spring Hateoas Pageable
Asked Answered
A

1

8

I have a Rest-Service using HAteoas, what worked before without pageing. Now I am producing pageable Json. I did it with out-of-the box features from Spring-Hateoas. But now I am stucking consuming it and I guess it is really not well documented, if it is.

My JSON looks like follows:

{
"_embedded": {
"vertragResourceList": [
  {
    "identifier": 728,
    "auszubildender": "Rumm",
    "beruf": "Landwirt/in",
    "betrieb": "Mitterbauer Johann",
    "betriebsNummer": "e12d0949-67ae-4134-9dc2-fb67758b6b16",
    "zustaendigeStelle": "Irgendwo",
    "beginn": 529887600000,
    "status": "RECENT",
    "fachrichtung": null,
    "schwerpunkt": "Grünland oder Ackergras",
    "ende": 623113200000,
    "_links": {
      "self": {
        "href": "http://localhost:8080/bbsng-app-rest/vertrag/728"
      }
    }
  },
  {
    "identifier": 803,
    "auszubildender": "Gossen",
    "beruf": "Landwirt/in",
    "betrieb": "Beer Johann",
    "betriebsNummer": "d5a20cb9-7273-4b75-85bd-f8e7d6a843c4",
    "zustaendigeStelle": "Woanders",
    "beginn": 278118000000,
    "status": "RECENT",
    "fachrichtung": null,
    "schwerpunkt": "Ackerfutterbau",
    "ende": 339116400000,
    "_links": {
      "self": {
        "href": "http://localhost:8080/bbsng-app-rest/vertrag/803"
      }
    }
  }
]
},
"page": {
"size": 2,
"totalElements": 1000,
"totalPages": 500,
"number": 5
}
}

====

But now my list is "_embedded", so how can I consume it in most convenient way. I would prefer out-of-the-box soltions by Spring-Hateoas or similar.

My code before worked like follows (Json was not wrapped in _embedded/vertragResourceList before!!!).

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public VertragsListe findeAlleVertraege(final Integer firstDataSet, final Integer lastDataSet, final VertragDTFilter vertragsFilter,
        final VertragDTSorting vertragSorting) {
    final VertragsListe vertragsListe = new VertragsListe();
    final String url = LinkUtils.findeVertrag(firstDataSet, lastDataSet, vertragsFilter, vertragSorting);
    final ResponseEntity<List> entity = template.getForEntity(url, List.class);

    if (OK.equals(entity.getStatusCode())) {
        final List<LinkedHashMap> body = entity.getBody();
        for (final LinkedHashMap map : body) {
            vertragsListe.add(getPopulatedVertrag(vertragsListe, map));
        }
    }

    return vertragsListe;
}

Stacktrace:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@e89d61c; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@e89d61c; line: 1, column: 1]

=====

EDIT:

Corresponding Resourceclass looks like this (Serverside and Clientside!!!):

public class VertragPagedResources extends PagedResources<VertragResource> {

    @SuppressWarnings("unchecked")
    public VertragPagedResources(final Collection<VertragResource> content, final PageMetadata metadata) {
        super(content, metadata, CollectionUtils.EMPTY_COLLECTION);
    }

    public VertragPagedResources() {
        super();
    }

}

On Clientside I changed now followed:

@Autowired private RestTemplate template;

@Override
public VertragPagedResources findeAlleVertraege(final Integer firstDataSet, final Integer lastDataSet, final VertragDTFilter vertragsFilter,
        final VertragDTSorting vertragSorting) {
    final String url = LinkUtils.findeVertrag(firstDataSet, lastDataSet, vertragsFilter, vertragSorting);
    final ResponseEntity<VertragPagedResources> entity = template.getForEntity(url, VertragPagedResources.class);

    if (OK.equals(entity.getStatusCode())) {
        return entity.getBody();
    }

    return new VertragPagedResources();
}

Now I don't get any exceptions, but content is empty. The only thing what is filled correctly are the information from pageable (numberOfReturned Datasets, pageSize and so on). The content is empty List!!! When debugging and I try out the given URL in browser, then JSON looks like above mentioned.

<200 OK,PagedResource { content: [], metadata: Metadata { number: 1, total pages: 100, total elements: 1000, size: 10 }, links: [] },{Server=[Apache-Coyote/1.1], X-Application-Context=[application:custom:8080], totalNumber=[1000], Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Wed, 28 Jan 2015 16:58:16 GMT]}>

VertragResource (Client & Server):

public class VertragResource extends IdentifierResourceSupport {

  @NotNull private String auszubildender;
  @NotNull private String beruf;
  @NotNull private String betrieb;
  @NotNull private String betriebsNummer;
  @NotNull private String zustaendigeStelle;
  @NotNull private Calendar beginn;
  @NotNull private String status;

  private String fachrichtung;
  private String schwerpunkt;
  private Calendar ende;

  // GETTER & SETTER ....

Controller Server-Side:

@RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<VertragPagedResources> showAll( /*  PARAMS  */ ) { 

    // FILTER ...
    final VertragFilter filter = new VertragFilter();
    // FILL FILTER

    // SORTING ...
    final VertragSorting sorting = new VertragSorting(/* BLA */)

    // COMPUTE ...
    final VertragResourceAssembler assembler = new VertragResourceAssembler();
    final List<Vertrag> alleVertrage = service.findeAlleVertraege(/* BLA */);
    final List<VertragResource> resources = assembler.toResources(alleVertrage);

    //

    final long totalElements = service.zaehleAlleVertraege(filter);
    final long size = Math.min(displayLength, totalElements);
    final long totalPages = totalElements / size;
    final PageMetadata pageMetadata = new PageMetadata(displayLength, displayStart, totalElements, totalPages);
    final VertragPagedResources pagedResources = new VertragPagedResources(resources, pageMetadata);
    return new HttpEntity<VertragPagedResources>(pagedResources, headerTotalNumberOfData());
}

====

IdentifierResourceSupport :

public class IdentifierResourceSupport extends ResourceSupport {
    private Long identifier;

    public Long getIdentifier() {
        return identifier;
    }

    public void setIdentifier(Long identifier) {
       this.identifier = identifier;
    }
}

=====

EDIT 2:

What I did now, I switched Spring-Boot from 1.2.1 back to 1.1.10. Now I get an exception when trying the same, it seems that Spring-Boot 1.2.1 hides the exception:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "_embedded" (class at.compax.bbsng.client.mvc.client.resource.VertragPagedResources), not marked as ignorable (3 known properties: "links", "content", "page"])
 at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@62532c56; line: 1, column: 15] (through reference chain: at.compax.bbsng.client.mvc.client.resource.VertragPagedResources["_embedded"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_embedded" (class at.compax.bbsng.client.mvc.client.resource.VertragPagedResources), not marked as ignorable (3 known properties: "links", "content", "page"])
 at [Source: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@62532c56; line: 1, column: 15] (through reference chain: at.compax.bbsng.client.mvc.client.resource.VertragPagedResources["_embedded"])
Andaman answered 23/1, 2015 at 18:14 Comment(12)
I understand that you get a different data structure, but what exactly is the problem?Dennisedennison
The Point is, without using PageableResource the List of "Vertrag" in Json was in Root. So the above mentioned Java-Code worked as it is. But now my List is "wrapped" in _embedded/vertragResourceList and I can't get my List with ' final ResponseEntity<List> entity = template.getForEntity(url, List.class);'Andaman
So the Java code in your question is the consuming part. Who would have thought that. Try Resources.class. You the should get the list by entity.getBody().getContent().Dennisedennison
Thanks for your hint, I updated my question above.Andaman
I've got the feeling that PagedResources<VertragResource> should be PagedResources<Vertrag>. If that doesn't solve the problem the server-side code would be important, in particular the creation and population of the VertragPagedResources instance.Dennisedennison
The PagedResource extends the interface PagedResources<T> extends Resources<T> , so it wants a resource-class.Andaman
Server-Controller code addedAndaman
T is supposed to be the entity class.Dennisedennison
IdentifierResourceSupport is not a Spring class, I guess its your own. Does it have any Jackson annotation or is there a mixin for it?Dennisedennison
If I give him just the entity, then the entities doesnt have the self link. The IdentifierResourceSupport just extends the ResourceSupport, see above. Can it be, that somehow the RootJson must be configured somewhere, in that case "vertragResourceList" (see JSON). that maybe it doesnt find together because of naming???Andaman
it is really pain, I can do what I want, content is always emptyAndaman
I downgraded Spring-Boot to 1.1.10 and get now exception when calling REST-Service. See Edit2 aboveAndaman
A
7

Okay,

I got the solution now.

First of all, there is a BUG in Spring Boot 1.2.1. like mentioned in the question EDIT2. Spring-Boot 1.1.10 throws exception, because it cannot map the content in _embedded. Thats why my content stayed empty. Spring Boot 1.2.1 just leave it emptry without any hint as exception. Downgrading to 1.1.10 gave me the hint.

So what were the consequence to change:

Controller on Serverside:

@RequestMapping(method = GET, produces = "application/hal+json")
public HttpEntity<VertragPagedResources> showAll( /* PARAMS */  ) { 

    // LIKE CODE IN QUESTION ...

    return new HttpEntity<VertragPagedResources>(pagedResources);
}

====

RestTemplate Config:

Then you need to configure your Resttemplate to handle HAL-Format.

@Bean
public RestTemplate restTemplate() {
    final ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new Jackson2HalModule());

    final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
    converter.setObjectMapper(mapper);

    return new RestTemplate(Collections.<HttpMessageConverter<?>> singletonList(converter));
}

The Client-Code stays same as first Edit in Question!

Andaman answered 30/1, 2015 at 15:49 Comment(2)
At first I didn't understand how you were handling the PagedResources. This blog post helped a lot. izeye.blogspot.com/2015/01/…Militarist
The magic is to configure the rest template correctly. Thanks for your postAndaman

© 2022 - 2024 — McMap. All rights reserved.