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"])
Michael Hegner
  • 5,555
  • 9
  • 38
  • 64
  • 1
    I understand that you get a different data structure, but what exactly is the problem? – a better oliver Jan 25 '15 at 12:28
  • 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 entity = template.getForEntity(url, List.class);' – Michael Hegner Jan 27 '15 at 16:50
  • 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()`. – a better oliver Jan 27 '15 at 17:32
  • Thanks for your hint, I updated my question above. – Michael Hegner Jan 28 '15 at 16:53
  • I've got the feeling that `PagedResources` should be `PagedResources`. If that doesn't solve the problem the server-side code would be important, in particular the creation and population of the `VertragPagedResources` instance. – a better oliver Jan 28 '15 at 17:12
  • The PagedResource extends the interface PagedResources extends Resources , so it wants a resource-class. – Michael Hegner Jan 28 '15 at 17:28
  • Server-Controller code added – Michael Hegner Jan 28 '15 at 17:32
  • `T` is supposed to be the entity class. – a better oliver Jan 28 '15 at 17:34
  • `IdentifierResourceSupport` is not a Spring class, I guess its your own. Does it have any Jackson annotation or is there a mixin for it? – a better oliver Jan 28 '15 at 17:42
  • 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??? – Michael Hegner Jan 29 '15 at 17:07
  • it is really pain, I can do what I want, content is always empty – Michael Hegner Jan 29 '15 at 17:46
  • I downgraded Spring-Boot to 1.1.10 and get now exception when calling REST-Service. See Edit2 above – Michael Hegner Jan 29 '15 at 18:02

1 Answers1

6

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!

Michael Hegner
  • 5,555
  • 9
  • 38
  • 64
  • 1
    At first I didn't understand how you were handling the PagedResources. This blog post helped a lot. http://izeye.blogspot.com/2015/01/consume-spring-data-rest-hateoas-hal.html – Mark.ewd Dec 04 '15 at 00:24
  • The magic is to configure the rest template correctly. Thanks for your post – Michael Hegner Dec 05 '15 at 07:11