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(){...}
Related
I want to create an API which can handle multiple post request on the same endpoint. I'd like to distinct between these requests by a content type.
#RestController
#RequestMapping("/")
public class TestController {
#GetMapping
public String hello() {
return "Hello";
}
#PostMapping(consumes = "application/json;data-model=1")
public String post1(#RequestBody HashMap<String, String> body) {
return "Post1";
}
#PostMapping(consumes = "application/json;data-model=2")
public String post2(#RequestBody HashMap<String, String> body) {
return "Post2";
}
}
With this code, I'm getting an error:
java.lang.IllegalStateException: Ambiguous handler methods mapped for '/': {public java.lang.String com.example.demo.TestController.post1(java.util.HashMap,java.lang.String), public java.lang.String com.example.demo.TestController.post2(java.util.HashMap,java.lang.String)}
I've seen it's possible to distinct by different query params. Is it also somehow possible for content type? Or am I forced into a large switch..case?
Why would I even want to do so?
I'm testing this approach:
https://www.infoq.com/articles/rest-api-on-cqrs/
Solution, thanks to M.Deinum:
#RestController
#RequestMapping("/")
public class TestController {
#GetMapping
public String hello() {
return "Hello";
}
#PostMapping(consumes = "application/vnd.data-model1+json")
public String post1(#RequestBody HashMap<String, String> body) {
return "Post1";
}
#PostMapping(consumes = "application/vnd.data-model2+json")
public String post2(#RequestBody HashMap<String, String> body) {
return "Post2";
}
}
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.
For learning purposes, I have made a custom authentication system where I pass a token from the client to the server through the Authorization header.
In the server side, I'd like to know if it's possible to create in the interceptor, before the request reaches a method in the controller, an User object with the email from the token as a property, and then pass this user object to every request where I require it.
This what I'd like to get, as an example:
#RestController
public class HelloController {
#RequestMapping("/")
public String index(final User user) {
return user.getEmail();
}
}
public class User {
private String email;
}
Where user is an object that I created in the pre-interceptor using the request Authorization header and then I can pass, or not, to any method in the RestController.
Is this possible?
#Recommended solution
I would create a #Bean with #Scope request which would hold the user and then put the appropriate entity into that holder and then take from that holder inside the method.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}
and then
#Component
public class MyInterceptor implements HandlerInterceptor {
private CurrentUser currentUser;
#Autowired
MyInterceptor(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.currentUser.setCurrentUser(new User("whatever"));
return true;
}
}
and in the Controller
#RestController
public class HelloController {
private CurrentUser currentUser;
#Autowired
HelloController(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#RequestMapping("/")
public String index() {
return currentUser.getCurrentUser().getEmail();
}
}
#Alternative solution
In case your object that you would like to have, only contains one field, you can just cheat on that and add that field to the HttpServletRequest parameters and just see the magic happen.
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TRY ONE AT THE TIME: email OR user
//BOTH SHOULD WORK BUT SEPARATELY OF COURSE
request.setAttribute("email", "login#domain.com");
request.setAttribute("user", new User("login#domain.com"));
return true;
}
}
You can use a local thread context object as follows - which will be handling one parameter per request thread (thread safe):
public abstract class LoggedUserContext {
private static ThreadLocal<User> currentLoggedUser = new ThreadLocal<>();
public static void setCurrentLoggedUser(User loggedUser) {
if (currentLoggedUser == null) {
currentLoggedUser = new ThreadLocal<>();
}
currentLoggedUser.set(loggedUser);
}
public static User getCurrentLoggedUser() {
return currentLoggedUser != null ? currentLoggedUser.get() : null;
}
public static void clear() {
if (currentLoggedUser != null) {
currentLoggedUser.remove();
}
}
}
Then in the interceptor prehandle function:
LoggedUserContext.setCurrentLoggedUser(loggedUser);
And in the interceptor postHandler function:
LoggedUserContext.clear();
From any other place:
User loggedUser = LoggedUserContext.getCurrentLoggedUser();
I know that there are a lot of similar questions in Stackoverflow but none of them helped me.
I have a controller like this:
com.mypkg.controller;
#RestController
public class MyController {
#RequestMapping(method = RequestMethod.POST,
......
public ResponseEntity<?> MyEndpoint(myParams) {
return this.myMethod(myParams, "myString");
}
public ResponseEntity<?> myMethod(myParams, String myString){
//do something
return myReponseEntity
}
}
I defined my aspect in this way:
com.mypkg.controller;
#Aspect
#Component
#Slf4j
public class MyAspect {
#Around("execution(* com.mypkg.controller.MyController.MyEndpoint(..)) && args(..,aParam)")
public ResponseEntity<?> endpointAround(ProceedingJoinPoint joinPoint, String aParam) throws Throwable {
// I am working fine
// do something
return
}
#Around("execution(* com.mypkg.controller.MyController.myMethod(..)) && args(..,myString)")
public ResponseEntity<?> myMethodAround(ProceedingJoinPoint joinPoint, String myString) throws Throwable {
// **** I AM NOT CALLED****
// do something
// return ...
}
}
I configured the AutoProxy
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass=true)
public class AopConfig {}
The function endpointAround is called every time that I call MyEndpoint (throw the REST api).
The problem is the second #Around. it is not called. I need to call a method everytime MyEndpoint is exectued and another one eveytime that MyEndpoint call myMethod.
The problem is that your method myMethod is call from within your other method directly, and not as a someSpringBean.myMethod.
The way spring works is by wrapping any of your beans, and then on the 'wrapping' it can execute all the aspect or other spring related stuff. When you call one method from another one inside the same class, you don't go through the wrapping, thus the aspect related stuff can't happen
You just missed some code. Let us use below code snippet it will work.
Use this com.mypkg.controller.MyController.myMethod instead of
com.mypkg.controller.myMethod it will work
Controller
com.mypkg.controller;
#RestController
public class MyController {
#RequestMapping(method = RequestMethod.POST,
......
public ResponseEntity<?> MyEndpoint(myParams) {
return this.myMethod(myParams, "myString");
}
public ResponseEntity<?> myMethod(myParams, String myString){
//do something
return myReponseEntity
}
}
and in Aspect
com.mypkg.controller;
#Aspect
#Component
#Slf4j
public class MyAspect {
#Around("execution(* com.mypkg.controller.MyController.MyEndpoint(..)) && args(..,aParam)")
public ResponseEntity<?> endpointAround(ProceedingJoinPoint joinPoint, String aParam) throws Throwable {
// I am working fine
// do something
return
}
#Around("execution(* com.mypkg.controller.MyController.myMethod(..)) && args(..,myString)")
public ResponseEntity<?> myMethodAround(ProceedingJoinPoint joinPoint, String myString) throws Throwable {
// **** I AM NOT CALLED****
// do something
// return ...
}
}
You just missed the package path. Your method path should be like this...springbean.method
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 { /* ... */ }