How to handle repository exception in the controller - spring mvc - spring

i'm using spring boot and spring MVC. I'm creating a simple form(CRUD)
here is the code:
#Document
public class User
{
#Id
private ObjectId id;
#Indexed(unique=true)
#NotNull
#Size(min=2, max=30)
private String username;
#NotNull
#Size(min=2, max=30)
private String password;
private List<String> roles;
...
Controller:
#Controller
#RequestMapping("/admin")
public class AdminController {
...
/**
* NEW USER (POST)
*/
#RequestMapping(value = "new", method = RequestMethod.POST)
public ModelAndView newUser(#Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return new ModelAndView("/admin/new");
}
user.setRoles(Arrays.asList(Constants.ROLE_ADMIN));
ur.save(user);
return new ModelAndView("redirect:/admin");
}
/**
* NEW USER (VIEW)
*/
#RequestMapping(value = "new", method = RequestMethod.GET)
public ModelAndView newUser(User user) {
ModelAndView mv = new ModelAndView("admin/new");
return mv;
}
...
}
and the View:
<form name="new" th:action="#{/admin/new}" th:object="${user}" method="post">
<table>
<tr th:if="${error != null}">
<td colspan="4">
<span th:text="${error}"></span>
</td>
</tr>
<tr>
<td>Name:</td>
<td><input type="text" name="username" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></td>
</tr>
<tr>
<td><button type="submit">Create</button></td>
</tr>
</table>
</form>
and it works, if I put an username bigger than 30 character.
But if I got and exception from the repository, for example:
DuplicateKey from the mongodb repository, didn't work.
So i tried to put this code in the controller:
#ExceptionHandler(Exception.class)
public ModelAndView handleCustomException(Exception ex) {
ModelAndView model = new ModelAndView("admin/new");
model.addObject("error", ex.getMessage());
return model;
}
It handle all the exceptions, but in this moment I don't have the "User" or "BindingResult" and when it try to render gets this error:
2015-09-22 13:36:55.498 ERROR 6208 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('username')" (admin/new:21)] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
What I'm doing wrong?
How should I handle this kind of exception?
There is a way to send the USER to ExceptionHandler?
Thanks.

Because you don't have to return admin/new from the error handler. The view is expecting the User object that you are correctly populating in normal controllers. In case of an error this object simply isn't there. So, return another view from the error controller: ModelAndView model = new ModelAndView("admin/error"); for example.
Anyhow, this is not a solution. You need to catch exceptions. In this situation it is always better to have an intermediate layer called Service. In service you can catch any repository exceptions and decide what to do about them.

Related

How to post a list to controller in Thymeleaf

I am new to Thymeleaf,Maybe this is a simple question.Please help me.Thanks.
Controller code:
#Controller
public class TestController {
#GetMapping(value = "/")
public String testget(Map<String, Object> model) {
TestBean bean = new TestBean();
List<Person> list = new ArrayList<>();
for (int index = 1; index < 5; index++) {
Person p = new Person();
p.setId(index);
p.setName("name" + index);
list.add(p);
}
model.put("allList", "nothing");
bean.setList(list);
model.put("testbean", bean);
return "NewFile";
}
#PostMapping(value = "/")
public String testpost(Map<String, Object> model//
, #ModelAttribute(name = "testbean") TestBean bean) {
List<Person> list = bean.getList();
model.put("bean", bean);
model.put("allList", list.toString());
return "NewFile";
}}
simple mapper and a Person bean:
#Data
public class TestBean {
private List<Person> list;
}
#Data
public class Person {
private int id;
private String name;
}
HTML code :
<form action="#" th:action="#{/}" th:object="${testbean}" method="post">
<p th:text="'ALL : ' + ${allList}"></p>
<table>
<tr th:each="person : ${testbean.list}">
<td>Id:<input type="text" th:value="${person.id}"
/></td>
<td>name: <input type="text" th:value="${person.name}" /></td>
</tr>
</table>
<input type="submit" value="submit" />
</form>
I want put list to page and change properties to refresh.
but i don't know how to add th:field tag, I try to add
**th:field="*{testbean.list[__${index}__].id}"**
but it failed with :
Invalid property 'testbean' of bean class [com.TestBean]
UPDATE1
And i tried
th:field="*{list[__${index}__].id}"
I got a error Where I added th:field
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring4.processor.attr.SpringInputGeneralFieldAttrProcessor' (NewFile:14)] with root cause
java.lang.NumberFormatException: For input string: "null"
My questions is what i can do something that i can get a List in controller.
Invalid property 'testbean' of bean class [com.TestBean]
Use th:field="*{list[__${index}__].id} pattern without testbean definition. If you use * notation then it will reference to parent object.
java.lang.NumberFormatException: For input string: "null"
Add any variable like "rowStat" to the loop for keeping status index:
th:each="person,rowStat : ${testbean.list}"
and access it like th:field="*{list[__${rowStat.index}__].id}. So index will not be null and no problem converting the string to int.
This is how I do it on page that lists some search results - table with checkboxes. Code itself is stripped only to relevant lines. places.content is a collection of Place objects.
<form th:action="#{selected-places}" method="POST" >
<tr th:each="place,i : ${places.content}">
<td><input name="places" th:value="${place.id}" type="checkbox"></td>
</tr>
</form>
And the controller side
#PostMapping(value = "/selected-places", params = "action=delete")
public String deletePlaces(#RequestParam Place[] places, RedirectAttributesModelMap model) {
....
}
As you can see, despite fact that form is sending only list of IDs, spring will autohydrate model entities. You can always Place[] places to collection of integers and it still will work.

Replace only selected fields using binding

I'm building simple twitter clone in Spring MVC. I want to provide edit functionality to posted messages.
Message domain object looks like this (simplified)
public class Message {
long id;
String text;
Date date;
User user;
}
I created jps form
<form:form action="edit" method="post" modelAttribute="message">
<table>
<tr>
<td><label for="text">Message: </label></td>
<td><form:textarea path="text" id="text"/></td>
</tr>
<tr>
<td><input type="submit" name="commit" value="Save" /></td>
</tr>
</table>
</form:form>
and added those method in controller class
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String showEditMessage(#RequestParam long id, Model model) {
Message message = messageService.findMessage(id);
if (message == null) {
return "404";
}
model.addAttribute("message", message);
return "users/editMessage";
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
public String editMessage(#Valid #ModelAttribute Message message, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/users/editMessage";
}
messageService.updateMessage(message);
return "/users/editMessage";
}
The problem is that the Message received in editMessage() contains only text field. I assume that this is expected behaviour. Can it be configured to replace fields that are only in jsp form?
I know this is only one field and I could just use #RequestParam String message, but sooner or later I will face similar problem with more than just one field.
I also have side question.
Are attributes added in showEditMessage() are passed to editMessage() method? I tried to add "id" attribute in first method, but I couldn't retrive it using "#RequestParam long id" in second.
#SessionAttributes("message")
On top of controller class solved it.

Spring MVC: Error 400 The request sent was syntactically incorrect

In short, I have a Spring 3.1 MVC project and my controller doesn't respond to the POST request when I hit the submit button.
There is no error, just no response. The controller method is not being called. I have a logger in the method that displays an INFO message and nothing is displayed (other INFO messages do display). MVC is working (at least partial) because I get a response from a "home" JSP page, but nothing for a POST.
I'm including things that seem important; tell me if there's something you'd like to see.
Controller class:
#Controller
#RequestMapping(value = "/index")
public class Test {
#Autowired
private IAdminService service;
#RequestMapping(value = "/list", method = RequestMethod.GET)
public String lister(Model model) {
model.addAttribute("matieres", service.liserMatiere());
return "Action";
}
#RequestMapping(value="/saveMat", method = RequestMethod.POST)
public ModelAndView saveMat(#ModelAttribute("matiere") Matiere m) {
ModelAndView mav = new ModelAndView();
mav.addObject("mat", m);
service.ajouterMatiere(m);
mav.setViewName("Action");
return mav;
}
and this the NouvelleMat.jsp:
<%#taglib uri="http://www.springframework.org/tags/form" prefix="f"%>
<f:form class="form-horizontal" method="POST"
action="saveMat" modelAttribute="matiere">
<table>
<tr>
<td>Name:</td>
<td><f:input path="name" maxlength="30" /></td>
</tr>
<tr>
<td>Subject:</td>
<td><f:input path="subject" maxlength="50" /></td>
</tr>
<tr>
<td valign="top">Message:</td>
<td><f:textarea path="note" cols="70" rows="20" /></td>
</tr>
<tr>
<td><f:button type="submit" value="Submit matiere" name="submit" /></td>
<td> </td>
</tr>
</table>
</f:form>
I got Etat HTTP 400 The request sent by the client was syntactically incorrect
this is the matiere entity:
#Entity
public class Matiere implements Serializable{
#SuppressWarnings("unused")
private static final long serialVersionUID = 1L;
#Id
private String name;
private String subject;
private String note;
getters and setters....
}
First of all, i think slashes at the beginning of the method RequestMapping are unnecessary, they will be relative to classe's "/index".
But to debug such problem, You should check out the request your browser sends to the controller.
If You are using Chrome, go with ctrl+shift+c to open the console, or F12 for firebug in Firefox, or console in IE. Go to network, and check out what is Your browser sending. You will probably be able to tell what is the mistake by looking at the request body and headers.

Getting => java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'enumLanguage' available as request attribute

I am using Spring form to get inputs from client (if i use normal html input). If i use Spring form input i got error : java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'enumLanguage' available as request attribute
this is my JSP:
<form:form commandname="enumLanguage" action="${pageContext.request.contextPath}/enumLanguage/create.action" method="post" modelAttribute="enumLanguage" >
<fieldset class="langStep">
<legend>Language Details</legend>
<table class="langpadding">
<tr>
<td><label>Name:</label></td>
<td><form:input path="name" cssClass="textbox2"></form:input></td>
<td><label class="llangpadding">Short Name:</label></td>
<td><form:input path="shortName" cssClass="textbox2"></form:input></td>
</tr>
</table>
Save<span class="icon icon3"></span>
</form:form>
and this is my Controller:
#RequestMapping( value="/enumLanguage/create.action", method=RequestMethod.POST)
public ModelAndView create(#ModelAttribute EnumLanguage enumLanguage) throws Exception {
ModelAndView mvc = null;
try{
List<EnumLanguage> enumLanguages = new ArrayList<EnumLanguage>();
enumLanguages.add(enumLanguage);
List<EnumLanguage> enumLanguagesList = enumLanguageService.create(enumLanguages);
mvc = new ModelAndView("setup/EnumLanguageList");
} catch (Exception e) {
}
return mvc;
}
Make sure you have your #ModelAttribute set to the model when rendering the view
Make sure you made available in the view a model attribute with a key enumLanguage which is the value of the commandname of the form.
So the controller method that returns the view containing the form that you posted should look something like this.
#RequestMapping(value = "/language-details.do", method = RequestMethod.GET)
public ModelAndView initLanguageDetailsView() {
ModelMap model = new ModelMap();
EnumLanguage enumLang = new EnumLanguage();
//setters blah blah
//...
//make it available to the view
model.addAttribute("enumLanguage", enumLang);
return new ModelAndView("language-details", model);
}

how to read the already existing bean in jstl|form?

I have simple form with SpringMVC, I want retrieve my already had bean by pbid. the problem is the server side I can get the already setting bean, but the jsp side it always get new bean. Can I use the #ModelAttribute("productbean") to received some parameters to get the bean store in my server-side? how to do it? The jstl|form seem always get new form
<form:form method="post" modelAttribute="productbean" action="">
<table>
</tr>
<tr>
<td width="118"><form:label for="name" path="name" > Production Name:</form:label></td>
<td colspan="2"><form:hidden path="pbid"/><form:input path="name" type="text" size="50" /></td>
</tr>
...
My controler is like:
#RequestMapping(value="/createproduct", method=RequestMethod.GET)
public String getProduct(HttpServletRequest req, Model model,
#RequestParam(value = "pbid", required = false, defaultValue = "") String spbid) throws MalformedURLException {
UUID pbid;
if(spbid.isEmpty())pbid=UUID.randomUUID();
else pbid=UUID.fromString(spbid);
ProductBean tmp;
if(!products.containsKey(pbid)){
tmp=createbean();
pbid=tmp.getPbid();
System.err.println("============new productbean===============\n");
}else{
tmp=products.get(pbid);
System.err.println(tmp.getMpf().size());
System.err.println(tmp.getMpf().printFileNameList());
}
.....
#ModelAttribute("productbean")
public ProductBean createbean(){
ProductBean productbean=new ProductBean(context.getRealPath(filepath));
products.put(productbean.assignID(), productbean);
return productbean;
}
Add session attribute annotation over class
#SessionAttributes("productbean")
#controller
public class test() {
}

Resources