2

Using spring-data, I want to write two method for my Person entity.

Person.java:

public class Person {
    @Id
    String id;
    String name;
    Integer age;
    // getters/setters omitted for clarity
}

I have written also a PersonResouce:

public class PersonResource extends Resource<Person> {

    public PersonResource(Person content, Link... links) {
        super(content, links);
    }

}

I have also added a PersonResourceAssembler:

public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {

    public PersonResourceAssembler() {
        super(PersonController.class, PersonResource.class);
    }

    public PersonResource createResource(Person person) {
        PersonResource personResource = new PersonResource(person);
        Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();
        personResource.add(link);
        return personResource;
    }

    @Override
    public PersonResource toResource(Person person) {
        PersonResource resource = createResource(person);
        return resource;
    }
}

And this is my PersonController:

@RestController
@RequestMapping("persons")
public class PersonController {

  @Autowired
  private PersonService personService;

  @GetMapping
  public HttpEntity<List<PersonResource>> showAll(@PageableDefault(size = 20) Pageable pageable, PersonDTO condition) {
    Page<Person> page = personService.findAll(pageable, condition);
    Iterable<Person> personList = page.getContent();
    PersonResourceAssembler assembler = new PersonResourceAssembler();
    List<PersonResource> resources = assembler.toResources(personList);
    return new HttpEntity<>(resources);
  }

  @RequestMapping(name = "{id}", produces= MediaType.APPLICATION_JSON_VALUE)
  public HttpEntity<PersonResource> showOne(@PathVariable("id") Long id, PersonDTO condition) {
    condition.setId(id);
    Person person = personService.get(id);
    PersonResourceAssembler assembler = new PersonResourceAssembler();
    PersonResource resource  = assembler.toResource(person);
    return new HttpEntity<>(resource);
  }

}

This is the response for the list:

[
  {
    "createdById": 1,
    "createdDate": "2017-09-21T10:21:05.741Z",
    "deleted": false,
    "email": null,
    "firstName": "User49",
    "id": 52,
    "lastModifiedById": null,
    "lastName": "robot",
    "links": [
      {
        "href": "http://localhost:8080/users/52",
        "rel": "self"
      }
    ],
    "middleName": null,
    "mobile": "010101010001149",
    "roleList": [
      {
        "createdById": null,
        "createdDate": "2017-09-21T10:21:05.580Z",
        "deleted": false,
        "id": 2,
        "lastModifiedById": null,
        "name": "USER",
        "userList": null,
        "version": 0
      }
    ],
    "username": "user49",
    "version": 0
  }
]

This is the response for one resource :

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/users/52"
    }
  },
  "createdById": 1,
  "createdDate": "2017-09-21T10:21:05.741Z",
  "deleted": false,
  "email": null,
  "firstName": "User49",
  "id": 52,
  "lastModifiedById": null,
  "lastName": "robot",
  "middleName": null,
  "mobile": "010101010001149",
  "roleList": [
    {
      "createdById": null,
      "createdDate": "2017-09-21T10:21:05.580Z",
      "deleted": false,
      "lastModifiedById": null,
      "name": "USER",
      "userList": null
    }
  ],
  "username": "user49"
}

I've looked at the documentation and it appear possible to use PagedResources to create pagination.

I also want my response to look like the RepositoryRestController response which means for the list reponse :

  • put entities under key "_embedded"
  • put links under key "_links"
  • put page under key "page"

image

I have tried to play a bit with PagedResources but it appear to work differently than Resource and can't just replace it.

I would like to see a Controller/Assembler that use the PagedResource.

Solution

solution for me was to do like this

  @GetMapping
  public ResponseEntity<?> findAll(PagedResourcesAssembler<Person> pageAssembler, @PageableDefault(size = 20) Pageable pageable, UserDTO condition) {
    Page<User> userList = userService.findAll(pageable, condition);
    PagedResources<?> resources = pageAssembler.toResource(userList, new UserResourceAssembler());
    return ResponseEntity.ok(resources);
  }
Dimitri Kopriwa
  • 13,139
  • 27
  • 98
  • 204

1 Answers1

2

Try to use PagedResourcesAssembler to build paged resources:

@RestController
@RequestMapping("persons")
public class PersonController {

    @Autowired private PersonService personService;
    @Autowired private PagedResourcesAssembler<Person> assembler;
    @Autowired private EntityLinks links;

    @GetMapping("/paged")
    public ResponseEntity<?> getPaged(Pageable pageable) {
        Page<Person> personsPage = personService.getPaged(pageable);

        Link pageSelfLink = links.linkFor(Person.class).slash("/paged").withSelfRel();
        PagedResources<?> resources = assembler.toResource(personPage, this::toResource, pageSelfLink);

        return ResponseEntity.ok(resources);
    }

    private ResourceSupport toResource(Person person) {
        Link pesonLink = links.linkForSingleResource(person).withRel("person");
        Link selfLink = links.linkForSingleResource(person).withSelfRel();
        return new Resource<>(person, personLink, selfLink);
    }
}

See my example.

Cepr0
  • 28,144
  • 8
  • 75
  • 101
  • I have created a **UserResourceAssembler** that has the `toResource` method. It is written in the documentation that we should do [create a dedicated class responsible for doing so](https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.resource-assembler). What disturb me in your example is that you do not use it and create your own method. I would like to follow spring recommendation and use a different class, I wasn't able to reproduce the PageResource line you wrote with my requirements. – Dimitri Kopriwa Sep 21 '17 at 12:17
  • In my example I'm using this [toResource](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/PagedResourcesAssembler.html#toResource-org.springframework.data.domain.Page-org.springframework.hateoas.ResourceAssembler-org.springframework.hateoas.Link-) method of `PagedResourcesAssembler` that used custom assembler for any element of page collections, so my `toResource` method is a part of this assembler. – Cepr0 Sep 21 '17 at 12:43
  • Is there a way to have this `toResource` method in a separate class ? Like the **UserResourceAssembler** I've made? – Dimitri Kopriwa Sep 21 '17 at 12:46
  • I have tried with your code but apparently it doesn't work : [see screenshot](https://user-images.githubusercontent.com/1866564/30696446-e263efb4-9f05-11e7-97b5-168969a40080.png) – Dimitri Kopriwa Sep 21 '17 at 12:49
  • Add `implements org.springframework.hateoas.Identifiable` to your `User` class definition. – Marc Tarin Sep 21 '17 at 12:54
  • Thanks @MarcTarin, I managed to start my application and this return the expected result. Now I need to know how I should replace my `UserResourceAssembler` and move the `toResource` method in an appropriate class. Do you always leave it like this ? – Dimitri Kopriwa Sep 21 '17 at 13:26
  • 1
    Simply pass an 'UserResourceAssembler resourceAssembler' argument to your controller method. Spring will inject it for you, then you can use its `toResource()` method and pass it to `PagedResourcesAssembler$toResource()`. You may also use a [PersistentEntityResourceAssembler](https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/webmvc/PersistentEntityResourceAssembler.html). – Marc Tarin Sep 21 '17 at 13:57