I want to expose some REST resources in Spring, by default I can just let Spring Boot deal with this and this generates a nice HATEOAS response where I get everything 'free'.
Now I need to perform some logic when a certain resource is being POSTED, so I implemented my own @RestController class and override the POST method as follows:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.net.URI;
import java.security.Principal;
@RestController
@RequestMapping(Constants.PROJECTSPATH)
public class ProjectController {
@Autowired private ProjectRepository projectRepo;
@Autowired private ProjectRoleRepository projectRoleRepo;
@Autowired private AccountRepository accountRepo;
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> addProject(@RequestBody Project projectFromRequest, Principal principal) {
Project project = projectRepo.save(projectFromRequest);
Account currentAccount = accountRepo.findByUsername(principal.getName());
ProjectRole ownerRole = new ProjectRole(project, currentAccount, ProjectRoleEnum.OWNER);
projectRoleRepo.save(ownerRole);
final URI uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/{id}")
.buildAndExpand(project.getId())
.toUri();
return ResponseEntity.created(uri).body(new ProjectResource(project));
}
}
I use a class inheriting from ResourceSupport and in there I generate the required links for a HATEOAS response. So far so good, that isn't too much custom work.
The problem is that if I now just try to GET all projects, I get this message:
{"timestamp":1518610270403,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'GET' not supported","path":"/projects"}
I really prefer not to implement the other methods too, the default done by Spring Boot is great. But am I now forced to implement these as well and keep my entire HATEOAS response up to date with my domain object changes every time? That is functionality that I love 'out of the box'.
How to override only 1 Http Method (POST) in a @RestController, but let Spring handle all the other Http methods with a standard HATEOAS response
You can't do that with a @RestController
. As stated in the Spring Data REST reference:
Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the
@RepositoryRestController
annotation instead of a standard Spring MVC @Controller or @RestController
It is not explicitly mentionned, but annotating your controller with @RepositoryRestController
allows you to define a custom behavior for one endpoint while keeping all the other endpoints that Spring automatically generates.
For older versions, note that you can use the @RequestMapping
annotation at the method level only. Issue fixed in 2.4 M1 (Gosling), 2.3.1 (Fowler SR1), 2.1.6 (Dijkstra SR6).
Your example becomes:
@RepositoryRestController
@RequestMapping(/project)
public class ProjectController {
@Autowired private ProjectRepository projectRepo;
@Autowired private ProjectRoleRepository projectRoleRepo;
@Autowired private AccountRepository accountRepo;
@PostMapping(Constants.PROJECTSPATH) // @PostMapping is a shortcut for @RequestMapping(method = RequestMethod.POST). path can be something like "/prj" (beginning with slash, because on class level, there is no suffix slash)
public ResponseEntity<?> addProject(@RequestBody Project projectFromRequest, Principal principal) {
Project project = projectRepo.save(projectFromRequest);
Account currentAccount = accountRepo.findByUsername(principal.getName());
ProjectRole ownerRole = new ProjectRole(project, currentAccount, ProjectRoleEnum.OWNER);
projectRoleRepo.save(ownerRole);
final URI uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/{id}")
.buildAndExpand(project.getId())
.toUri();
return ResponseEntity.created(uri).body(new ProjectResource(project));
}
}