3

Premise:
A web-service exposes its data via REST. Each record belongs to the user who created the record (row-level-security). Users may only retrieve own records.

@RepositoryRestResource(path = "talks")
public interface TalkRepository extends PagingAndSortingRepository<Talk, Long> {

    @Override
    @Query("select t from Talk t where t.owner.id= ?#{principal?.id}")
    Page<Talk> findAll(Pageable pageable);
}

That repository is now available under a /talks endpoint.

Question:
Is there a way 1) to expose the same domain entity at multiple endpoints and 2) define different @Query annotations depending on the endpoint?

  • /talks
    I'd let that be my default implementation open to admin roles
  • /me/talks
    this is the endpoint that applies row level security against the principal and as part of the /me/** endpoints is exposed as public api to implementing clients.

This question is partially related to https://jira.spring.io/browse/DATAREST-555, but only in so far that the additional path segment is currently not supported.

Rationale:
I like the idea of not having to put too much conditional logic into SPeL queries like is owner or has_some_role (some examples here). Further it would become easy to protect the /me/** endpoints by different strategies than the default API (e.g. only /me/** might be subject to OAuth2).

sthzg
  • 5,514
  • 2
  • 29
  • 50

2 Answers2

2

If you know better/more concise solutions I am happy to accept other answers.

Based on the suggestion of @OliverGierke, the official docs and various other SO answers (also almost exclusively by Oliver, mostly 1, 2 and 3) I have implemented a custom controller to serve the endpoint.

This also enables projections on the custom endpoint and uses Spring Data REST's HATEOS assembler to provide the HAL+JSON output. What I haven't worked on so far is how to reuse the profile and alps logic that SDR provides out of the box but gets lost within the custom controller.

@BasePathAwareController
public class MyTalksController {

    private final TalkRepository repository;
    private final PagedResourcesAssembler pagedResourcesAssembler;

    @Autowired
    public MyTalksController(TalkRepository repo, PagedResourcesAssembler assembler) {
        repository = repo;
        pagedResourcesAssembler = assembler;
    }

    @RequestMapping(method = RequestMethod.GET, value = "/me/talks")
    @ResponseBody
    public PagedResources<?> getTalks(Pageable pageable, PersistentEntityResourceAssembler entityAssembler) {
        Page<Talk> talks = repository.meFindAll(pageable);
        return pagedResourcesAssembler.toResource(talks, entityAssembler);
    }
}
Community
  • 1
  • 1
sthzg
  • 5,514
  • 2
  • 29
  • 50
0

Answering 1) — yes, possible as follows:

// skipping old requests for the sake of the backward compatibility with the clients
// just returning ok with HTTP status 200
@RequestMapping(method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS, RequestMethod.DELETE},
                value = {"/lock", "/configure", "/cancel", "/initialize", "/register"})
public ApiResponse ok() {
    return ApiResponse.success();
}

Answering 2) — different query logic will naturally fit different endpoints, so there is a need to create a corresponding method for each one, I'd assume.

Poliakoff
  • 1,592
  • 1
  • 21
  • 40