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();
}
}
Related
When a page is loaded with an input which is bound to a value in the view scopped backing bean and if the value is is pre-initialized, then after required field validation the input is again populated with the previous value.
Here is the managed bean:
#ManagedBean(name = "adminController")
#ViewScoped
public class AdminController extends BaseWebController implements Serializable {
private static final long serialVersionUID = 1019716974557397635L;
private transient CustomerDTO customerDTO;
public AdminController() {
log.debug("Inside AdminController");
}
#PostConstruct
public void init() {
initCustomerDTO();
customerDTO.setCustomerName("Test");
}
public void saveCustomer(ActionEvent event) {
try {
getServiceProvider().getCustomerService().addNewCustomer(customerDTO);
getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveSuccess(getFacesContext()));
} catch (Throwable throwable) {
getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveError(getFacesContext()));
printStackTrace(throwable);
}
initCustomerDTO();
}
private void initCustomerDTO() {
customerDTO = new CustomerDTO();
}
public CustomerDTO getCustomerDTO() {
return customerDTO;
}
public void setCustomerDTO(CustomerDTO customerDTO) {
this.customerDTO = customerDTO;
}
}
CustomerDTO is a POJO.
The jsf contains:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:form id="customerForm">
<p:inputText id="customerName" styleClass="customerName"
autocomplete="off"
label="#{adbBundle['admin.addCustomerPanel.addCustomerTable.customerName']}"
value="#{adminController.customerDTO.customerName}"
required="true" />
<p:commandButton value="#{adbBundle['saveButton']}"
actionListener="#{adminController.saveCustomer}"
icon="ui-icon-check"
update=":growl, #form, :adminTabView:customerListForm:customerListPanel" />
</h:form>
</ui:composition>
I also have a custom converter class for trimming blank String.
#FacesConverter(forClass = String.class)
public class StringTrimmer implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (StringUtils.isBlank(value)) {
if (component instanceof EditableValueHolder) {
((EditableValueHolder) component).setSubmittedValue(null);
}
return null;
}
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString();
}
}
So when the page is first loaded the input field contains "Test". I cleaned out the test field and click on the save button. I received error message for required field validation, but the input is again get populated with the default value "Test" that I have set before.
How can I solve the issue?
Update:
I am running in JBoss AS7 with Mojarra 2.1.7, which is JBoss'es implementation and Primefaces 3.4.2.
Your problem is caused by the converter.
When JSF validation fails for a specific component, the submitted value is kept in the component, else it is nulled out and set as local value. When JSF is finished with validation of all components and no one has failed validation, then all local values are nulled out and set as model value (the one referenced as value attribute). When JSF needs to redisplay the form with values, it first checks if the submitted value is not null and then display it, else it checks if the local value is not null and then display it, else it displays the model value outright.
In your case, you're converting the empty string submitted value to null regardless of validation outcome. So the submitted value is always null and thus JSF will always redisplay the model value instead of the empty string submitted value.
I believe that you rather need the following context parameter instead of the converter, certainly if the sole goal is to prevent the model values being polluted with empty strings when empty inputs are submitted.
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Or if your sole goal is really to trim whitespace from string, as the converter class name suggests, then you should not be touching the submitted value at all. You can find a concrete example here: Why does <h:inputText required="true"> allow blank spaces? The getAsObject() won't be set as submitted value, but instead as local value (on validation success) and ultimately as model value.
I have a weird problem with a custom #FacesConverter with JBoss-7.1.0.CR1b during an AJAX call in p:selectOneMenu (Primefaces 3.0).
The simplified Converter looks like this, there are no NPE or other exceptions in this class
#FacesConverter("MyConverter")
public class MyConverter implements Converter
{
public Object getAsObject(FacesContext fc, UIComponent uic, String value)
{
logger.debug("getAsObject value: "+value);
if (submittedValue.trim().equals("")) {return null;}
else
{
MyEjb ejb = new MyEjb();
ejb.setId(Long.parseLong(value()));
return ejb; //**** alternative with return null; ****
}
}
public String getAsString(FacesContext fc, UIComponent uic, Object value)
{
if (value == null || value.equals("")) {return "";}
else
{
MyEjb ejb = (MyEjb)value;
return ""+ejb.getId();
}
}
}
The converter is used in a p:selectOneMenu:
<h:form>
<p:selectOneMenu value="#{clientBean.selected}" converter="MyConverter">
<f:selectItems value="#{clientBean.all}" var="my"
itemLabel="#{my.name}" itemValue="#{my}"/>
<p:ajax listener="#{clientBean.changed}" />
</p:selectOneMenu>
</h:form>
That's not rocket engineering, the changed method simply makes a debug:
public void changed()
{
logger.info("changed() "+selected);
}
But now the annoying part: The changed() is never called with the code like above, but I get the converter invoked three times:
12:37:51,500 DEBUG getAsObject value: 35
12:37:51,502 DEBUG getAsObject value:
12:37:51,503 DEBUG getAsObject value:
If I change the p:selectOneMenu value="#{clientBean.selectedId}" to a long selectedId and don't use the Converter the method is called once. Even if I return null in getAsObject()the changed() is called (once). I don't assume it's Primefaces related, because I have the same behavior if I use h:selectOneMenu and f:ajax.
You should have a <p:messages />, <p:growl /> or <h:messages /> which is been ajax-updated in your view. You should also pay attention to warnings in server logs about possible missing faces messages. The chance is big that you're seeing the infamous Validation error: Value not valid validation error.
After conversion, JSF will validate if the submitted object is one of the available items as part of safeguard against tampered/hacked requests. JSF will do that by submittedObject.equals(oneOfAvailableObjects) for each of the available objects as you have there in <f:selectItems>. If nothing matches, then JSF will display this validation error.
In your particular case, the MyEjb class has apparently no equals() method or its implementation is broken. See also Right way to implement equals contract.
Here is the scenario - I want to create a view which can be used from a number of other views to create and edit a specific type of object.
My application has an address entity which can be shared between other entities.
In the view which maintains an entity, I would like a button/link which navigates to the address edit view for the address linked to that entity.
Another view which handles a different entity also needs to be able to navigate to the address edit view with its address.
The address view would then navigate back to the calling view once editing is completed.
My problem is that I can't seem to find a way to pass the address entity from the first view into the address view.
I think I want some kind of conversation scope, but don't know how to get the address without knowing about the page bean that references it, but obviously my address view can only know about addresses.
I am using JSF2.1 (MyFaces/PrimeFaces) and CDI (OpenWebBeans) and CODI.
I'm sure I must be missing something simple. (Simple relative to JSF/CDI terms that is!)
Just pass the address ID as request parameter and have the target view to convert, validate and set it in the bean by <f:viewParam>.
E.g.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
</h:link>
and then in addresses/edit.xhtml
<f:metadata>
<f:viewParam id="id" name="id" value="#{editAddressBacking.address}"
converter="#{addressConverter}" converterMessage="Bad request. Unknown address."
required="true" requiredMessage="Bad request. Please use a link from within the system." />
</f:metadata>
<h:message for="id" />
<h:form rendered="#{not empty editAddressBacking.address}">
<h:inputText value="#{editAddressBacking.address.street}" />
...
</h:form>
In order to navigate back to the original page, you could pass another request parameter.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
<f:param name="from" value="#{view.viewId}" />
</h:link>
(where #{view} is the implicit object referring to the current UIViewRoot)
and set it by <f:viewParam> as well so that you can in the submit method of the edit address backing bean just return to it:
public String save() {
// ...
return from + "?faces-redirect=true";
}
See also:
Communication in JSF 2 - Processing GET request parameters
I think I have come up with a solution.
As I am using CODI I can leverage the ConversationGroup annotation.
I've created an emtpy interface AddressConversation, then added this to all the backing beans that need to show the address/addressEdit.xhtml view, as well as the backing bean for the addressEdit view.
I'm also using CODI view config so my action methods return ViewConfig derived class objects.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class AddressView implements Serializable
{
private Class<? extends Views> fromView;
private Address editAddress;
private Address returnAddress;
// Getters/setters etc...
public Class<? extends Views> cancelEdit()
{
returnAddress = null;
return fromView;
}
}
So in a calling view I have (PrimeFaces commandLink)
<p:commandLink value="#{enquiryView.addressLinkText}" action="#{enquiryView.editAddress()}" immediate="true"/>
and in the backing bean EnquiryView I can #Inject an instance of AddressView in the correct conversation group, then set the address and return view properties when action method is called.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class EnquiryView implements Serializable
{
#Inject #ConversationGroup(AddressConversation.class) private AddressView addrView;
public Class<? extends Views> editAddress()
{
addrView.setAddress(editEnq.getAddress());
addrView.setFromView(Views.Enquiry.EnquiryEdit.class);
return Views.Address.AddressEdit.class;
}
}
I can also observe the navigation in EnquiryView and update the enquiry entity when an address has been "saved" in the address edit view.
protected void onViewConfigNav(#Observes PreViewConfigNavigateEvent navigateEvent)
{
if (navigateEvent.getFromView() == Views.Address.AddressEdit.class &&
navigateEvent.getToView() == Views.Enquiry.EnquiryEdit.class)
{
onEditAddressReturn();
}
}
private void onEditAddressReturn()
{
if (addrView.getReturnAddress() != null) {
// Save pressed
editEnq.setAddress(addrView.getReturnAddress());
}
}
If you want another entity to be set when the address is OK, just give the adresss process the EL name of the bean you want to set :
<f:param name="targetBeanSetter" value="enquiryBean.adress" />
And in Java :
public String executeAndBack() {
int last = this.targetBeanSetter.lastIndexOf('.');
String base = this.targetBeanSetter.substring(0, last);
String property = this.targetBeanSetter.substring(last + 1);
Object yourEntityToSet = FacesContext.getCurrentInstance().getELContext().getELResolver().getValue(context.getELContext(), null, base);
try {
PropertyUtils.setSimpleProperty(yourEntityToSet, property, constructeurHolder.getConstructeur());
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
return from + "?faces-redirect=true";
}
If you only need access to the choosen Adress, without building linked objects, when you are back on the first page, just inject in EnquiryView using
#ManagedProperty(value="{address}")
Address adress;
Given a very simple object:
class User {
private Integer id;
private String name;
public User() {}
public Integer getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
and a very simple controller action:
#RequestMapping(value="/edit/{id}/**", method=RequestMethod.POST)
public String editFromForm(#PathVariable("id") Integer id, #Valid User user, BindingResult bindingResult, Model model) {
// If we have errors, don't save
if(bindingResult.hasErrors()) {
// Put what they did in the model and send it back
model.addAttribute(user);
return "users/edit";
} else {
userDAO.save(user);
}
// Show them the updated page on success
return "redirect:/users/" + user.getId() + "/" + user.getName();
}
and a very simple form:
<sf:form method="POST" modelAttribute="user">
<label for="user_name">Name:</label>
<sf:input path="name" id="user_name" />
<input type="submit" value="save" /><sf:errors path="name" cssClass="error" />
</sf:form>
How should I be updating the entity in the database? Currently (since saveOrUpdate() is the actual hibernate call behind my DAO's save() method, a new object is persisted instead of updating the existing one because the id field is not being set on the object created from the form submission.
A couple of possible solutions have come to me, but I am not sure which is best in terms of keeping things clean and also secure (so that a malicious user cannot just fire in edits to whatever object Id's they wish).
Insert the id from the URL parameter into the object coming from the model binder
Have a hidden id field in the form and let the model binder attach the id
In both of those cases, there is no check in place to make sure that the object is still the same one, such as a checksum of some sort. How do others deal with this? Are there any clear example that walk through this issue?
Another issue that comes up is that I'd rather not need a setId() method since Hibernate is managing all of the id's. From what I have been able to determine, the Spring MVC model binder can only bind a field if it has the expected getter and setter. Is there some other way to apply the new state, such as getting the current User from the db by the URL's id parameter and then applying the new state to it, but without having to explicitly code all of the field copies?
I am sure there is a fairly simple, straightforward way to handle this, but my heat-addled brain does not seem to be able to come up with a suitable solution.
I'm fairly new to Spring + Hibernate so forgive me if this is one of those mundane, highly covered topics, but I have not been able to find any clear example that covers my very simple situation. If this has been covered sufficiently elsewhere, please point me in the right direction.
A couple of possible solutions have come to me, but I am not sure which is best in terms of keeping things clean and also secure (so that a malicious user cannot just fire in edits to whatever object Id's they wish).
Neither of the two approaches you mention will really handle a user who is attempting to edit objects that the user is not authorized to. At the end of the day, the user submitting the form needs to tell you which object they are submitting data for - whether it is in the URL parameter or in a hidden form parameter. I would say which of the two you choose is a matter of style and personal preference, really.
But what you need to be doing regardless of choice is to verify that the currently-logged-in user is authorized to change the object when processing the form submission. This would mean you need to check that this user is entitled to edit the current object ID, using whatever logic comprises "is allowed to do this" for your application.
Still have the same User POJO
class User {
private Integer id;
private String name;
public User() {}
public Integer getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
#RequestMapping("/create/userCreate.do")
public String createUser(Model model) {
User user = new User();
model.addAttribute(user);
return "userCreate";
}
Here I don't send the Id or any parameter, just pass the User form model attribute
#RequestMapping(value="/edit/userEdit.do", method=RequestMethod.POST)
public String editFromForm(#ModelAttribute("user") #Valid User user, BindingResult bindingResult, Model model) {
// If we have errors, don't save
if(bindingResult.hasErrors()) {
// Put what they did in the model and send it back
model.addAttribute(user);
return "users/edit";
} else {
userDAO.saveOrUpdate(user);
}
// Show them the updated page on success
return "redirect:/users/" + user.getId() + "/" + user.getName();
}
Here, the User object is bind user name and id. If there is no Id then the object will be saved and if the user object already is having Id then it will be updated. You don't have to pass id or parameter and make it visible on the address bar. Sometimes I don't like the idea of showing parameters to the users. I hope that helps
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<head>
<title> User Edit
</title>
</head>
<body>
<form:form method="post" action="userEdit.do" commandName="user">
<form:hidden path="id" />
<form:label path="username">User name:</form:label>
<form:input path="username" type="text" maxlength="20" />
<form:errors path="username" />
</form:form>
</body>
</html>
Im a bit late, but maybe it will be useful to someone.
Today I solved the same problem. I think your problem was there:
} else {
userDAO.save(user);
}
If it was hibernate session save, then new id was generated each time it was called. I used
session.saveOrUpdate(user)
instead of
session.save(user)
I agree 100% on what #matt b says. I have worked with similar products where at the end of the day you don't have any way of knowing if the user is editing the right object. Your only option is to make sure the user is permitted to do so. If you have a user who is editing their profile then obviously they shouldn't be editting any other person.
Now the easiest way to do what you are trying to do is actually niether. YOu don't want to use the url or hidden attributes. YOu should be able to use #ModelAttribute to do this. Here is an example
#RequestMapping(method = RequestMethod.GET)
public ModelAndView setupForm(#RequestParam("petId") int petId) {
User user = ...
model.addAttribute("user", user);
return model;
}
Then you should be able to do
#RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(#ModelAttribute("user") User user) {
...
}
There is no setters or hidden fields. All the loading and binding is handled via Spring. More documentation here.
I have a command object associated with a spring form controller:
public class PluginInstance {
private Set<PluginParameter> pluginParameters = new HashSet<PluginParameter>();
... some other string/long properties and getter setters...
}
the PluginParameter also have a Set in it which contain the values
public class PluginParameter {
private String parmName;
private Set<PluginParmvalue> pluginParmvalues = new HashSet<PluginParmvalue>();
...some other string/long properties and getter setters...
}
(Normally the pluginParmvalues will contain only one value, a list have been used for future expandability)
In the spring form I binding the values as
<form:input path="pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue" />
but the thing is that there can be a form:select(to present multiple predefined options to the user) or form:input (user can input any value). This has to be decided from another object
public class PluginConfigParm {
private String parmName;
private ArrayList<String> choices;
...getter setters and other properties
}
where I have to compare the name of PluginConfigParm.paramName with PluginParameter.paramName when they match and PluginConfigParm.choices.size() > 0 then form:select will be shown populated with the values from PluginConfigParm.choices otherwise form:input will be shown.
The question is simple: How can I do that.
Any help will be highly appreciated.
By using List<> instead of Set<> in controller. Problem solved. May be Set<> has no getter/setter that can be bind with spring form.
So <form:input path="pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue" />
and List<> in controller makes my life easier.
The Set is not an indexed collection so I could not work by using this syntax
pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue
eg:
class person{
set name<string> = new HashSet<String>()
}
<input type="hidden" path="person.name" name="person.name" value="<%=valueStr%>"/>
take valueStr = "hello, world"
by giving it as comma seperated values.. set works
Its working for me