Hibernate #Valid strange behaivor in SpringMVC - java

First, I'm very new to this, sorry if this question is dumb. I have two controllers with class request mappings and form to validate:
#Controller
#RequestMapping("/creditcard")
public class CreditCardController {
#Autowired
//repositories
#RequestMapping(value = "/addnewcard", method = RequestMethod.POST)
public String addNew(
#ModelAttribute("newCard") #Valid CreditCard creditCard,
BindingResult bindingResult, Principal principal, Model model) {
if (bindingResult.hasErrors()) {
//render error view
}
creditCardService.registerNew(creditCard, principal.getName());
return "redirect:/account";
}
Another
#Controller
#RequestMapping("/account")
public class AccountController {
#Autowired
//repo
#RequestMapping("")
public String showUserProfile(Principal principal, Model model) {
String username = principal.getName();
User user = userService.findByUsername(username);
Account account = user.getAccount();
List<Payment> payers = paymentService
.getAllPaymentsForPayerAccount(account.getId());
model.addAttribute("user", user);
model.addAttribute("account", account);
model.addAttribute("payers", payers);
return "userprofile";
}
}
Form on userprofile.jsp
<form:form cssClass="form-horizontal" modelAttribute="newCard" action="creditcard/addnewcard">
........
</form:form>
And all this works without #Valid. When I add #Valid it works fine, when validation fails ( shows error view with messages), but when it succeeds I get 404 error due to incorrect URI - http://localhost:8080/PaymentSystem/creditcard/creditcard/addnewcard. Here one /creditcard is extra, #Valid somehow adds this to my URI. I found to ways for me to solve this:
1) I moved addNew card method to 'AccountController'
2) I just removed #RequestMapping("/creditcard")
But still I do not found any explanation of such behaviour. Any idea?

I found 3rd way to solve this issue, most suitable in this situation. I just added ${pageContext.request.contextPath} to my action param like this:
<form:form cssClass="form-horizontal" modelAttribute="newCard" action="${pageContext.request.contextPath}/creditcard/addnewcard">
........
</form:form>
But still it don't know the reason of that kind of #Valid behaviour.

Related

How to use #ModelAttributes and #SessionAttribute in spring MVC

I am doing a simple spring MVC project. There are three jsp pages page 1, page 2 and page 3. In page 1 and page 2 I have user form and I want to print two user's details in page 3. I think it could be solved using spring mvc annotation #ModelAttribute and #SessionAttribute but I don't know how to use them.
If anyone have any idea about my problem, please help me with a simple example
It's documented in Spring Framework's documentation:
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-sessionattrib
#Controller
#SessionAttributes(value = {"user1", "user2"})
public class MyController {
// ...
#GetMapping("/page1")
public String page1(ModelMap model){
model.put("user", new User());
return "page1";
}
#PostMapping("/page1")
public String page1Post(#ModelAttribute("user") User user, ModelMap model{
model.put("user1", user);
model.put("user", new User());
return "page2";
}
#PostMapping("/page2")
public String page2Post(#ModelAttribute("user") User user, ModelMap model{
model.put("user2", user);
return "page3";
}
}
With this user1 and user2 will be available in your page3

Spring mvc miss id of dependent collection when combine form object from jsp

I have following controller to return view:
#RequestMapping(value = "/admin/adminUsers", method = RequestMethod.GET)
public String adminUsers(ModelMap model, HttpSession session) {
Set<TerminalAdmin> users = terminalAdminService.getAllAdmins();
session.setAttribute("users", users);
model.addAttribute("adminRoles", terminalAdminService.findAllAdminRoles());
model.addAttribute("terminalAdmin", new TerminalAdmin());
model.addAttribute("generatedPassword", PasswordUpdateStatus.generatePassword());
return "admin/adminUsers";
}
terminalAdminService.findAllAdminRoles()
returns collection with ids:
On jsp I render it like this:
<form:form modelAttribute="terminalAdmin" action="/admin/addNewAdmin">
...
<form:checkboxes items="${adminRoles}" path="adminRoles"/>
...
</form:form>
I have the follwing controller to accept this object:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#ModelAttribute #Valid TerminalAdmin terminalAdmin...){
....
}
In debug I see that terminalAdmin comes with adminRoles without ids.
How to fix this?
P.S.
It is continue of Dependent collection duplicates when I save entity
i prefer to use Converters, because for me it's cleaner
you should have something like the following:
public class StringToAdminRoleConverter implements Converter<String, AdminRole> {
#Autowired
TerminalAdminService terminalAdminService;
#Override
public AdminRole convert(String role) {
return terminalAdminService.findRoleByName(role);
}
}

Spring REST service to consume and produce both HTML form POST and AJAX/JSON in a single method

I'm trying to teach myself Spring by creating a very simple web application. I have a class to create "Note" objects:
#Controller
#RequestMapping(value = "/notes")
public class NoteRestController {
#Autowired
private MappingJackson2JsonView jsonView;
[...]
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST, consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ModelAndView create(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
ModelAndView mav;
// how can I test the request source?
if (<requesting from HTML FORM>) {
// return jsonView
mav = new ModelAndView(jsonView);
} else {
// return JSP view
mav = new ModelAndView("note", "model", model);
}
model.addAttribute("note", note);
if (result.hasErrors()) {
model.addAttribute("errors", result.getAllErrors());
// on error, redirect back to note page with form
// return new ModelAndView("note/note", "model", model);
return mav;
}
note.setId(daoService.createNote(note));
return mav;
}
}
I would like to be able to use a single method (like the above) to handle requests from both an AJAX post AND a HTML form post. If triggered by AJAX I would like to return JSON (with validation errors if present), and if it is triggered by a HTML form, I would like to return to the JSP using the form taglib
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
and show validation errors next to input fields using e.g.
<form:errors path="title" cssClass="errorMessage"></form:errors>
Is this possible, or should I be creating two controllers; one for the REST/JSON, and one for HTML/form? Maybe there is something I can pass into the method that can be interrogated to determibne the request source, but I can't see it right now.
What would be the "best practice" in this case?
EDIT 1:
Trying answer from #ring-bearer first as it allows for the same URL pattern, but having issues.
Using methods:
// used to handle JSON/XML
#RequestMapping(method = RequestMethod.POST, produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public #ResponseBody Note create(
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
// used to handle form view
#RequestMapping(method = RequestMethod.POST)
public ModelAndView createForView(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
Interestingly, the HTML form submission, still gets handled by create() and not createForView(). After looking at the form submission request headers, I see that this Accept header:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
By adding produces = "text/html" to #RequestMapping on createForView(), all 3 scenarios work (form, AJAX/JSON, AJAX/XML).
Is this normal, or am I still missing something?
This can be achieved using "content negotiation". Spring MVC needs to be enabled for content negotiation using a "contentNegotiationManager" definition. It can be set up using Java or XML configuration. The configuration will centrally manage media type mappings(json, xml etc). Once that is set up, a controller class can be built to cater to both JSON and View(HTML). Below is a generic example(uncompiled), it should be easy to refactor your class to similar structure to avoid violation of DRY.
#Controller
class ReportController{
//1- Method for JSON/marshalling types(XML)
#RequestMapping(value="/report", produces={"application/xml", "application/json"})
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<ReportPara> generateReport(Principal principal) {
return reportService.generateReport(principal);
}
//2- For View technologies ( Eg:JSP HTML)
#RequestMapping("/report")
public String generateReportForView(Model model, Principal principal) {
model.addAttribute( generateReport(principal) );
// Return the view to use for rendering the response
return ¨reports/main¨;
}
}
Which of the two #RequestMapping methods will execute? It is determined by content negotiation definition. Eg: URLs such as report.xml or report.json map to the first method, any other URLs ending in report.anything map to the second.
The following will be easier to maintain:
#Controller
class NoteController {
#Autowired NoteService service;
#RequestMapping(method = RequestMethod.POST, value = "/note")
public ModelAndView createFromForm(#ModelAttribute #Valid Note note, BindingResult result) {
return new ModelAndView("note", create(note));
}
#RequestMapping(method = RequestMethod.POST, value = "/api/note")
#ResponseBody
public Note createFromApi(#RequestBody Note note) {
return create(note);
}
private Note create(Note note) {
return service.create(note);
}
}

Spring MVC 3.0 Rest problem

I'm trying out Spring MVC 3.0 for the first time and like to make it RESTfull.
This is my controller:
#Controller
#RequestMapping(value = "/product")
#SessionAttributes("product")
public class ProductController {
#Autowired
private ProductService productService;
public void setProductValidator(ProductValidator productValidator, ProductService productService) {
this.productService = productService;
}
#RequestMapping(method = RequestMethod.GET)
public Product create() {
//model.addAttribute(new Product());
return new Product();
}
#RequestMapping(method = RequestMethod.POST)
public String create(#Valid Product product, BindingResult result) {
if (result.hasErrors()) {
return "product/create";
}
productService.add(product);
return "redirect:/product/show/" + product.getId();
}
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public Product show(#PathVariable int id) {
Product product = productService.getProductWithID(id);
if (product == null) {
//throw new ResourceNotFoundException(id);
}
return product;
}
#RequestMapping(method = RequestMethod.GET)
public List<Product> list()
{
return productService.getProducts();
}
}
I have 2 questions about this.
I'm a believer in Convention over Configuration and therefor my views are in jsp/product/ folder and are called create.jsp , list.jsp and show.jsp this works relatively well until I add the #PathVariable attribute. When I hit root/product/show/1 I get the following error:
../jsp/product/show/1.jsp" not found how do I tell this method to use the show.jsp view ?
If I don't add the RequestMapping on class level my show method will be mapped to root/show instead of root/owner/show how do I solve this ? I'd like to avoid using the class level RequestMapping.
add your 'product' to Model and return a String /product/show instead of Product. In your show.jsp, you can access the product object form pageContext
Check out the section in the manual about "Supported handler method arguments and return types".
Basically, when your #RequestMapping method returns just an object, then Spring uses this as a single model attribute, and, I'm guessing, attempts to use the request URL as the basis for the view name.
The easiest way to return the view and data you want from the same method is probably to just have the method return a ModelAndView, so you can explicitly specify the viewName and the model data.

Spring 3.0 set and get session attribute

I want to read a domain object (UserVO) from session scope.
I am setting the UserVO in a controller called WelcomeController
#Controller
#RequestMapping("/welcome.htm")
public class WelcomeController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(BindingResult result, SessionStatus status,HttpSession session){
User user = loginService.loginUser(loginCredentials);
session.setAttribute("user", user);
return "loginSuccess";
}
}
I am able to use the object in jsp pages <h1>${user.userDetails.firstName}</h1>
But I am not able to read the value from another Controller,
I am trying to read the session attribute as follows:
#Controller
public class InspectionTypeController {
#RequestMapping(value="/addInspectionType.htm", method = RequestMethod.POST )
public String addInspectionType(InspectionType inspectionType, HttpSession session)
{
User user = (User) session.getAttribute("user");
System.out.println("User: "+ user.getUserDetails().getFirstName);
}
}
The code you've shown should work - the HttpSession is shared between the controllers, and you're using the same attribute name. Thus something else is going wrong that you're not showing us.
However, regardless of whether or not it works, Spring provides a more elegant approach to keeping your model objects in the session, using the #SessionAttribute annotation (see docs).
For example (I haven't tested this, but it gives you the idea):
#Controller
#RequestMapping("/welcome.htm")
#SessionAttributes({"user"})
public class WelcomeController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(ModelMap modelMap){
User user = loginService.loginUser(loginCredentials);
modelMap.addtAttribute(user);
return "loginSuccess";
}
}
and then
#Controller
#SessionAttributes({"user"})
public class InspectionTypeController {
#RequestMapping(value="/addInspectionType.htm", method = RequestMethod.POST )
public void addInspectionType(InspectionType inspectionType, #ModelAttribute User user) {
System.out.println("User: "+ user.getUserDetails().getFirstName);
}
}
However, if your original code isn't working, then this won't work either, since something else is wrong with your session.
#SessionAttributes works only in context of particular handler, so attribute set in WelcomeController will be visible only in this controller.
Use a parent class to inherit all the controllers and use SessionAttributes over there. Just that this class should be in the package scan of mvc.
May be you have not set your UserVO as Serializable.

Categories