How to implement REST API on a RestController using ModelAndView - spring

Im struggling on how to implement a API when the methods in my controller are returning a ModelAndView. Most tutorials I can find are returning ResponseEntities. Should i be making seperate API controllers which strictly handle the API calls under a /api mapping? (which i believe isn't RESTFUL practice). Or is it possible to handle my API calls in the same controller, even when making use of ModelAndView?
My controller looks as following:
#RestController
#RequestMapping("/dish")
public class DishController {
private final DishRepository dishRepository;
public DishController(DishRepository dishRepository) {
this.dishRepository = dishRepository;
}
#GetMapping
public ModelAndView list() {
Iterable<Dish> dishes = this.dishRepository.findAll();
return new ModelAndView("dishes/list", "dishes", dishes);
}
#GetMapping("{id}")
public ModelAndView view(#PathVariable("id") Dish dish) {
return new ModelAndView("dishes/view", "dish", dish);
}
#GetMapping(params = "form")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String createForm(#ModelAttribute Dish dish) {
return "dishes/form";
}
#ResponseStatus(HttpStatus.CREATED)
#PostMapping
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView create(#Valid Dish dish, BindingResult result,
RedirectAttributes redirect) {
if (result.hasErrors()) {
return new ModelAndView("dishes/form", "formErrors", result.getAllErrors());
}
dish = this.dishRepository.save(dish);
redirect.addFlashAttribute("globalMessage", "view.success");
return new ModelAndView("redirect:/d/{dish.id}", "dish.id", dish.getId());
}
#RequestMapping("foo")
public String foo() {
throw new RuntimeException("Expected exception in controller");
}
#ResponseStatus(HttpStatus.OK)
#GetMapping("delete/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView delete(#PathVariable("id") Long id) {
this.dishRepository.deleteById(id);
Iterable<Dish> dishes = this.dishRepository.findAll();
return new ModelAndView("dishes/list", "dishes", dishes);
}
#ResponseStatus(HttpStatus.OK)
#GetMapping("/modify/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView modifyForm(#PathVariable("id") Dish dish) {
return new ModelAndView("dishes/form", "dish", dish);
}

You should not use Model and View in RestController. The main goal of RestController is to return data, not views. Take a look here for more details: Returning view from Spring MVC #RestController.

#RestController is a shorthand for writing #Controller and #ResponseBody, which you should only use if all methods are returning an object that should be treated as the response body (eg. JSON).
If you want to combine both REST endpoints and MVC endpoints within the same controller, you can annotate it with #Controller and individually annotate each method with #ResponseBody.
For example:
#Controller // Use #Controller in stead of #RestController
#RequestMapping("/dish")
public class DishController {
#GetMapping("/list")
public ModelAndView list() { /* ... */ }
#GetMapping
#ResponseBody // Use #ResponseBody for REST API methods
public List<Dish> findAll() { /* ... */ }
}
Alternatively, as you've mentioned, you can use multiple controllers:
#Controller
#RequestMapping("/dish")
public class DishViewController { /* ... */ }
#RestController
#RequestMapping("/api/dish")
public class DishAPIController { /* ... */ }

Related

Is there a way, value defined in one main controller, can be accessed from all the other spring mvc controller?

Something like this:
#Controller
public class HomeController {
public String getCurrentUserDetails() {
String username = "testuser";
return username;
}
}
#Controller
public class DashboardController {
#RequestMapping("/admin/dashboard")
public ModelAndView showDashbard() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("username", GET username FROM HOME CONTROLLER [HomeController's getCurrentUserDetails]);
modelAndView.setViewName("/admin/dashboard");
return modelAndView;
}
}
#Controller
public class ProfileController {
#RequestMapping("/admin/profile")
public ModelAndView showProfile() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("username", GET username FROM HOME CONTROLLER [HomeController's getCurrentUserDetails]);
modelAndView.setViewName("/profile/dashboard");
return modelAndView;
}
}
Yes I know I can instantiate the HomeController and get the object. But making object in every controller is hectic.
I just want the value of one controller available in all other controllers.

Springboot evaluate api path from Controller method name

#RestController
#RequestMapping(value = "/api/orders/")
public class OrderController {
#PostMapping("create")
public ResponseEntity<OrderResponseV2> create(
#RequestBody OrderRequestV2 orderRequest) {
OrderResponse response = createOrderService.createOrder(orderRequest);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
}
Is there a way to get the whole API path including root context during runtime using Relfection from class+method name?

Spring MVC architecture

I have three .jsp pages on my app (index, user and admin). And I have three controllers for everything page but in my administration and user controllers use duplicate methods. For example, I have a method "getGenres" (For shows all genres from DB) in admin controller and user controller. How can I combine these methods if "#RequestMapping" is different in controllers?
#Controller
#EnableWebMvc
#RequestMapping("admin")
public class AdminController {
#Autowired
private GenreTableService genreService;
#RequestMapping(value = "genres")
public ResponseEntity<Map<String, List<Genre>>> getGenres() throws ServiceException {
Map<String, List<Genre>> genres = new HashMap<>(1);
genres.put("genres", genreService.getAll());
return new ResponseEntity<>(genres, HttpStatus.OK);
}
You want to combine methods. this is sample code.
enter link description here
enter link description here
like this :
#Controller
#RequestMapping("/common")
public class AdminController {
#Autowired
private GenreTableService genreService;
#RequestMapping(value = "/genres")
public ResponseEntity<Map<String, List<Genre>>> getGenres() throws ServiceException {
Map<String, List<Genre>> genres = new HashMap<>(1);
genres.put("genres", genreService.getAll());
return new ResponseEntity<>(genres, HttpStatus.OK);
}
}
or
#Controller
public class AdminController {
#Autowired
private GenreTableService genreService;
#RequestMapping(value = {"/admin/genres", "/user/genres"})
public ResponseEntity<Map<String, List<Genre>>> getGenres() throws ServiceException {
Map<String, List<Genre>> genres = new HashMap<>(1);
genres.put("genres", genreService.getAll());
return new ResponseEntity<>(genres, HttpStatus.OK);
}
}

Post authorizing Spring asynchronous controller response

I have a REST controller with a GET method. It returns a resource. I want to verify if the resource belongs to the authorized user by comparing the owner field on the Resource with the authorized user's login. With a normal synchronous request I'd do something like this:
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/resources/{id}")
#PostAuthorize("returnObject.ownerLogin == authentication.name")
public Resource getResource(#PathVariable Long id) {
return aService.getResource(id);
}
}
But what if the controller method is asynchronous (implemented with DeferredResult)?
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/resources/{id}")
#PostAuthorize("returnObject.ownerLogin == authentication.name")
public DeferredResult<Resource> getResource(#PathVariable Long id) {
DeferredResult<Resource> deferredResult = new DeferredResult<>();
aService
.getResourceAsync(id)
.thenAccept(resource -> {
deferredResult.setResult(resource);
});
return deferredResult;
}
}
Where AService interface looks like this:
#Service
public class AService {
#Async
public CompletableFuture<Resource> getResourceAsync(Long id) {
// implementation...
}
public Resource getResource(Long id) {
// implementation...
}
}
And Resource class is a simple DTO:
public class Resource {
private String ownerLogin;
// other fields, getters, setters
}
In the second example Spring Security obiously looks for the ownerLogin field on the DeferredResult instance. I'd like it to treat the asynchronously resolved Resource as the returnObject in the #PostAuthorize SPEL expression.
Is it possible? Maybe someone can suggest an alternatve approach? Any suggestions are welcome.
Couldn't achieve my goal with PostAuthorize and endedd up doing the following:
Made Resource a subresource of the User resource. Used a PreAuthorize annotation to validate user's login.
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}/resources/{id}")
#PreAuthorize("#login == authentication.name")
public DeferredResult<Resource> getResource(#PathVariable String login, #PathVariable Long id) {
DeferredResult<Resource> deferredResult = new DeferredResult<>();
aService
.getResourceAsync(login, id)
.thenAccept(resource -> {
deferredResult.setResult(resource);
});
return deferredResult;
}
}
Added an ownership check in AService. If Resource owner and the requesting user's login don't match throw an Exception that resolves to a 404 HTTP status:
#Service
public class AService {
private final ARepository aRepository;
public AController(ARepository aRepository) {
this.aRepository = aRepository;
}
#Async
public CompletableFuture<Resource> getResourceAsync(String owner, Long id) {
Resource resource = aRepository.getResource(id);
if (!resource.owner.equals(owner)) {
// resolves to 404 response code
throw ResourceNotFounException();
}
return resource;
}
}

Spring MVC Index Controller Strict Request

Trying to map the index controller correctly.
#Controller
#RequestMapping("/")
public class ClientIndexController
{
#RequestMapping(method=RequestMethod.GET)
public ModelAndView index()
{
}
}
or
#Controller
public class ClientIndexController
{
#RequestMapping("/")
public ModelAndView index(HttpServletRequest request)
{
}
}
These both approaches could not distinguish two different requests.
http://domain.com/
http://domain.com/?test=1 - in this case 404 must be thrown.
How can I avoid such behavior?
You can have Map with all request parameters, and check if the map is empty. Then you can implement a lot of different ways in creating a 404 (the one in the example below in only one way (maybe not the best)).
#Controller
#RequestMapping("/")
public class ClientIndexController {
#RequestMapping(method=RequestMethod.GET)
public ModelAndView index(#RequestParam Map<String,String> allRequestParams) {
if(allRequestParams != null && !allRequestParams.isEmpty() {
throw new ResouceNotFoundException();
}
}
#ExceptionHandler(ResouceNotFoundException.class)
#ResponseStatus(404)
public void RprocessValidationError(ResouceNotFoundException ex) {
}
}
If you only want to check that a special parameter is not there then you could use
#RequestMapping(method = RequestMethod.GET, params="!test")
public ModelAndView index(){...}

Resources