I'm trying to get some validators working with Spring 3. I keep getting an error:
org.springframework.beans.NotReadablePropertyException: Invalid property 'name' of bean class [java.lang.String]: Bean property 'name' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
My question is, is this what that error referring to. I believe that in the rejectValue method, it is calling getName() on myobj. Is it saying that myobj.getName() does not exist? Because I can tell you it does. Otherwise this method would not even compile.
public void validate(Object target, Errors errors) {
MyObject myobj = (MyObject)target;
String name = myobj.getName();
if(name.length() >100 || name.length() < 10) {
errors.rejectValue("name", "fieldLength");
}
}
Here is MyObject:
public class MyObject {
public MyObject() {
super();
// TODO Auto-generated constructor stub
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And in my jsp:
<form:form name="MyForm" commandName="myobj" method="post" action="${testurl}">
<table id="mytable" width="100%">
<tbody>
<tr>
<td colspan="2">
<form:errors path="name"/>
</td>
</tr>
<tr>
<td>
Name:
</td>
<td>
<form:input path="name"/>
</td>
</tr>
</tbody>
</table>
My controller:
#ActionMapping(params="myaction=test")
public void test(#ModelAttribute("myobj") MyObject myobj, BindingResult bindingResult,
ActionRequest request, ActionResponse response, SessionStatus sessionStatus) {
}
testValidator.validate(myobj, bindingResult);
if (!bindingResult.hasErrors()) {
response.setRenderParameter("myaction", "nextpage");
} else {
response.setRenderParameter("myaction", "test");
}
}
This is most likely telling you that some EL expression is failing, because Spring cannot figure out what the type of the 'name' property is based on the getter/setter signatures. If you showed us the declaration of (all of) the getters and setters for 'name', maybe we can be more specific.
Annoyingly, it seems like the code was right the whole time. I finally ran maven clean and then rebuilt and redeployed, along with clearing all cache, etc, and now it is working with that code. I am going to modify it to try to get something closer to what I actually want to happen (nested validations) and see where I get with that.
I guess your problem has nothing to do with validation per se. I can only suspect that the code you're pasting is not your exact example, but a hand-made one inspired by the code really executed.
There has to be some basic problem with MyObject property name. My personal guess is that the getter is defined in the super class of MyObject and only the setter is re-defined (or overriden) in the MyObject class.
Anyway, the best advice I can have for you is to try to extract a minimal example of your code and try to run it. Even if for some reasons you can't share the example with us, that will make it easier for you to find the reason of your troubles.
This happened to me with a Spring Validator that kept complaining about two unreadable properties on my domain POJO. One was a java.lang.Boolean, the other was a java.lang.Integer and they both had proper JavaBeans conventional getters and setters.
After some digging around I discovered I initialized the Boolean property with:
#Value("T{Boolean.FALSE}")
and the Integer property with:
#Value("0")
This was residue of a previous instructional project. Anyway, once removed, those annoying unreadable property errors ceased and the validator started doing it's job.
Related
Here is a sample skeleton of DTO object.
public class MyDTO
{
List<Student> students=new ArrayList<>();
}
public class Student
{
String name;
Integer age;
// setter and getter methods
}
Now, the user has a chance to enter a lot of students into the list and any student detail might contain an error. The possible errors are student age being greater than 25, and name containing special characters etc.
For example, students[2].name has a special character and student[4].age > 25, then they are errors. Now, I would like to display the error below those fields and also highlight the corresponding fields.
<form th:field="${myDTO}">
<input type="text" th:field="*{students[0].name}" th:errorclass="fieldError"/>
<span class="error" th:if="${#fields.hasErrors('students[0].name')}" th:errors="*{students[0].name}"></span>
<input type="number" th:field="*{students[0].age}" min="15" max="25" th:errorclass="fieldError"/>
<span class="error" th:if="${#fields.hasErrors('students[0].age')}" th:errors="*{students[0].age}"></span>
</form>
I am confused on what to put in th:field attribute? When I write as above, such type of error is the result
Neither BindingResult nor plain target object for bean name
'students+'['+0+']'' available as request attribute.
In my validators, I have such type of code..
int idx=0;
for(Student st: students)
{
errors.pushNestedPath("students["+idx+"]");
ValidationUtils.invokeValidator(studentValidator, st, errors);
errors.popNestedPath();
idx++;
}
and in the StudentValidator class..
#Override
public void validate(Object obj, Errors errors) {
Student s=(Student) obj;
if(containsSpecialCharacters(s.name))
{
errors.rejectValue("name","name.containsSpecialCharacters",null,null);
}
if(s.age>25 || s.age<15)
{
errors.rejectValue("age","age.invalid",null,null);
}
}
Now, my problems are
How do I show those errors, highlight the corresponding fields?
What to put in the th:field tag?
Next, the student records are added dynamically, that is the student rows doesn't exist previously, by clicking on Add student button, the user will be able to add the student. Now, even the th:field must also be updated. How to do that, because it is related to thymeleaf template processing which is done previously but not after the page is loaded?
Hope you will reply as soon as possible.
your validation seems to be right, but maybe you need to pass a BindingResult as parameter in your controller so that the error can be retrieved in your view layer.
#PostMapping("/students")
public String saveStudent(#Valid Student, BindingResult bindingResult, RedirectAttributes redirAttrs) {
if (bindingResult.hasErrors()) {
// Show errors here
bindingResult.getAllErrors().stream().forEach(System.out::println);
return "student-edit";
} else {
Long id = releaseLogService.save(student).getId();
redirAttrs.addFlashAttribute("message", "Success");
return "redirect:/student/edit/" + id;
}
}
As I struggled for hours I finally found where those annoying ClassCastExceptions came from, which I thought were produced by Hibernate and it's enum-mapping.
But they came from my JSF view, where I passed a List from
<h:selectManyCheckbox value="#{createUserManager.user.roles}" ... >
<f:selectItems value="#{createUserManager.roles}"/>
</h:selectManyCheckbox>
back into my backing bean.
My data simply consists of the values of an enum:
public Role[] getRoles()
{
return Role.values();
} .
I was really shocked when I tested the setter of roles in the User-class and got this:
public void setRoles(List<Role> paramRoles) {
System.out.println(paramRoles.get(0) instanceof Role); //output: false
for(Role role : paramRoles){ ...} //crashes with ClassCastException
}
Changing List<Role> paramRoles to List<String> paramRoles worked perfectly.
How is this possible? Shouldn't those generics be type safe or is type erasure in connection with JSF killing the whole type safety thing?
Also shouldn't the return value of h:selectManyCheckbox be List<Role>, like I passed in via the f:selectItems?
The behaviour you are experiencing is fully expected. Moreover, it is related to java generics in the same way as to how HTTP works.
The problem
The HTTP part
The problem is that you don't fully understand how HTTP works. When you submit data by pressing the submit button, parameters of your generated by JSF <h:selectManyCheckbox> tag, as a bunch of <input type="checkbox" name="..." value="userRoleAsString"> checkboxes, will be sent as strings and retrived ultimately as request.getParameter("checkboxName"); also as strings. Of course, JSF has no idea how to construct you model object class, Role.
The generics part
As you know due to the fact that java chose type erasure for generics to provide for backwards compatibility, information about generic types is basically a compile-type artifact and is lost at runtime. So at runtime your List<Role> erases to a plain, good old List. And as far as EL is a runtime language that uses Java Reflection API to deal with your expressions / call methods, at runtime no such information is available. Taking into account the HTTP part, JSF does its best and assigns string objects to your list, as it's all it can implicitly do. If you are willing to tell JSF to do otherwise, you need to do that explicitly, i.e. by specifying a converter to know what type of object to expect in an HTTP request.
The JSF part: aftermath
JSF has a provided javax.faces.Enum converter and in indeed works, if EL knew of the compile-time generic type of your list, that is Role. But it doesn't know of it. It would be not necessary to provide for a converter in case your multiple selection would be done on a Role[] userRoles object, or if you used the unique selection like in <h:selectOneMenu> with a value bound to Role userRole. In these examples the built-in enum converter will be called automatically.
So, to get it work as expected you need to provide for a Converter that will 'explain' JSF what type of values does this list hold, and how to do the transformations from Role to String, and vice versa.
It is worth noting that this will be the case with any bound List<...> values within the multiple choice JSF components.
Points of reference on Stack Overflow
After the problem was examined and resolved I was wondering if no one faced it in the past and searched for some previous answers here. Not surprisingly, it was asked before, and of course the problem was solved by BalusC. Below are two most valuable point of reference:
JSF 2.0 use enum in selectMany menu;
How to make a dropdown menu of a enum in JSF;
How to create and use a generic bean for enums in f:selectItems?.
The test case and two examples of working converters
Below I provide for a test case entirely for your understanding: everything works as expected apart from the third <h:selectManyCheckbox> component. It's up to you to trace it fully to eliminate the issue altogether.
The view:
<h:form>
Many with enum converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles}" converter="roleEnumConverter">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Many with plain converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles2}" converter="roleConverter">
<f:selectItems value="#{q16433250Bean.allRoles2}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter
<!-- will NOT be mapped correctly with Role object, but with a default String instead -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles3}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter + array
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles4}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
<h:commandButton value="Submit" action="#{q16433250Bean.action}"/>
</h:form>
The bean:
#ManagedBean
#RequestScoped
public class Q16433250Bean {
private List<Role> userRoles = new ArrayList<Role>();//getter + setter
private List<Role> userRoles2 = new ArrayList<Role>();//getter + setter
private List<Role> userRoles3 = new ArrayList<Role>();//getter + setter
private Role[] userRoles4;//getter + setter
public enum Role {
ADMIN("Admin"),
SUPER_USER("Super user"),
USER("User");
private final String name;
private Role(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public Role[] getAllRoles() {
return Role.values();
}
public String action() {
return null;
}
}
The converters:
#FacesConverter("roleEnumConverter")
public class RoleEnumConverter extends EnumConverter {
public RoleEnumConverter() {
super(Role.class);
}
}
and
#FacesConverter("roleConverter")
public class RoleConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Role role = Role.valueOf(value);
return role;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Role) || (value == null)) {
return null;
}
return ((Role)value).toString();
}
}
Could someone please help me find out why my attempt to bind Collection to the form in Spring MVC is not working?
Here is how my object looks like -
public class TestObj {
private Integer testNumber;
private String home;
private String destination;
}
Here is my form object that contains list of above object -
public class TestForm {
private List<TestObj> testList;
//contains getter and setter for testList
}
In my controller, I have implemented formBackingObject method -
public class MyController extends SimpleFormController {
public MyController() {
setCommandClass(TestForm.class);
setCommandName("testForm");
}
protected Object formBackingObject(HttpServletRequest request) throws Exception {
if (isFormSubmission(request)) {
testForm = (TestForm) super.formBackingObject(request);
//THIS ALWAYS RETURNS NULL ON FORM SUBMISSION
List<TestObj> testList = testForm.getTestList();
} else {
//load initial data using hibernate. TestObj is hibernate domain object.
List<TestObj> testList = myService.findTestList();
testForm = new TestForm(testList);
}
return testForm;
}
Here is my JSP snippet -
<form:form commandName="testForm" method="post">
<c:forEach items="${testForm.testList}" var="testvar" varStatus="testRow">
<tr>
<td>
<form:hidden path="testList[${testRow.index}].home" />
<c:out value="${testvar.home}" />
</td>
<td>
<form:input path="testList[${testRow.index}].destination" />
</td>
</tr>
</c:forEach>
<tr><td><input type="submit"></td></tr>
</form:form>
While the first time I load the data shows up fine on the form, when I press submit button the control goes to the formBackingObject method and the isFormSubmission returns true. However, when I get the command object using super.formBackingObject(request), it returns the form object with the testList value as null. I am unable to figure out why this simple case is not working?
I will really appreciate any help in getting this to work.
Are you using Spring 3? If so, you should take a look at this post.
With respect to list processing and object binding, take a look at this post.
Try using the following code. May be that can solve your problem.
private List<TestObj> operationParameterses = LazyList.decorate(new ArrayList<TestObj>(), FactoryUtils.instantiateFactory(TestObj.class));
It won't return you all null list.
Hope that helps you.
Cheers.
I guess my understanding of formBackingObject method must be wrong. I removed that method from the implementation, used referenceData for initial form load and onSubmit to process it on submit. This works fine and does get collection in the form back as expected.
Thanks all for the help though.
I'm using Spring 3.1.0.RELEASE. I have this field in my command object ...
public Set<EventFeed> getUserEventFeeds() {
return this.userEventFeeds;
}
On my Spring JSP page, I want to display a checkbox list of all possible event feeds, and then have checked checkboxes if the user has one in his set. I want to have some special HTML formatting around each checkbox, so I'm trying ...
<form:form method="Post" action="eventfeeds.jsp" commandName="user">
...
<c:forEach var="eventFeed" items="${eventFeeds}">
<tr>
<td><form:checkbox path="userEventFeeds" value="${eventFeed}"/></td>
<td>${eventFeed.title}</td>
</tr>
</c:forEach>
...
However, the items aren't getting checked by default if one is in the set. How do I do this? Here is the binder I'm using in my controller class ...
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(EventFeed.class, new EventFeedEditor());
}
private class EventFeedEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(eventFeedsDao.findById(Integer.valueOf(text)));
}
#Override
public String getAsText() {
return ((EventFeed) getValue()).getId().toString();
}
}
#Dave, there is something called form:checkBoxes. You can try with that.
<form:checkboxes path="userEventFeeds" items="${eventFeeds}" itemLabel="id" itemValue="value"/>
My assumption here is you should have "id" and "value" defined in the EventFeed class.
I just tried this by having a String[] availableList and String[] selectedList. It works like charm. You can give a try as well.
Interestingly this works:
<form:checkbox path="services" value="${type}" checked="checked"/>
You can do this by placing selected default property true in your class
class User {
boolean userEventFeeds = true;
}
I've tried both form:checkboxes and form:checkbox with the same data and the first works, the second doesn't. (Same release of Spring you have)
It looks like there was a bug which, despite their claim, seems to be still there.
For my usecase (reacting on stuff in a list that has nothing to do with the object being filled), this code worked:
(Please be aware that this is a last-resort kind of code, other solutions are most likely better suited.)
<%# taglib prefix="jstl" uri="http://java.sun.com/jsp/jstl/core" %>
<jstl:forEach var="listObject" items="${someList}">
<jstl:if test="${listObject.active}">
<form:checkbox path="active" checked="checked"/>
</jstl:if>
<jstl:if test="${!listObject.active}">
<form:checkbox path="active"/>
</jstl:if>
</jstl:forEach>
I have a tri-state Boolean attribute (true, false, and null) in of one of my objects, and I don't know how to properly bind it using the Spring Forms tags. I would like to use a series of 3 radio buttons (true, false, and null), but it seems that Spring does not like what I'm trying thus far.
Here's the backing POJO:
public class Spirit {
/*** Private Fields **/
private Integer id;
private String name;
private Boolean isAlive;
/*** Constructor **/
public Spirit() {}
/*** Getters **/
public Integer getId() {return id;}
public String getName() {return name;}
public Boolean isAlive() {return isAlive;}
/*** Setters **/
public void setId(Integer id) {this.id = id;}
public void setName(String name) {this.name = name;}
public void isAlive(Boolean isAlive) {this.isAlive = isAlive;}
}
And here's the form I'm using (that doesn't work):
<sf:form method="POST" modelAttribute="spirit">
<table>
<tr>
<th><label for="spirit_name">Name</label></th>
<td><sf:input path="name" id="spirit_name" /></td>
</tr>
<tr>
<th><label for="spirit_isAlive">Livelyness</label></th>
<td>N/A: <sf:radiobutton path="isalive" value="null" /> Alive: <sf:radiobutton path="isalive" value="true" /> Dead: <sf:radiobutton path="isalive" value="false" /></td>
</tr>
</table>
<sf:hidden path="id"/>
<input type="submit" value="save" />
</sf:form>
The error I'm getting:
SEVERE: Servlet.service() for servlet jsp threw exception
org.springframework.beans.NotReadablePropertyException: Invalid property 'isAlive' of bean class [com.example.Spirit]: Bean property 'isAlive' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:707)
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:699)
at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:98)
at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:224)
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:174)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:194)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:160)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.autogenerateId(AbstractDataBoundFormElementTag.java:147)
at org.springframework.web.servlet.tags.form.AbstractCheckedElementTag.autogenerateId(AbstractCheckedElementTag.java:78)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.resolveId(AbstractDataBoundFormElementTag.java:138)
at org.springframework.web.servlet.tags.form.AbstractSingleCheckedElementTag.writeTagContent(AbstractSingleCheckedElementTag.java:82)
at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:102)
at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:79)
at org.apache.jsp.WEB_002dINF.views.spirits.edit_jsp._jspx_meth_sf_005fradiobutton_005f0(edit_jsp.java:523)
at org.apache.jsp.WEB_002dINF.views.spirits.edit_jsp._jspService(edit_jsp.java:201)
There is another question that is very similar, but the solution in that case was to simply change the type to boolean, rather than Boolean. Unfortunately, changing the get/set methods for the tri-state is only available as a last-resort option. What is it that I am doing incorrectly?
You shouldn't specify value=null in the JSP Tag. This will result in the browser sending a value of isalive=null in the HTTP POST.
Since there is no way to bind "null" (the String) to a Boolean, this rightfully throws an error.
If the radio button is not selected by the user, then the browser will not send anything for the form parameter (or it will send isalive=, I forget which - but it does not matter). In this case Spring will not attempt to bind the field, which will leave your POJO with a isAlive field with null value.
So in summary - if you set value=null in the HTML/JSP tag, you are telling the browser to POST a string literal null. Just leave it out.
In the end, what I ended up doing was to use a <select> box for the values that the JSP was sending and binding, and then manipulated the UI via javascript to hide the actual select box and update it based on the user interacting with custom controls.
This worked quite well, since I eventually needed to have more than 3 states, but the downside is that it requires an extra class to model the states.