PUT request in Spring MVC - java

I'm trying to write a simple PUT request method in Spring MVC. I got the following:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
String name,
String email) {
User user = repository.findOne(id);
user.setName(name);
user.setEmail(email);
System.out.println(user.toString());
repository.save(user);
return user;
}
Which is obviously wrong, because it returns the following:
User{id=1, name='null', email='null'}
I also tried with #RequestBody annotation, but that also did not help. Any ideas what I'm doing wrong here would be greatly appreciated.

You can receive name and email whith the #RequestBody annotation:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestBody User user) {}
This is a better practice when it comes to REST applications, as your URL becomes more clean and rest-style.
You can even put a #Valid annotation on the User and validate its properties.
On your postman client, you send the User as a JSON, on the body of your request, not on the URL. Don't forget that your User class should have the same fields of your sent JSON object.
See here:

You did not tell spring how to bind the name and email parameters from the request. For example, by adding a #RequestParam:
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestParam String name,
#RequestParam String email) { ... }
name and email parameters will be populated from the query strings in the request. For instance, if you fire a request to /users/1?name=Josh&email=jb#ex.com, you will get this response:
User{id=1, name='Josh', email='jb#ex.com'}
In order to gain more insight about defining handler methods, check out the spring documentation.

Related

Spring security role-based authorisation best practices

So I basically have a controller method with a PreAuthorize annotation. By default the method will return all projects. The method signature also includes an optional query string (blank query means retrieve all records).
The issue is that if the logged in user is only supposed to view/manage his/her own records, the query string needs to include a filter in it such as "clientId:2".
Now to do that, I was thinking of using the Principal object to retrieve the logged in user and check if he/she is a client as well, then I update the query by adding the required filter to it.
I am just not sure if this is the best approach for this type of issues.
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
#RequestMapping(value = "/projects", method = RequestMethod.GET)
public ResponseEntity<RestResponse> list(Principal principal, #RequestParam(value = "query", required = false, defaultValue = "") String query) {
//If a client is logged in, he/she will have the MANAGE_OWN authority so will need to update the query string to include clientId:<logged-in-client-id>
I would rather move the #PreAuthorize to an application service.
class SomeApplicationService {
UserService userService;
SecurityService securityService;
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
public List<Project> getProjects(String clientId) {
User currentUser = userService.getLoggedInUser();
if(securityService.canManageAllProjects(currentUser))
//get all projects or projects of clientId
else if(securityService.canManageOwnProjects(currentUser))
//get own projects, ignore clientId
}
}

Forwarding to another spring controller that receive parameter from POST action

I have a request mapping for a controller let's say it's A, it receives post action and uses its post values as parameter, sometimes the parameters will be very long so that's the reason why it's POST not GET apart from the best practices and security;
RequestMapping(value = "/reports/performance/indicator/{indicatorType}", method = RequestMethod.POST)
public String generatePerformanceReportsIndicator(ModelMap map,HttpServletResponse response, #PathVariable("indicatorType") Long indicatorType,
#RequestParam(value = "siteIds", required = false) List<Long> siteIds,
#RequestParam(value = "timeframeIds", required = false) List<String> timeframeIds,
#RequestParam(value = "showTarget", required = false) String showTarget,Locale locale) {
And then it turned out that in another spring controller I need to forward the request to the first one.
The problem is how I can add post parameters to the request before forwarding it to the first request mapping? is that healthy to say for example?
new FirstController().generatePerformanceReportsIndicator(....);
Given that:
I don't want the first request mapping to use get instead of post of the mentioned reasons.
I don't want to write redundant code by creating another method that extract the parameters as attribute from the model map.
You should not manually call other controllers! What you can do is redirect to them with RedirectAttributes like:
#RequestMapping(value = "/doctor/doEditPatientDetails", method = RequestMethod.POST)
public String editPatientDetails(Model model, #ModelAttribute(value = "user") #Valid User user,
BindingResult result, RedirectAttributes attr, Principal principal) {
if (null != principal) {
if (result.hasErrors()) {
attr.addFlashAttribute("org.springframework.validation.BindingResult.user", result);
attr.addFlashAttribute("user", user);
attr.addAttribute("id", user.getId());
return "redirect:/doctor/editPatient/{id}";
}
}
....
return "redirect:/doctor/patients";
}
#RequestMapping(value = "/doctor/editPatient/{id}", method = RequestMethod.GET)
public String showEditPatient(Model model, #ModelAttribute("id") String id, Principal principal) {
if (null != principal) {
//here you can access the model and do what everything you want with the params.
if (!model.containsAttribute("user")) {
model.addAttribute("user", user);
}
....
}
return "/doctor/editPatient";
}
Note that, to redirect to a link like "redirect:/doctor/editPatient/{id}" you have to add the id in RedirectAttributes. Also not that there are many ways you can achive the same functionality like HttpServletRequest

How Spring controller handles parameter implicitly?

I am a newbie on Spring framework and maybe this is an easy question.
I have a link as follows and attempt Spring controller handles the value"201610061023" of this link.However,my code did not work.
I know this value can be attached as a parameter or pathvariable in path but I just curious can I pass this value implicitly?
Thank you very much.
201610061023
#RequestMapping(value = "/Order")
public String requestHandlingMethod(#ModelAttribute("test") String name, HttpServletRequest request) {
return "nextpage";
}
Spring will not handle the title of the link simply because the title of the link will not be sent by the browser. To send it you can either:
add the value as parameter: 201610061023
add the value as path variable: 201610061023
add a JavaScript that will copy the title onClick into the href or send the generated URL with document.location. This can be automated, but it's pretty uncommon.
Your a-tag is wrong, you need to submit the id, there is no implicit way to submit the link-text (except a lot of java script code)!
201610061023
#RequestMapping(value = "/Order/{orderId}")
public String requestHandlingMethod(#PathVariable("orderId") long orderId, #ModelAttribute("test") String name, HttpServletRequest request) {
return "nextpage";
}
or
201610061023
#RequestMapping(value = "/Order")
public String requestHandlingMethod(#RequestParam("orderId") long orderId, #ModelAttribute("test") String name, HttpServletRequest request) {
return "nextpage";
}
See #RequestParam vs #PathVariable for the difference between this two approaches

#RequestParam in Spring MVC handling optional parameters

Is it possible for a Spring controller to handle both kind of requests?
1) http://localhost:8080/submit/id/ID123432?logout=true
2) http://localhost:8080/submit/id/ID123432?name=sam&password=543432
If I define a single controller of the kind:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout", required = false) String logout,
#RequestParam("name") String username,
#RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
the HTTP request with "logout" is not accepted.
If I define two controllers to handle each request separately, Spring complains with the exception "There is already 'Controller' bean method ... mapped".
Before Java 8 and Spring 5 (but works with Java 8+ and Spring 5+ too)
You need to give required = false for name and password request parameters as well. That's because, when you provide just the logout parameter, it actually expects for name and password because they are still "implicitly" mandatory.
It worked when you just gave name and password because logout wasn't a mandatory parameter thanks to required = false already given for logout.
Update for Java 8 and Spring 5 (and above)
You can now use the Optional class from Java 8 onwards to make the parameters optional.
#RequestMapping (value = "/path", method = RequestMethod.GET)
public String handleRequest(#RequestParam("paramName") Optional<String> variableName) {
String paramValue = variableName.orElse("");
// use the paramValue
}
As part of Spring 4.1.1 onwards you now have full support of Java 8 Optional (original ticket) therefore in your example both requests will go via your single mapping endpoint as long as you replace required=false with Optional for your 3 params logout, name, password:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout") Optional<String> logout,
#RequestParam("name") Optional<String> username,
#RequestParam("password") Optional<String> password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
Create 2 methods which handle the cases. You can instruct the #RequestMapping annotation to take into account certain parameters whilst mapping the request. That way you can nicely split this into 2 methods.
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"logout"})
public String handleLogout(#PathVariable("id") String id,
#RequestParam("logout") String logout) { ... }
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"name", "password"})
public String handleLogin(#PathVariable("id") String id, #RequestParam("name")
String username, #RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model, BindingResult errors)
throws LoginException {...}
In case someone is looking for mapping Optional parameters with Pojo, same can be done like below.
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
LoginRequest loginRequest,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
#Data
#NoArgsConstructor
//#AllArgsConstructor - Don't use this
public class LoginRequest {
private Optional<String> logout = Optional.empty();
private Optional<String> username = Optional.empty();
private Optional<String> password = Optional.empty();
}
Note: Do not use #AllArgsConstructor on POJO else it will initialize the fields as null.

Overloading a spring controller method with the same request mapping

I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.

Categories

Resources