I'm trying to do something I think should be really simple. I have a Question
object, setup with spring-boot, spring-data-rest and spring-hateoas. All the basics work fine. I would like to add a custom controller that returns a List<Question>
in exactly the same format that a GET to my Repository
's /questions
url does, so that the responses between the two are compatible.
Here is my controller:
@Controller
public class QuestionListController {
@Autowired private QuestionRepository questionRepository;
@Autowired private PagedResourcesAssembler<Question> pagedResourcesAssembler;
@Autowired private QuestionResourceAssembler questionResourceAssembler;
@RequestMapping(
value = "/api/questions/filter", method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody PagedResources<QuestionResource> filter(
@RequestParam(value = "filter", required = false) String filter,
Pageable p) {
// Using queryDSL here to get a paged list of Questions
Page<Question> page =
questionRepository.findAll(
QuestionPredicate.findWithFilter(filter), p);
// Option 1 - default resource assembler
return pagedResourcesAssembler.toResource(page);
// Option 2 - custom resource assembler
return pagedResourcesAssembler.toResource(page, questionResourceAssembler);
}
}
Option 1: Rely on the provided SimplePagedResourceAssembler
The problem with this option is none of the necessary _links
are rendered. If there was a fix for this, it would be the easiest solution.
Option 2: Implement my open resource assembler
The problem with this option is that implementing QuestionResourceAssembler
according to the Spring-Hateoas documentation leads down a path where the QuestionResource
ends up being a near-duplicate of Question
, and then the assembler needs to manually copy data between the two objects, and I need to build all the relevant _links
by hand. This seems like a lot of wasted effort.
What to do?
I know Spring has already generated the code to do all this when it exports the QuestionRepository
. Is there any way I can tap into that code and use it, to ensure the output from my controller is seamless and interchangeable with the generated responses?
I've found a way to imitate the behavior of Spring Data Rest completely. The trick lies in using a combination of the PagedResourcesAssembler
and an argument-injected instance of PersistentEntityResourceAssembler
. Simply define your controller as follows...
@RepositoryRestController
@RequestMapping("...")
public class ThingController {
@Autowired
private PagedResourcesAssembler pagedResourcesAssembler;
@SuppressWarnings("unchecked") // optional - ignores warning on return statement below...
@RequestMapping(value = "...", method = RequestMethod.GET)
@ResponseBody
public PagedResources<PersistentEntityResource> customMethod(
...,
Pageable pageable,
// this gets automatically injected by Spring...
PersistentEntityResourceAssembler resourceAssembler) {
Page<MyEntity> page = ...;
...
return pagedResourcesAssembler.toResource(page, resourceAssembler);
}
}
This works thanks to the existence of PersistentEntityResourceAssemblerArgumentResolver
, which Spring uses to inject the PersistentEntityResourceAssembler
for you. The result is exactly what you'd expect from one of your repository query methods!