Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSF and type safety

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?

like image 534
Zhedar Avatar asked May 08 '13 05:05

Zhedar


People also ask

What is JSF in JavaScript?

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.

What is included in our JSF tutorial?

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.

Why deltaspike for JSF?

(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.

What are type-safe view-configs?

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.


1 Answers

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

  1. 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.

  2. 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.

  3. 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();
    }

}
like image 96
skuntsel Avatar answered Sep 23 '22 21:09

skuntsel