Question

I think that I found a error in runtime of Java with JSF 2.0 (Using Primefaces), in this project I'm using JSF 2.0 Primefaces and CDI.

Resuming the problem, I have a method setter in my business class Role that received a List, but the JSF is setting a ArrayList on that. Should the java throw a exception or at least should not find a method that matches? here is:

public void setAcl(List<Integer> acl) {
    this.acl = acl;
    System.out.println("Received: " + this.acl);
    for(Object i : this.acl) {
        System.out.println(i.getClass() + " > " + i);
    }
}

The output of this method is:

Received: [1, 5]
class java.lang.String > 1
class java.lang.String > 5

And when I try to use in foreach like this:

for(Integer i : this.acl) {
    System.out.println(i.getClass() + " > " + i);
}

Throws

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

Can you explain what is going on here? Is it a error on JSF or Java? Or is my misunderstood?

First I have a UI JSF (.xhtml) that has a p:selectManyCheckbox, this manyCheckbox receive a array of enum PontoSenha (using the enum's method values() ) from managedbean and define the values to itemValue and itemLabel. Follow the code of manyCheckbox:

<p:selectManyCheckbox id="acl" value="#{roleController.role.acl}" layout="grid" columns="4" required="true" requiredMessage="Selecione pelo menos um ponto de senha" >  
    <f:selectItems value="#{roleController.pontosSenha}" var="pontoSenha" itemValue="#{pontoSenha.pontoSenha}" itemLabel="#{pontoSenha.descricao}" />  
</p:selectManyCheckbox>

Declaration of enum PontoSenha:

package br.com.bsetechnology.atacadao.business;
public enum PontoSenha {
    CADASTRO_FORNECEDOR(1, "Cadastrar fornecedor"),
    CADASTRO_LOJA(2, "Cadastrar loja"),
    CADASTRO_PRODUTO(3, "Cadastrar produto"),
    RELATORIO(4, "Gerar relatório"),
    SEGURANCA_GRUPOS(5, "Gerenciar grupos de usuário"),
    SEGURANCA_USUARIOS(6, "Gerenciar usuários");

    private int pontoSenha;
    private String descricao;

    private PontoSenha(int pontoSenha, String descricao) {
        this.pontoSenha = pontoSenha;
        this.descricao = descricao;
    }

    public int getPontoSenha() {
        return pontoSenha;
    }

    public String getDescricao() {
        return descricao;
    }

    @Override
    public String toString() {
        return String.format("%02d - %s", pontoSenha, descricao);
    }
}

Declaration of the ManagedBean

package br.com.bsetechnology.atacadao.controller;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.inject.Inject;
import javax.inject.Named;
import br.com.bsetechnology.atacadao.business.PontoSenha;
import br.com.bsetechnology.atacadao.business.Role;
import br.com.bsetechnology.atacadao.core.FacesUtil;
import br.com.bsetechnology.atacadao.dao.RoleDAO;

@Named("roleController")
@RequestScoped
public class RoleController {

    @Inject
    private RoleDAO roleDao;
    private Role role;

    public void setRoleDao(RoleDAO roleDao) {
        this.roleDao = roleDao;
    }

    public Role getRole() {
        if(role == null)
            role = new Role();
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    public PontoSenha[] getPontosSenha() {
        return PontoSenha.values();
    }

    public List<Role> getAll() {
        return roleDao.getAll();
    }

    public void salva() {
        System.out.println("Salvando");
        boolean resultAction = false;

        if(role.getCodigo() > 0) {
            resultAction = roleDao.update(role);
        } else {
            resultAction = roleDao.insert(role);
        }

        if(resultAction) {
            role = new Role();
            FacesUtil.addMessage(FacesMessage.SEVERITY_INFO, "Grupo salvo com sucesso.", null);
        } else {
            FacesUtil.addMessage(FacesMessage.SEVERITY_WARN, "Grupo não foi salvo.", null);
        }
    }
}

Business class Role

package br.com.bsetechnology.atacadao.business;
import java.util.ArrayList;
import java.util.List;

public class Role {
    private int codigo;
    private String descricao;
    private List<Integer> acl;

    public Role() {
        acl = new ArrayList<Integer>();
    }

    public Role(int codigo, String descricao, List<Integer> acl) {
        setCodigo(codigo);
        setDescricao(descricao);
        setAcl(acl);
    }

    public int getCodigo() {
        return codigo;
    }

    public void setCodigo(int codigo) {
        this.codigo = codigo;
    }

    public String getDescricao() {
        return descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }

    public List<Integer> getAcl() {
        return acl;
    }

    public void setAcl(List<Integer> acl) {
        this.acl = acl;
        System.out.println("Received: " + this.acl);
        for(Object i : this.acl) {
            System.out.println(i.getClass() + " > " + i);
        }
    }

    public void addPontoSenha(int pontoSenha) {
        this.acl.add(pontoSenha);
    }

    public void remPontoSenha(int pontoSenha) {
        this.acl.remove(pontoSenha);
    }
}

The page JSF that I use to register roles (template.xhtml is only HTML and CSS):

<ui:composition template="/WEB-INF/template.xhtml" xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui">

    <ui:define name="body-content">
    <div class="row">
        <h:form id="formGrupo" class="form-horizontal"><fieldset>
        <p:panel header="Edição de grupo de usuário" >
            <div class="control-group">
                <h:outputLabel for="codigo" value="Código" readonly="" styleClass="control-label" />
                <div class="controls">
                    <h:inputText id="codigo" class="form-control" value="#{roleController.role.codigo}"
                        style="width:100px;text-align:right;" />
                </div>
            </div>
            <div class="control-group">
                <h:outputLabel for="descricao" value="Descrição" styleClass="control-label" />
                <div class="controls">
                    <h:inputText id="descricao" class="form-control" value="#{roleController.role.descricao}"
                        required="true" maxlength="20" requiredMessage="Descrição obrigatória" converterMessage="Descrição inválida"  />
                    <h:message for="descricao" class="error" />
                </div>
            </div>
            <div class="control-group">
                <h:outputLabel value="Pontos de senha" styleClass="control-label" />
                <div class="controls checkbox">
                    <p:selectManyCheckbox id="acl" value="#{roleController.role.acl}" layout="grid" columns="4"
                        required="true" requiredMessage="Selecione pelo menos um ponto de senha" >  
                        <f:selectItems value="#{roleController.pontosSenha}" var="pontoSenha" itemValue="#{pontoSenha.pontoSenha}" itemLabel="#{pontoSenha.descricao}" />  
                    </p:selectManyCheckbox>
                    <h:message for="acl" errorClass="error" />
                </div>
            </div>
            <div class="form-actions">
                <hr/>
                <p:commandLink value="Salvar" styleClass="btn btn-primary" style="color:#fff;" action="#{roleController.salva}" update=":formGrupo,:formTable:tblRoles">
                </p:commandLink>
            </div>
            <p:messages globalOnly="true" showDetail="false" closable="true" />
        </p:panel>
        </fieldset></h:form>
    </div>
    <br/>
    <div class="row">               
        <h:form id="formTable" styleClass="form-horizontal"><fieldset>
            <p:panel header="Grupos de usuários">
                <p:dataTable id="tblRoles" var="role" value="#{roleController.all}" rowKey="#{role.codigo}" stickheader="true" >

                    <p:column headerText="Código" width="20">
                        <h:outputText value="#{role.codigo}" />
                    </p:column>
                    <p:column headerText="Descrição">
                         <h:outputText value="#{role.descricao}" />
                    </p:column>                 
                    <p:column width="200">      
                        <p:commandLink value="Editar" class="btn btn-success btn-sm" style="color:#fff;margin-left:5px;" update=":formGrupo">
                            <f:setPropertyActionListener value="#{role}" target="#{roleController.role}" />
                        </p:commandLink>
                        <p:commandLink value="Excluir" styleClass="btn btn-danger btn-sm" style="color:#fff;margin-left:5px;" />  
                    </p:column>
                </p:dataTable>

            </p:panel>
        </fieldset></h:form>
    </div>
    </ui:define>
</ui:composition>

Full print stack trace

Jan 29, 2014 10:59:43 PM com.sun.faces.context.AjaxExceptionHandlerImpl handlePartialResponseError
SEVERE: javax.faces.component.UpdateModelException: javax.el.ELException: /seguranca/grupos.xhtml @27,87 value="#{roleController.role.acl}": Error writing 'acl' on type br.com.bsetechnology.atacadao.business.Role
    at javax.faces.component.UIInput.updateModel(UIInput.java:867)
    at javax.faces.component.UIInput.processUpdates(UIInput.java:749)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1286)
    at org.primefaces.component.panel.Panel.processUpdates(Panel.java:288)
    at javax.faces.component.UIForm.processUpdates(UIForm.java:281)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1286)
    at javax.faces.component.UIComponentBase.processUpdates(UIComponentBase.java:1286)
    at javax.faces.component.UIViewRoot.processUpdates(UIViewRoot.java:1254)
    at com.sun.faces.lifecycle.UpdateModelValuesPhase.execute(UpdateModelValuesPhase.java:78)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:198)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:646)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at br.com.bsetechnology.atacadao.core.AccessControlFilter.doFilter(AccessControlFilter.java:31)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)
Caused by: javax.el.ELException: /seguranca/grupos.xhtml @27,87 value="#{roleController.role.acl}": Error writing 'acl' on type br.com.bsetechnology.atacadao.business.Role
    at com.sun.faces.facelets.el.TagValueExpression.setValue(TagValueExpression.java:139)
    at javax.faces.component.UIInput.updateModel(UIInput.java:832)
    ... 30 more
Caused by: javax.el.ELException: Error writing 'acl' on type br.com.bsetechnology.atacadao.business.Role
    at javax.el.BeanELResolver.setValue(BeanELResolver.java:153)
    at com.sun.faces.el.DemuxCompositeELResolver._setValue(DemuxCompositeELResolver.java:255)
    at com.sun.faces.el.DemuxCompositeELResolver.setValue(DemuxCompositeELResolver.java:281)
    at org.apache.el.parser.AstValue.setValue(AstValue.java:218)
    at org.apache.el.ValueExpressionImpl.setValue(ValueExpressionImpl.java:253)
    at org.jboss.weld.el.WeldValueExpression.setValue(WeldValueExpression.java:64)
    at com.sun.faces.facelets.el.TagValueExpression.setValue(TagValueExpression.java:131)
    ... 31 more
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at br.com.bsetechnology.atacadao.business.Role.setAcl(Role.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at javax.el.BeanELResolver.setValue(BeanELResolver.java:142)
    ... 37 more
Was it helpful?

Solution

This is caused by a combination of several technical limitations and facts.

  • In Java, generics are compile time syntactic sugar. Ultimately, when a Java class is compiled, all generic type information is lost. So, during runtime there's no means of any generic type information in the List instance being passed around.

  • The expression language (EL, #{}) runs during runtime using Java Reflection API and sees in your particular case only a List, not a List<Integer>.

  • The generated HTML output and the obtained HTTP request parameters are in Java perspective basically Strings.

  • As long as you don't explicitly specify a JSF Converter between String to the desired type, JSF will let EL (read: Reflection API) add the unconverted submitted String value to the List.

In order to get the submitted value to end up as Integer in the model, you have 3 options:

  1. Explicitly specify a converter for String to Integer. Fortunately, JSF has a built-in one, the IntegerConverter which has the converter ID javax.faces.Integer. So all you need to do is specifying it in the input component's converter attribute.

     <p:selectManyCheckbox ... converter="javax.faces.Integer">
    
  2. Use Integer[] instead of List<Integer> as model property.

     private Integer[] acl;
    

    This way the desired type is visible to EL (read: Reflection API) and it'll perform automatic conversion using the built-in converter.

  3. Upgrade to at least JSF 2.3. As per spec issue 1422 the UISelectMany components will have automatic conversion when a Collection is used, using the same basic principle as OmniFaces SelectItemsConverter.

See also UISelectMany javadoc.

Obtain the Converter using the following algorithm:

  • If the component has an attached Converter, use it.

  • If not, look for a ValueExpression for value (if any). The ValueExpression must point to something that is:

    • An array of primitives (such as int[]). Look up the registered by-class Converter for this primitive type.

    • An array of objects (such as Integer[] or String[]). Look up the registered by-class Converter for the underlying element type.

    • A java.util.Collection. Do not convert the values. Instead, convert the provided set of available options to string, exactly as done during render response, and for any match with the submitted values, add the available option as object to the collection.

If for any reason a Converter cannot be found, assume the type to be a String array.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top