I'm using Spring 4.0.0.RELEASE, Spring Data Commons 1.7.0.M1, Spring Hateoas 0.8.0.RELEASE
My resource is a simple POJO:
public class UserResource extends ResourceSupport { ... }
My resource assembler converts User objects to UserResource objects:
@Component
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> {
public UserResourceAssembler() {
super(UserController.class, UserResource.class);
}
@Override
public UserResource toResource(User entity) {
// map User to UserResource
}
}
Inside my UserController I want to retrieve Page<User>
from my service and then convert it to PagedResources<UserResource>
using PagedResourcesAssembler
, like displayed here: https://stackoverflow.com/a/16794740/1321564
@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler) {
Page<User> u = service.get(p)
return assembler.toResource(u);
}
This doesn't call UserResourceAssembler
and simply the contents of User
are returned instead of my custom UserResource
.
Returning a single resource works:
@Autowired
UserResourceAssembler assembler;
@RequestMapping(value="{id}", method=RequestMethod.GET)
UserResource getById(@PathVariable ObjectId id) throws NotFoundException {
return assembler.toResource(service.getById(id));
}
The PagedResourcesAssembler
wants some generic argument, but then I can't use T toResource(T)
, because I don't want to convert my Page<User>
to PagedResources<User>
, especially because User
is a POJO and no Resource.
So the question is: How does it work?
EDIT:
My WebMvcConfigurationSupport:
@Configuration
@ComponentScan
@EnableHypermediaSupport
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(pageableResolver());
argumentResolvers.add(sortResolver());
argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
}
@Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
return new HateoasPageableHandlerMethodArgumentResolver(sortResolver());
}
@Bean
public HateoasSortHandlerMethodArgumentResolver sortResolver() {
return new HateoasSortHandlerMethodArgumentResolver();
}
@Bean
public PagedResourcesAssembler<?> pagedResourcesAssembler() {
return new PagedResourcesAssembler<Object>(pageableResolver(), null);
}
@Bean
public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() {
return new PagedResourcesAssemblerArgumentResolver(pageableResolver(), null);
}
/* ... */
}
SOLUTION:
@Autowired
UserResourceAssembler assembler;
@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler pagedAssembler) {
Page<User> u = service.get(p)
return pagedAssembler.toResource(u, assembler);
}
You seem to have already found out about the proper way to use but I'd like to go into some of the details here a bit for others to find as well. I went into similar detail about
PagedResourceAssembler
in this answer.Representation models
Spring HATEOAS ships with a variety of base classes for representation models that make it easy to create representations equipped with links. There are three types of classes provided out of the box:
Resource
- an item resource. Effectively to wrap around some DTO or entity that captures a single item and enriches it with links.Resources
- a collection resource, that can be a collection of somethings but usually are a collection ofResource
instances.PagedResources
- an extension ofResources
that captures additional pagination information like the number of total pages etc.All of these classes derive from
ResourceSupport
, which is a basic container forLink
instances.Resource assemblers
A
ResourceAssembler
is now the mitigating component to convert your domain objects or DTOs into such resource instances. The important part here is, that it turns one source object into one target object.So the
PagedResourcesAssembler
will take a Spring DataPage
instance and transform it into aPagedResources
instance by evaluating thePage
and creating the necessaryPageMetadata
as well as theprev
andnext
links to navigate the pages. By default - and this is probably the interesting part here - it will use a plainSimplePagedResourceAssembler
(an inner class ofPRA
) to transform the individual elements of the page into nestedResource
instances.To allow to customize this,
PRA
has additionaltoResource(…)
methods that take a delegateResourceAssembler
to process the individual items. So you end up with something like this:And the client code now looking something like this:
Outlook
As of the upcoming Spring Data Commons 1.7 RC1 (and Spring HATEOAS 0.9 transitively) the
prev
andnext
links will be generated as RFC6540 compliant URI templates to expose the pagination request parameters configured in theHandlerMethodArgumentResolvers
forPageable
andSort
.The configuration you've shown above can be simplified by annotating the config class with
@EnableSpringDataWebSupport
which would let you get rid off all the explicit bean declarations.ALTERNATIVE WAY
Another way is use the Range HTTP header (read more in RFC 7233). You can define HTTP header this way:
That means, you want to get resource from 20 to 41 (including). This way allows consuments of API receive exactly defined resources.
It is just alternative way. Range is often used with another units (like bytes etc.)
RECOMMENDED WAY
If you wanna work with pagination and have really applicable API (hypermedia / HATEOAS included) then I recommend add Page and PageSize to your URL. Example:
Then, you can read this data in your BaseApiController and create some QueryFilter object in all your requests:
Your api should returns some special collection with information about number of items.
Your model classes can inherit some class with pagination support: