0

I'm using the latest of Spring REST and HATEOAS trying to expose a link to a search endpoint.

Here is the resource assembler:

@Component
public class AdminResourceAssembler extends ResourceAssemblerSupport<Admin, AdminResource> {

    public AdminResourceAssembler() {
        super(AdminController.class, AdminResource.class);
    }

    public AdminResource toResource(Admin admin) {
        AdminResource adminResource = createResourceWithId(admin.getId(), admin);
        BeanUtils.copyProperties(admin, adminResource);
        adminResource.add(linkTo(AdminController.class).slash(admin.getId()).slash("module").withRel("module"));        
        return adminResource;
    }

}

Here is the end point controller:

@RequestMapping(value = UriMappingConstants.PATH_SEPARATOR + UriMappingConstants.SEARCH, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<PagedResources<AdminResource>> search(@RequestParam(value = "searchTerm", required = true) String searchTerm, @PageableDefault Pageable pageable, PagedResourcesAssembler<Admin> pagedResourcesAssembler, UriComponentsBuilder builder) {
    HttpHeaders responseHeaders = new HttpHeaders();
    Pageable pageRequest = buildPageRequest(pageable.getPageNumber(), pageable.getPageSize());
    Page<Admin> searchedAdmins = adminService.search(searchTerm, pageRequest);
    responseHeaders.setLocation(builder.path(UriMappingConstants.PATH_SEPARATOR + UriMappingConstants.ADMINS + UriMappingConstants.PATH_SEPARATOR + "search").queryParam("searchTerm", searchTerm).queryParam("page", pageable.getPageNumber()).queryParam("size", pageable.getPageSize()).buildAndExpand(searchTerm).toUri());
    PagedResources<AdminResource> adminPagedResources = pagedResourcesAssembler.toResource(searchedAdmins, adminResourceAssembler);
    return new ResponseEntity<PagedResources<AdminResource>>(adminPagedResources, responseHeaders, HttpStatus.OK);
}

private Pageable buildPageRequest(int pageIndex, int pageSize) {
    Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "lastname"), new Sort.Order(Sort.Direction.ASC, "firstname"));
    return new PageRequest(pageIndex, pageSize, sort);
}

First, I'm not sure if I should call in the buildPageRequest method and simply pass in the original pageable to the search service.

The problem I have is two fold:

The published link in the response is missing the searchTerm parameter:

{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search{?page,size,sort}

I would expect it to be like:

{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search{?searchTerm,page,size,sort}

But again, as a newbie I don't know for sure.

And the controller always fetches the first page of the 10 items, ignoring the page number and size arguments I give in the request:

curl -H "Accept:application/json" --user joethebouncer:mignet http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstna&page=3&size=5

I guess I'm not too far from a asolution, but I don't even know exactly what the exposed link should look like.

Any directions would be very nice :-)

EDIT: Added information

The pageable configuration:

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
    argumentResolvers.add(resolver);
    super.addArgumentResolvers(argumentResolvers);
}

The service is simply a wrap around the repository:

public Page<Admin> search(String searchTerm, Pageable page) {
  return adminRepository.search(searchTerm, page);
}

which is an interface:

@Query("SELECT a FROM Admin a WHERE LOWER(a.firstname) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.lastname) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.email) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(a.login) LIKE LOWER(CONCAT('%', :searchTerm, '%')) ORDER BY a.lastname ASC, a.firstname ASC") public 

Page search(@Param("searchTerm") String searchTerm, Pageable page);

The database is H2 fronted by JPA and the console shows:

One can see that the offset is missing...

select admin0_.id as id1_1_, admin0_.version as version2_1_, admin0_.email as email3_1_, admin0_.firstname as firstnam4_1_, admin0_.lastname as lastname5_1_, admin0_.login as login6_1_, admin0_.password as password7_1_, admin0_.password_salt as password8_1_, admin0_.post_login_url as post_log9_1_ from admin admin0_ where lower(admin0_.firstname) like lower(('%'||'irstn'||'%')) order by admin0_.lastname ASC, admin0_.firstname ASC, admin0_.lastname asc, admin0_.firstname asc limit 10

Kind Regards,

Stephane Eybert

Stephane
  • 11,836
  • 25
  • 112
  • 175
  • are you using @enableautoconfiguration ? – Chris DaMour Aug 19 '14 at 00:51
  • No, never heard of that annotation. Let me Google it... – Stephane Aug 19 '14 at 06:21
  • Stephane, I see you solved this issue at http://patrickgrimard.com/2014/05/16/pagination-with-spring-data-and-hateoas-in-an-angularjs-app/. Would you care to share your solution? – John Deverall Nov 11 '15 at 23:55
  • @JohnDeverall I share it in the comment on his blog. I must admint it was a year ago and I lost context on this. Have you been through his blog article ? It should be easy to follow and implement. – Stephane Nov 12 '15 at 08:49

2 Answers2

0

no idea what buildPageRequest is doing...but i would bet you don't need it and it's the source of your paging problems

Regarding the self link. PagedResourceAssembler creates a very simple default one...but you can build a more complicated one and pass it in as a third parameter of the toResource method. So just build your own link in any of the many ways. I'd suggest something like:

  linkTo(methodOn(SomeController.class).search(searchTerm, pageable, null, null))

another thing to note that a self link should never be templated so this is a bug in spring-hateoas (really a dependency) see https://jira.spring.io/browse/DATACMNS-515

Chris DaMour
  • 3,650
  • 28
  • 37
  • I included the buildPageRequest method in the source code. It is simply building the page request sorting order. – Stephane Aug 18 '14 at 22:48
  • are you sure your service is respecting the pageable? – Chris DaMour Aug 18 '14 at 23:01
  • I added a linkTo: adminPagedResources.add(linkTo(methodOn(AdminController.class).search(searchTerm, pageable, pagedResourcesAssembler, builder)).withRel("search")); I can see it in the response: {"links":[{"rel":"next","href":"http://localhost:8080/nitro-project-rest/admins/search?page=1&size=10&sort=lastname,firstname,asc"},{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search{?page,size,sort}"},{"rel":"search","href":"http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstn"}],"content" but the generated self link is different. – Stephane Aug 18 '14 at 23:03
  • My controller endpoint ignores the page number and size. The arguments it receives are pageable page: 0 and size: 10 Here is the request: curl -H "Accept:application/json" --user joethebouncer:mignet http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstn&page=3&size=10&sort=asc – Stephane Aug 18 '14 at 23:16
  • 1
    you don't call add, you pass it as third param for toResource you should make the rel self – Chris DaMour Aug 19 '14 at 00:47
  • I now pass the selfLink I build: PagedResources adminPagedResources = pagedResourcesAssembler.toResource(searchedAdmins, adminResourceAssembler, selfLink); and the output becomes: {"links":[{"rel":"next","href":"http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstna&page=1&size=10&sort=lastname,firstname,asc"},{"rel":"self","href":"http://localhost:8080/nitro-project-rest/admins/search?searchTerm=irstna{&page,size,sort}"}],"content": The next link looks good. Only the self one looks templated. – Stephane Aug 19 '14 at 06:35
  • You'd have directions on how I could add a first and last links ? As it is now, only the next link is produced, and the ugly self templated link. – Stephane Aug 19 '14 at 07:35
  • But still, the controller keeps ignoring the passed in arguments for page number and size. The logger always shows them with their default values of page number 0 and size 10. Note the size of 10 instead of 20 as I use a resolver.setFallbackPageable(new PageRequest(0, 10)); in addArgumentResolvers method. These two arguments are ignored even if I request on the next link from the previous request. – Stephane Aug 19 '14 at 09:00
  • you can add other links via the add, it's only the self link that is part of the toResrouce() method. – Chris DaMour Aug 19 '14 at 14:39
0

Stupid me. I could not see curl needed double quotes around the url to allow for multiple parameters. curl -H "Accept:application/json" --user joethebouncer:mignet "localhost:8080/nitro-project-rest/admins/… now works fine.

Stephane
  • 11,836
  • 25
  • 112
  • 175