As I struggled for hours I finally found where those annoying ClassCastException
s 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
?
JSF tutorial provides basic and advanced concepts of JSF. Our JSF tutorial is designed for beginners and professionals both. JSF stands for Java Server Faces. It is a server-side Java framework for web development.
Our JSF tutorial includes all topics of JSF such as features, example, validation, bean validation, managed bean, referencing managed bean method, facelets etc Before learning JSF, you must have the basic knowledge of Java programming language. Our JSF tutorial is designed to help beginners and professionals.
(Since DeltaSpike provides the default integration only for JSF, the whole documentation for view-configs is located here.) Thanks to features like multiple (meta-data-)inheritance via interfaces, it provides a powerful approach to bind meta-data to one or multiple views.
Type-safe view-configs are static configs which can be used in combination with every view-technology which is based on Java. Currently DeltaSpike itself provides an integration for JSF, however, the basic concepts are independent of it.
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 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.
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:
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With