SpringBoot/MVC & Thymleaf form validation on POST with URL parameters - validation

I have a form and validation works. The problem comes in when a url parameter was added. The url parameter is a token and is required. So this is what my controller looks like:
#RequestMapping(value = "/resetpassword", method = RequestMethod.GET)
public String showResetForm(ResetPassword resetPassword, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
model.addAttribute("token", token);
return "resetpassword";
}
#RequestMapping(value = "/resetpassword", method = RequestMethod.POST)
public String setPwd(#ModelAttribute("resetPassword") #Valid ResetPassword resetPassword,// RedirectAttributes reDirectAttr,
BindingResult bindingResult, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
if (bindingResult.hasErrors()) {
//reDirectAttr.addFlashAttribute("org.springframework.validation.BindingResult.resetPassword",bindingResult);
//reDirectAttr.addFlashAttribute("resetPassword",resetPassword);
return "resetpassword?token="+token;
}
else {
if (token == null) {
// TODO: no token, what to do here??
return "redirect:/resetpassword?token=\"\"&msg=notoken";
}
ResetPasswordResponseDto response = super.resetUserPassword(
resetPassword.getUname(), resetPassword.getPassword(),
token);
if (response.getPasswordResetResult() == PasswordResetResult.SUCCESSFUL) {
// TODO: it worked, what now?
return "redirect:/login";
} else if (response.getPasswordResetResult() == PasswordResetResult.INVALID_TOKEN) {
// TODO: bad token
return "redirect:/resetpassword?token="+token+"&msg=badtoken";
} else if (response.getPasswordResetResult() == PasswordResetResult.OUT_OF_POLICY_PW) {
// TODO: out of policy pw
return "redirect:/resetpassword?token="+token+"&msg=outofpolicy";
} else if (response.getPasswordResetResult() == PasswordResetResult.LDAP_FAILURE) {
// TODO: other failure
return "redirect:/resetpassword?token="+token+"&msg=error";
}
}
return "redirect:/resetpassword?token="+token+"&msg=error";
//return new RedirectView("resetpassword?token=\"\"&msg=notoken");
}
So I tried a bunch of things but nothing seems to work. Here is what I would like to happen when the view is requested /resetpassword?token=1232453 the view is displayed. Then if the form has errors the url parameter persists in the url and the form displays the errors. Right now I get an error saying that the template cannot be resolved. Ok fair enough, so I tried doing a redirect instead
return "redirect:/resetpassword?token="+token;
and that seems to work, however the URL parameter is lost and the view loses the bindingResult errors. In the code, I posted I also tried FlashAttributes but I just get an error "Validation failed for object='resetPassword'. Error count: 4" which is correct but I need it to show the form and the errors I coded with Thymeleaf. Any help or suggestions would be great!
Resources I have looked at:
Spring - Redirect after POST (even with validation errors)
&
SpringMVC controller: how to stay on page if form validation error occurs

Have you tried returning a ModelAndView instead of just the redirect string? Attributes on the model will be available as URL query parameters.
ModelAndView redirect = new ModelAndView("redirect:/resetpassword");
redirect.addObject("token", token);
redirect.addObject("msg", "error");
return redirect;

Related

When returning a json, how can you tell using Spring that you want to go to a certain url?

In my code, I am returning a path in String form(/successPassword or /systemError). Basically this tells Spring to go to the path /successPassword or /systemError, /successPassword goes to successPassword.html and /systemError goes to systemError.html. These html files are located in src/main/resources/templates.
#RequestMapping(value = "/user/new_password", method = RequestMethod.POST)
public String createNewPassword(#RequestParam(value = "newpassword", required = false) String password,
#RequestParam(value = "hash") String hash, Model model) {
LOG.info("set new password using hash " + hash);
LOG.info("new password " + password);
boolean hashValid = passwordService.isHashValid(hash);
if (hashValid) {
ValidateMessage message = passwordService.validateNewPassword(password);
if (!message.isError()) {
Account account = passwordService.getAccount(hash);
passwordResetService.saveNewPassword(password, account);
model.addAttribute("account", account);
return "/successPassword";
} else {
LOG.info("Password not complex enough.");
return "/systemError";
}
} else {
LOG.info("Invalid hash.");
return "/systemError";
}
}
Now it looks like I need to return a json object instead. I know that I have to annotate the method with #ResponseBody instead, but my question is how would i package now the object that I will return so that whoever receives it will know to which path it needs to go to? My html codes are using Thymeleaf (no javascript whatsoever).

Spring MVC Ajax Post 400 Error (Bad Request)

So I'm using Spring MVC and I'm trying to do an ajax post to add a comment to a Post entity, like a typical social network. And I'm getting an error in the Chrome Developer's Tool's that says this Failed to load resource: the server responded with a status of 400 (Bad Request). I'm thinking that might mean something is going wrong in my controller, however the way this is set up, it's not letting me check it out in debug mode.
I'll show you guys all the pieces of code that work together so you guys can get a better understanding of my problem.
So here's my Ajax, and everything is running through, and sending a "there was an error" message, so It's at least running through the code and reaching the controller.
Also the CDATA stuff is for Thymeleaf.
<script th:inline="javascript">
/*<![CDATA[*/
var postById = /*[[${postById.id}]]*/'1';
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#submit").on("click", function(ev) {
ev.preventDefault();
$.ajax({
url : "newComment",
type : "post",
data : {
"postById" : postById,
"newComment" : $("#newComment").val()
},
success : function(data) {
console.log(data);
location.reload();
},
error : function() {
console.log("There was an error");
//location.reload();
}
});
});
});
/*]]>*/
</script>
Here's my Controller Get Method
#RequestMapping(value="viewCourse/post/{postId}", method=RequestMethod.GET)
public ModelAndView postViewGet (#RequestParam(value = "pageSize", required = false) Integer pageSize,
#RequestParam(value = "page", required = false) Integer page, #PathVariable Long postId) {
ModelAndView modelAndView = new ModelAndView("post");
{
// Evaluate page size. If requested parameter is null, return initial
// page size
int evalPageSize = pageSize == null ? INITIAL_PAGE_SIZE : pageSize;
// Evaluate page. If requested parameter is null or less than 0 (to
// prevent exception), return initial size. Otherwise, return value of
// param. decreased by 1.
int evalPage = (page == null || page < 1) ? INITIAL_PAGE : page - 1;
//StudySet studySet = studySetRepo.findOne(studySetId);
//List <Row> rows = studySet.getRows();
//Set<Row> rowSet = new TreeSet<Row>(rows);
Post postById = postRepo.findOne(postId);
Comment comment = new Comment();
Page<Comment> postComments = commentService.findByPostOrderByIdDesc((postById), new PageRequest(evalPage, evalPageSize));
Pager pager = new Pager(postComments.getTotalPages(), postComments.getNumber(), BUTTONS_TO_SHOW);
modelAndView.addObject("postId", postId);
modelAndView.addObject("postById", postById);
modelAndView.addObject("postComments", postComments);
modelAndView.addObject("comment", comment);
modelAndView.addObject("selectedPageSize", evalPageSize);
modelAndView.addObject("pageSizes", PAGE_SIZES);
modelAndView.addObject("pager", pager);
return modelAndView;
}
}
Here's my Controller Post Method
#RequestMapping(value="viewCourse/post/newComment", method=RequestMethod.POST)
public #ResponseBody Post newComment (#Valid #RequestParam Long postId, #RequestParam String newComment, ModelMap model, #AuthenticationPrincipal User user)
{
Post post = postRepo.findOne(postId);
Comment comment = new Comment();
comment.setComment(newComment);
comment.setPost(post);
comment.setDate(LocalDate.now());
comment.setTime(LocalTime.now());
comment.setDateTime(LocalDateTime.now());
comment.setUser(user);
user.getComments().add(comment);
post.getComments().add(comment);
commentRepo.save(comment);
Post savedPost = postRepo.save(post);
return savedPost;
}
Also I have some annotations in the entity objects, that could have something to do with it.
Here's my User Entity
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference
public Set<Comment> getComments() {
return comments;
}
public void setComments(Set<Comment> comments) {
this.comments = comments;
}
Here's my Comment entity
#ManyToOne
#JsonBackReference
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
Also here's a picture of my console in the Chrome Developer Tools, so you guys can see exactly what it's showing me.
If anyone can see where I'm going wrong and point me in the right direction, that would be great, thanks in advance.
Also if you guys need to see any other code, just let me know.
My problem was in the Ajax, I was looking for the postById.id, when I should have been using just the postId that I added to the model in the Get method in the controller.

Modifying SwaggerUI to remove required PathParam

I have a requirement to use optional path parameters. So made it like below;
#ApiOperation(httpMethod = "GET", value = "Get User Details With Optional Path Parameters", notes = "Output depends on values provided")
#ApiResponses(value = {
#ApiResponse(code = 404, message = "We do not unserstand what you mean"),
#ApiResponse(code = 400, message = "You are not requesting like a BOSS.") })
#RequestMapping(value = { "/getuser/userid/{userid}",
"/getuser",
"/getuser/userid/{userid}/alias/{alias}", "getuser/alias/{alias}" }, method = RequestMethod.GET, produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
#ResponseStatus(HttpStatus.OK)
private UserSchema getUserDetails(
#PathVariable Optional<String> userid,
#PathVariable Optional<String> alias) {
Users user = null;
UserSchema returningSchema = buildDefaultSchema();
if (alias.isPresent()) {
//Get The Value
} else {
//Try Get Other Value and do stuff etc.
}
//Similar for userid
try {
//Get User Data From DB
user = dao.getUserData(userid,alias);
//Bind data to returning schema
} catch (Exception ex) {
Log.error(getClass().getName(), ex);
returningSchema.setResponseText("Something is Wrong");
}
return returningSchema;
}
But with swagger, its not allowing to make a request, as PathVariables are required type. I do not know much javascript. Tried this solution to modify the swagger-ui.js, but seem to get lost in the huge file and cannot find the portion mentioned.
I use the latest Swagger-UI version. Is it possible i can make the request with optional path variables and the correct path should be shown in swagger?
NOTE: I know swagger spec does not allow optional path variables. But i want to change this in my app only.
Thanks.
Yes, you can definitely fork swagger-js and swagger-ui to support this. Take a look at the Operation.prototype.getMissingParams in operation.js.

HTTP Status 400 The request sent by the client was syntactically incorrect

This is my controller
#RequestMapping("/offercreated")
public String offerCreated(#Valid Offer offer, Model model, BindingResult result) {
if (result.hasErrors()) {
return "createoffer";
} else {
System.out.println("form validated");
return "offercreated";
}
and my bean is
#Size(min = 5, max = 45)
private String name;
The form is validated when i give the name of between 5 and 45 characters. But when the form is not validated I am getting 400 status error report. I dont know why i am getting the error. Please need help here
BindingResult parameter must follow the parameter being validated immediately. It's described here: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html
org.springframework.validation.Errors / org.springframework.validation.BindingResult validation results for a preceding command or form object (the immediately preceding method argument).
Wow when i change the controller parameters to Model and then Offer its working !!
#RequestMapping("/offercreated")
public String offerCreated(Model model, #Valid Offer offer, BindingResult result) {
if (result.hasErrors()) {
return "createoffer";
} else {
System.out.println("form validated");
return "offercreated";
}
can someone explain why that is ? i am so confused !

How to do a redirect in Spring MVC controller?

I am trying use a PRG (Post-Redirect-Get) pattern in one of my Spring MVC controller. The controller collects user data from an HTML form, does some processing, stores data in DB and then shows a JSP page. After saving data a redirect should happen and then the JSP page should be displayed.
I also tried to prepend "redirect:" in front of the VIEW_NAME but I get 404 then.
Please guide.
CartPageController.java
#Controller
#RequestMapping("/cartPageController.do")
public class CartPageController {
private static final Logger LOG = Logger.getLogger(CartPageController.class);
private static final String VIEW_NAME = "cart";
#Autowired
protected MasterDao masterDao;
#RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView processRequest(HttpServletRequest request, HttpServletResponse response) {
LOG.debug("Into the CartPageController...");
HttpSession session = request.getSession();
ModelAndView mav = new ModelAndView();
//create Cart object and store it in session
Cart cart = null;
if (session.getAttribute("cart") != null) {
cart = (Cart) session.getAttribute("cart");
} else {
cart = createCart();
session.setAttribute("cart", cart);
}
LOG.debug("cart = " + cart);
//determine the cart operation
String btnAddToCart = GenericUtils.nullToEmptyString(request.getParameter("btnAddToCart"));
String removeProduct = GenericUtils.nullToEmptyString(request.getParameter("removeProduct"));
String updateProduct = GenericUtils.nullToEmptyString(request.getParameter("updateProduct"));
LOG.debug("btnAddToCart = " + btnAddToCart);
LOG.debug("removeProduct = " + removeProduct);
LOG.debug("updateProduct = " + updateProduct);
if (btnAddToCart.length() > 0) {
addToCart(request, cart);
} else if (removeProduct.length() > 0) {
removeProduct(request, cart);
} else if (updateProduct.length() > 0) {
updateCart(request, cart);
}
//TODO: Should use PRG pattern here
//TRIED TO APPEND "redirect:" here but does not work, gives me 404
mav.setViewName(VIEW_NAME);
return mav;
}
//some more code below here
}
You should redirect to url not a view name. as Spring doc says:
While
the use of RedirectView works fine, if the controller itself creates
the RedirectView, there is no avoiding the fact that the controller is
aware that a redirection is happening. This is really suboptimal and
couples things too tightly. The controller should not really care
about how the response gets handled. In general it should operate only
in terms of view names that have been injected into it.
The special redirect: prefix allows you to accomplish this. If a view
name is returned that has the prefix redirect:, the
UrlBasedViewResolver (and all subclasses) will recognize this as a
special indication that a redirect is needed. The rest of the view
name will be treated as the redirect URL.
you can try
return new ModelAndView(new RedirectView("/page"));
You can't just prepend "redirect:" to your view name which results in redirect:cart and then obviously 404, what you have to do for redirect is to specify the redirect path for e.g.:
redirect:/cart.htm
This should explain it more in details.
You can do this way
redirect:cart.do and have requestmapping in controller which will return cart view.
Hope this will work.

Resources