How do I expose a new method in a MongoRepository extension class to the generated REST API and include the same ALPS/HATEOAS metadata, links etc.
I have the usual Spring Data MongoDB repository:
public interface CollectionRepository extends Repository<Collection, String> {
// Simple queries
Collection findFirstByName(@Param("name") String name);
}
Now I want to add another method and have it integrate into the generated Repository REST API so that it is included in the {repository}/collection/search response alongside the QueryDSL generated elements.
I'm stuck on 2 things 1) Using PersistentEntityResourceAssembler and PersistentEntityResource to provide the parameters and convert the response into the appropriate format. 2) Automating the generation of the HATEOAS/ALPS metadata so the parent URL shows the method with parameters in the API browser.
Here's my custom controller. Note that I had to specify a @Qualifier on the template autowiring as I have multiple databases and it was picking the wrong one be default.
@Component
@RepositoryRestController
@RequestMapping(value = "{repository}/search")
public class CollectionRepositoryController implements ResourceProcessor<RepositorySearchesResource> {
@Autowired private EntityLinks entityLinks;
@Autowired private PagedResourcesAssembler pagedResourcesAssembler;
@Autowired Repositories repositories;
@Autowired
@Qualifier("mongoTemplateCollections")
private MongoTemplate mongoTemplate;
private final CollectionRepository repository;
@Autowired
public CollectionRepositoryController(CollectionRepository repo) {
repository = repo;
}
@SuppressWarnings("unchecked")
@RequestMapping(value = "findText", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public PagedResources<PersistentEntityResource> findText(
Pageable pageable, @RequestParam(value = "findText", required = false) String findText,
PersistentEntityResourceAssembler resourceAssembler) {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny(findText);
Query query = TextQuery.queryText(criteria).sortByScore();
List<Collection> collections = mongoTemplate.find(query, Collection.class);
Page<Collection> page = new PageImpl<Collection>(Arrays.asList(new Collection()), pageable, pageable.getOffset());
// What goes below here to convert List<Collections> into PersistentEntityResource ?
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(Collection.class);
for(Collection collection : collections) {
PersistentEntityResource collectionResource = PersistentEntityResource.build(collection, persistentEntity).
withLink(new Link("/collection/" + collection.getId())).build();
Log.info("collections resource: " + collectionResource);
}
return pagedResourcesAssembler.toResource(page, resourceAssembler);
}
// https://mcmap.net/q/871850/-how-to-add-custom-methods-to-spring-data-rest-jpa-implementation-and-leverage-hateos-support
@Override
public RepositorySearchesResource process(RepositorySearchesResource resource) {
LinkBuilder lb = entityLinks.linkFor(Collection.class, "name");
resource.add(new Link(lb.toString() + "/search/findText{?findText}", "findText"));
return resource;
}
}
I also created this, but I'm not sure how/where to wire it in:
@Component
public class CollectionResourceAssembler implements ResourceAssembler<Collection, Resource<Collection>> {
@Autowired
EntityLinks entityLinks;
@Override
public Resource<Collection> toResource(Collection collection) {
Resource<Collection> resource = new Resource<Collection>(collection);
final LinkBuilder lb = entityLinks.linkForSingleResource(Collection.class, collection.getId());
resource.add(lb.withSelfRel());
resource.add(lb.slash("answers").withRel("answers"));
// other links
return resource;
}
}
The bit I'm having trouble with is the "what goes below here", such that it does as Oliver suggested:
calling the repository and using a PersistentEntityResourceAssembler injectable into the handler method to produce a PersistentEntityResource to return.
And, do I really have to manually set the ALPS/HATEOAS data using the ResourceProcessor.process() method or is there a trick to automating that?
This is where an example would be priceless. I'm pretty sure there's nothing in SO that shows how to do this, at least at the level of handholding I need. This one is also pretty close: Defining a resource assembler for a REST Spring HATEOAS controller but also assumes more than I know.