2

my project is moving away from a custom json format to json-hal and spring-data-rest. to continue the support for the "old" json i want to run the existing Resource-Controller parallel to the new Spring-Data-Rest provided one.

Whenever i configure spring-data-rest to use the same url as our existing controller ONLY the old controller is used and if the accept-header does not match i get an error response. when i use a different url, everything works

Is it possible to run a controller in parallel to the spring-data-rest one and respond based on the Accept-Header?

The old Controller:

@RepositoryRestController
@RequestMapping(value = "/api/accounts", produces = {"application/custom.account+json"})
public class AccountResource {

    @RequestMapping(method = RequestMethod.GET)
    @PreAuthorize("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')")
    public ResponseEntity<List<Account>> getAll(
        @RequestParam(value = "page", required = false) Integer offset,
        @RequestParam(value = "per_page", required = false) Integer limit,
        @RequestParam(value = "email", required = false) String email
    ) throws URISyntaxException {
        ...
    }
}
Laures
  • 5,389
  • 11
  • 50
  • 76
  • Have you tried specifying [`headers`](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#headers--) in your `@RequestMapping`? – heenenee Apr 03 '17 at 22:58

1 Answers1

3

@RepositoryRestController does not play well with @RequestMapping at the type level. First step, make sure you actually manage to catch the request, by removing the produces parameter from the RequestMapping (I use the GetMapping shortcut here). I also removed the @PreAuthorize annotation for it's not relevant for now, and introduced a parameter to catch the Accept header value (for debug):

@RepositoryRestController
public class AccountResource {

    @GetMapping(value = "/api/accounts")
    public ResponseEntity<List<Account>> getAll(
        @RequestParam(value = "page", required = false) Integer offset,
        @RequestParam(value = "per_page", required = false) Integer limit,
        @RequestParam(value = "email", required = false) String email,
    ) throws URISyntaxException {
        ...
    }

}

With this, you should be able to customize the GET /api/accounts at will and still benefit from POST/PUT/PATCH... /api/accounts provided automagically by Spring Data Rest, and also assert that content-type

If it works as expected, you can then:

  • try to narrow the method scope with produces = "application/custom.account+json" (no braces required for a single value) in the GetMapping annotation, and see that both your endpoint and the Spring generated endpoint method are avaiable
  • reinstate your @PreAuthorize annotation
  • get rid of the @RequestHeader parameter

That gives you:

@RepositoryRestController  // NO MAPPING AT THE TYPE LEVEL
public class AccountResource {

    @GetMapping(value = "/api/accounts", // Mapping AT THE METHOD LEVEL
                produces = "application/custom.account+json") // the content-type this method answers to
    @PreAuthorize("#oauth2.hasScope('read') and hasRole('ADMIN')")  // ROLE is 'ADMIN' not 'ROLE_ADMIN'
    public ResponseEntity<List<Account>> getAll(
        @RequestHeader("Content-Type") String contentType,
        @RequestParam(value = "page", required = false) Integer offset,
        @RequestParam(value = "per_page", required = false) Integer limit,
        @RequestParam(value = "email", required = false) String email,
    ) throws URISyntaxException {
        ...
    }

}

Now:

  • curl host:port/api/accounts will hit the Spring controller endpoint
  • curl host:port/api/accounts -H "Accept: application/custom.account+json" will hit your custom controller endpoint.
Marc Tarin
  • 3,109
  • 17
  • 49
  • i know that what you describe is possible but i want preserve the automatic spring-data-rest GET-endpoint AND offer my custom one for my own content-type. so far it looks like ANY GET i implement myself will replace the automatic one. – Laures Apr 04 '17 at 10:05
  • I offered you a *working* step by step to obtain the result you seek. I just tried it myself, it works, you can get both the automatic and the custom endpoint... I'll edit my answer with the final version and a curl example... – Marc Tarin Apr 04 '17 at 12:31
  • BTW, you should consider replacing your offset and limit parameters with a Pageable. – Marc Tarin Apr 04 '17 at 12:43
  • if i replicate your example in my existing project, my custom resource gets ignored and all requests go to spring-data-rest. the only difference between both requests is that with the custom Accept-Header the jackson serializer is not hal-aware. i will try your solution in an empty project as i cant update to the most recent boot/data-rest versions right now. – Laures Apr 04 '17 at 14:00
  • 2
    Probably some other configuration gets in the way. It's not always easy to track on large project with a lot of custom configuration. Which release of Spring Boot are you using? I've been using solutions similar to what I describe since release 1.4.x. Your log should show a bunch of lines with `RepositoryRestHandlerMapping : Mapped "{"` in them. Your controller should be mentionned explicitly and the Spring controller should be mentionned as `RepositoryRestHandlerMapping : Mapped "{[/{repository}],methods=[GET],produces=[application/hal+json || application/json]}"` – Marc Tarin Apr 04 '17 at 14:16
  • it looks like spring-data and my resource are mapped. but still only the data-rest method gets called. my guess is because data-rest is mapped to `application/*+json` it wins over `custom.account+json` – Laures Apr 04 '17 at 15:15
  • data-rest maps to: `{[/api/{repository}],methods=[GET],produces=[application/hal+json || application/json || application/*+json;charset=UTF-8]}` my controler maps to: `{[/api/accounts],methods=[GET],produces=[application/custom.account+json]}}` – Laures Apr 04 '17 at 15:17
  • and i'm too stupid to set the accept header correctly. it works now, i should use the same content-type everywhere so i don't get confused – Laures Apr 04 '17 at 15:34