1

I need to add a query endpoint to all of my Spring Data REST repositories. Something like this:

/api/users/query?query=...
/api/issues/query?query=...
/api/projects/query?query=...
...

or

/api/users/search/query?query=...
/api/issues/search/query?query=...
/api/projects/search/query?query=...
...

The URL format doesn't matter.

I implemented a custom base repository:

@NoRepositoryBean
public interface QueryableRepository<T, ID> extends JpaRepository<T, ID> {
    Page<T> findAllByQuery(String query, Pageable pageable);
}
public class CustomRepository<T, ID> extends SimpleJpaRepository<T, ID> implements QueryableRepository<T, ID> {
    public CustomRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    @Override
    public Page<T> findAllByQuery(String query, Pageable pageable) {
        return findAll(pageable); // Some omitted implementation here
    }
}
@EnableJpaRepositories(repositoryBaseClass = CustomRepository.class)

For sure findAllByQuery method is not exposed by Spring Data REST:

I can implement a controller for each entity type exposing such a method:

@RepositoryRestController
public class UserController {
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping(path = "/users/search/query")
    public ResponseEntity<CollectionModel<PersistentEntityResource>> findAllByQuery(
            String query,
            Pageable pageable,
            PagedResourcesAssembler<Object> pagedAssembler,
            PersistentEntityResourceAssembler resourceAssembler) {
        return ResponseEntity.ok(pagedAssembler.toModel(
                userRepository.findAllByQuery(value, pageable).map(Object.class::cast),
                resourceAssembler));
    }
}

But is it possible to add this method to all entities once, without creation of dosens of same controllers?

Denis
  • 1,167
  • 1
  • 10
  • 30
  • 1
    Maybe you could use a `@PathVariable` and construct a map that maps the allowed values of that variable to repository instances? – Alex R Oct 23 '22 at 15:06
  • Thanks! It seems that org.springframework.data.rest.webmvc.RepositorySearchController uses this approach, but I don't yet understand in details how it works. – Denis Oct 23 '22 at 15:18

1 Answers1

0

The following works for me:

@RepositoryRestController
public class QueryController {

    private final ApplicationContext context;

    public QueryController(ApplicationContext context) {
        this.context = context;
    }

    @GetMapping(value = "/{repository}/query")
    public ResponseEntity<Object> findAllByQuery(
            RootResourceInformation resourceInformation,
            String query, Pageable pageable,
            PagedResourcesAssembler<Object> pagedAssembler,
            PersistentEntityResourceAssembler resourceAssembler) {
        Repositories repositories = new Repositories(context);
        Optional<QueryableRepository<?, ?>> repository = repositories
                .getRepositoryFor(resourceInformation.getDomainType())
                .filter(QueryableRepository.class::isInstance)
                .map(QueryableRepository.class::cast);
        if (repository.isPresent()) {
            return ResponseEntity.ok(pagedAssembler.toModel(
                    repository.get()
                            .findAllByQuery(query, pageable)
                            .map(Object.class::cast),
                    resourceAssembler));
        } else {
            return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
        }
    }
}
Denis
  • 1,167
  • 1
  • 10
  • 30