Conditional validation in spring mvc - spring

Now I have following controller method signature:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(
#RequestParam("companyName") String companyName,
#RequestParam("email") String email,
HttpSession session, Principal principal) throws Exception {...}
I need to add validation for input parameters.
Now I am going to create object like this:
class MyDto{
#NotEmpty
String companyName;
#Email // should be checked only if principal == null
String email;
}
and I am going to write something like this:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams( MyDto myDto, Principal principal) {
if(principal == null){
validateOnlyCompanyName();
}else{
validateAllFields();
}
//add data to model
//return view with validation errors if exists.
}
can you help to achieve my expectations?

That's not the way Spring MVC validations work. The validator will validate all the fields and will put its results in a BindingResult object.
But then, it's up to you to do a special processing when principal is null and in that case look as the validation of field companyName :
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(#ModelAttribute MyDto myDto, BindingResult result,
Principal principal) {
if(principal == null){
if (result.hasFieldErrors("companyName")) {
// ... process errors on companyName Fields
}
}else{
if (result.hasErrors()) { // test any error
// ... process any field error
}
}
//add data to model
//return view with validation errors if exists.
}

Related

How to redirect from one controller method to another method with model data in spring boot application

In this example I am trying to redirect from handleSaveContact() controller method from contactSuccessMsg() controller method but after transfer I need to display success or update or failure msg to the UI which is only possible if I transfer Model data from 1st method to 2nd.
Could any one please suggest me how I can trasfer model data from one controller method to another controller method.
#GetMapping(value={"/", "/loadForm"})
public String loadContactForm(Model model) {
model.addAttribute("contact", new Contact());
return "index";
}
#PostMapping("/saveContact")
public String handleSaveContact(Contact contact, Model model) {
String msgTxt = null;
if(contact.getContactId()==null) {
msgTxt = "Contact Saved Successfully..!!";
}else {
msgTxt = "Contact Updated Successfully..!!";
}
contact.setIsActive("Y");
boolean isSaved = contactService.saveContact(contact);
if(isSaved) {
model.addAttribute("successMsg", msgTxt);
}else {
model.addAttribute("errorMsg", "Failed To Save Contact..!!");
}
return "redirect:/contactSuccessMsg";
}
/**
* To resolve Double Posting problem, redirecting the post req method to get request.
* #param contact
* #param model
* #return
*/
#GetMapping(value="/contactSuccessMsg")
public String contactSuccessMsg(Model model) {
model.addAttribute("contact", new Contact());
return "index";
}
I used Spring 3.2.3
1.)Added RedirectAttributes redirectAttributes to the method parameter list in controller1.
public String controlMapping1(
#ModelAttribute("mapping1Form") final Object mapping1FormObject,
final BindingResult mapping1BindingResult,
final Model model,
final RedirectAttributes redirectAttributes)
Inside the method added code to add flash attribute to redirectAttributes
redirectAttributes.addFlashAttribute("mapping1Form", mapping1FormObject);
Then, in the second contoller use method parameter annotated with #ModelAttribute to access redirect Attributes :
#ModelAttribute("mapping1Form") final Object mapping1FormObject
Here is the sample code from Controller 1:
#RequestMapping(value = { "/mapping1" }, method = RequestMethod.POST)
public String controlMapping1(
#ModelAttribute("mapping1Form") final Object mapping1FormObject,
final BindingResult mapping1BindingResult,
final Model model,
final RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("mapping1Form", mapping1FormObject);
return "redirect:mapping2";
}
From Contoller 2:
#RequestMapping(value = "/mapping2", method = RequestMethod.GET)
public String controlMapping2(
#ModelAttribute("mapping1Form") final Object mapping1FormObject,
final BindingResult mapping1BindingResult,
final Model model) {
model.addAttribute("transformationForm", mapping1FormObject);
return "new/view";
}

How to get the username (email in my case) in Spring Security [UserDetails/String]

I would like to get the e-mail which is username in my application to set the user which send a message. I decided to use typical method i.e. principal and getUsername():
#PostMapping("/messages/{id}")
#ResponseStatus(HttpStatus.CREATED)
public MessageDTO addOneMessage(#RequestBody MessageRequest messageRequest, #PathVariable ("id") Long id) {
checkIfChannelExists(id);
String content = messageRequest.getContent();
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = ((UserDetails) principal).getUsername();
Employee author = employeeRepository.findByEmail(username).get();
Message message = new Message(content, author, id);
messageRepository.save(message);
return new MessageDTO(message);
}
And MessageRequest.java:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MessageRequest {
#NonNull
private String content;
}
But, in this way I still get:
"message": "java.lang.String cannot be cast to org.springframework.security.core.userdetails.UserDetails"
What is wrong in my implementation? To be more precise, I use Postman to test POST requests:
{
"content": "something"
}
If you only need to retrieve the username you can get it through Authentication ie.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
instead of typecasting to your class springcontext provide the almost all details about the user.
If you want the controller to get the user name to test.
please use this code.
//using authentication
#RequestMapping(value = "/name", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Authentication authentication) {
return authentication.name();
}
//using principal
#RequestMapping(value = "/name", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}

Spring REST Service Controller not being validate by #PathVariable and #Valid

#Controller
#EnableWebMvc
#Validated
public class ChildController extends ParentController<InterfaceController> implements InterfaceController{
#Override
#RequestMapping(value = "/map/{name}", produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
#ResponseStatus( HttpStatus.OK)
#ResponseBody
public List<Friends> getAllFriendsByName(
#Valid
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable("name") String name,
#RequestParam(value="pageSize", required=false) String pageSize,
#RequestParam(value="pageNumber", required=false) String pageNumber,
HttpServletRequest request) throws BasicException {
//Some logic over here;
return results;
}
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
Hi, I am trying to do pretty basic validation for a spring request parameter but it just doesn't seem to call the Exception handler, could someone point me into the right direction
P.S. I keep getting NoHandlerFoundException
Spring doesn't support #PathVariable to be validated using #Valid. However, you can do custom validation in your handler method or if you insist on using #Valid then write a custom editor, convert your path variable value to an object, use JSR 303 bean validation and then use #Valid on that object. That might actually work.
Edit:
Here's a third approach. You can actually trick spring to treat your path variable as a model attribute and then validate it.
1. Write a custom validator for your path variable
2. Construct a #ModelAttribute for your path variable and then use #Validator (yes not #Valid as it doesn't let you specify a validator) on that model attribute.
#Component
public class NameValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
String name = (String) target;
if(!StringUtils.isValidName(name)) {
errors.reject("name.invalid.format");
}
}
}
#RequestMapping(value = "/path/{name}", method = RequestMethod.GET)
public List<Friend> getAllFriendsByName(#ModelAttribute("name") #Validated(NameValidator.class) String name) {
// your code
return friends;
}
#ModelAttribute("name")
private String nameAsModelAttribute(#PathVariable String name) {
return name;
}

SpringMVC controller: how to stay on page if form validation error occurs

I have next working code in my SpringMVC controller:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerForm(Model model) {
model.addAttribute("registerInfo", new UserRegistrationForm());
}
#RequestMapping(value = "/reg", method = RequestMethod.POST)
public String create(
#Valid #ModelAttribute("registerInfo") UserRegistrationForm userRegistrationForm,
BindingResult result) {
if (result.hasErrors()) {
return "register";
}
userService.addUser(userRegistrationForm);
return "redirect:/";
}
In short create method try to validate UserRegistrationForm. If form has errors, it leaves user on the same page with filled form fields where error message will be shown.
Now I need to apply the same behaviour to another page, but here I have a problem:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.GET)
public String buyGet(HttpServletRequest request, Model model, #PathVariable long buyId) {
model.addAttribute("buyForm", new BuyForm());
return "/buy";
}
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
if (result.hasErrors()) {
return "/buy/" + buyId;
}
buyForm.setId(buyId);
buyService.buy(buyForm);
return "redirect:/show/" + buyId;
}
I faced with issue of dynamic url. Now if form has errors I should specify the same page template to stay on current page, but also I should pass buyId as a path variable. Where are a conflict in this two requirements. If I leave this code as is, I get an error (I'm using Thymeleaf as a template processor):
Error resolving template "/buy/3", template might not exist or might not be accessible by any of the configured Template Resolvers
I can write something like return "redirect:/buy/" + buyId, but in this case I lose all data and errors of form object.
What should I do to implement in buyPost method the same behaviour as in create method?
I tried the solution metioned in this post at this weekend, but it doesn't work for BindingResult.
The code below works but not perfect.
#ModelAttribute("command")
public PlaceOrderCommand command() {
return new PlaceOrderCommand();
}
#RequestMapping(value = "/placeOrder", method = RequestMethod.GET)
public String placeOrder(
#ModelAttribute("command") PlaceOrderCommand command,
ModelMap modelMap) {
modelMap.put(BindingResult.MODEL_KEY_PREFIX + "command",
modelMap.get("errors"));
return "placeOrder";
}
#RequestMapping(value = "/placeOrder", method = RequestMethod.POST)
public String placeOrder(
#Valid #ModelAttribute("command") PlaceOrderCommand command,
final BindingResult bindingResult, Model model,
final RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("errors", bindingResult);
//it doesn't work when passing this
//redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "command", bindingResult);
redirectAttributes.addFlashAttribute("command", command);
return "redirect:/booking/placeOrder";
}
......
}
*I'm using Hibernate Validator APIs to validate my beans. To preserve form data along with displaying error messages, you need to do these 3 things:
Annotate your bean (eg. #NotEmpty, #Pattern, #Length, #Email etc.)
Inside controller:
#Controller
public class RegistrationController {
#Autowired
private RegistrationService registrationService;
#RequestMapping(value="register.htm", method=RequestMethod.GET, params="new")
public String showRegistrationForm(Model model) {
if (!model.containsAttribute("employee")) {
model.addAttribute("employee", new Employee());
}
return "form/registration";
}
#RequestMapping(value="register.htm", method=RequestMethod.POST)
public String register(#Valid #ModelAttribute("employee") Employee employee, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.employee", bindingResult);
redirectAttributes.addFlashAttribute("employee", employee);
return "redirect:register.htm?new";
}
registrationService.save(employee);
return "workspace";
}
// ....
}
Update your view/jsp to hold error messages:
This article can surely be helpful.
You can change your POST implementation to this:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
buyForm.setId(buyId); // important to do this also in the error case, otherwise,
// if the validation fails multiple times it will not work.
if (result.hasErrors()) {
byForm.setId(buyId);
return "/buy/{buyId}";
}
buyService.buy(buyForm);
return "redirect:/show/{buyId}";
}
Optionally, you can also annotate the method with #PostMapping("/buy/{buyId}") if you use Spring 4.3 or higher.

create two method for same url pattern with different arguments

I have scenario where one url "serachUser" may come with two different value (request parameter) userId or UserName.
so for this I have created two methods
public String searchUserById(#RequestParam long userID, Model model)
public ModelAndView searchUserByName(#RequestParam String userName)
But i am getting Ambiguous mapping found exception. Can Spring handle this situation?
You can use the params parameter to filter by HTTP parameters. In your case it would be something like:
#RequestMapping(value = "/searchUser", params = "userID")
public String searchUserById(#RequestParam long userID, Model model) {
// ...
}
#RequestMapping(value = "/searchUser", params = "userName")
public ModelAndView searchUserByName(#RequestParam String userName) {
// ...
}
Any way incase of request param null is allowed if you don't pass any value it will be null then you can write your coad like:
#RequestMapping(value = "/searchUser", params = {"userID","userName"})
public String searchUserById(#RequestParam long userID,#RequestParam String
userName,
Model model) {
if(userID != null){
//..
}else{
// ...
}

Resources