Dúvida sobre Conversores e SelectOneMenu - Primefaces

Olá Pessoal.

Estou tentando resolver um problema aqui. Preciso carregar o objeto selecionado em um SelectOneMenu no meu ManagedBean. Para isso estou usando um conversor e daí vem um dos clássicos problemas: “Erro de validação: o valor não é válido”. Já tentei de tudo, pesquisei em diversos foruns mas não consigo ver o erro que estou comentendo. Segue meus códigos:

TipoLogradouroConverter

@FacesConverter(value="tipoLogradouroConverter", forClass=TipoLogradouro.class)
public class TipoLogradouroConverter implements Converter {
	
	@Override
	public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
		System.out.println(value);
		if(value != null && !value.isEmpty()){
			System.out.println(new TipoLogradouroDAO().busca(Integer.parseInt(value)).getDescricao());
			return new TipoLogradouroDAO().busca(Integer.parseInt(value));
		}
		return null;
	}
	
	@Override
	public String getAsString(FacesContext context, UIComponent component, Object object) throws ConverterException {
		if(object != null && object instanceof TipoLogradouro && ((TipoLogradouro) object).getId() != null){
			System.out.println(((TipoLogradouro) object).getId().toString());
			return ((TipoLogradouro) object).getId().toString();
		}
		return null;
	}
	
}

teste.xhtml - criei isso apenas para tentar resolver este problema.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	  xmlns:ui="http://java.sun.com/jsf/facelets"
	  xmlns:f="http://java.sun.com/jsf/core"
	  xmlns:h="http://java.sun.com/jsf/html" 
	  xmlns:p="http://primefaces.org/ui">

	<h:head>teste</h:head>

	<h:body>
		
					
				<h:form id="teste">		
						
				<p:dataTable value="#{enderecoBean.enderecos}" var="endereco" 
					paginator="true" rows="50" selectionMode="single" selection="#{enderecoBean.endereco}" 
					rowKey="#{endereco.id}" >
					<p:ajax event="rowSelect" />
				
					<p:column headerText="Código">
						#{endereco.id}
					</p:column>
					
					<p:column headerText="Logradouro">
						#{endereco.logradouro}
					</p:column>
					
					<p:column headerText="Tipo">
						#{endereco.tipoLogradouro.descricao}
					</p:column>						
								
					<f:facet name="footer">
					
						<p:commandButton value="Novo Funcionário" id="newFuncionarioButton" ajax="false" >
						</p:commandButton>
						
						<p:commandButton value="Editar Funcionário" id="editFuncionarioButton" ajax="false" >
						</p:commandButton>							
												
					</f:facet>																																	
				
				</p:dataTable>		
				
				<h:outputText value="Endereço:" />
				
				<h:panelGrid columns="4" >
																	
					<p:selectOneMenu  value="#{funcionarioBean.tipoLogradouro}" converter="tipoLogradouroConverter" >
					
						<f:selectItem itemLabel="Selecione um Tipo de Logradouro..." itemValue="" />
						<f:selectItems value="#{funcionarioBean.tiposLogradouros}" var="tipoLogradouro" itemValue="#{tipoLogradouro}" itemLabel="#{tipoLogradouro.descricao}"  />
					
					</p:selectOneMenu>
						
					<p:inputText id="endereco" value="#{funcionarioBean.funcionario.pessoa.endereco.logradouro}" maxlength="50" size="80" />
											
					<h:outputText value="Nº:" />
					<p:inputText id="numero" value="#{funcionarioBean.funcionario.pessoa.endereco.numero}" maxlength="20" size="20" />																
											
				</h:panelGrid>				
				
				<p:commandButton value="gravar" action="#{enderecoBean.salvar}" ajax="false" update="teste" />
				
				</h:form>	
											
	
	</h:body>
	
</html>

E meu EnderecoBean

package br.flexweb.beans;

import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

import br.flexweb.dao.EnderecoDAO;
import br.flexweb.models.Endereco;
import br.flexweb.models.TipoLogradouro;

@ViewScoped
@ManagedBean
public class EnderecoBean {

	private Endereco endereco;
	private TipoLogradouro tipoLogradouro;
	private List<Endereco> enderecos;

	private EnderecoDAO dao = new EnderecoDAO();
	
	public void salvar(){
		endereco.setTipoLogradouro(tipoLogradouro);
		dao.adicionar(endereco);
	}
	
	public Endereco getEndereco() {
		return endereco;
	}

	public void setEndereco(Endereco endereco) {
		this.endereco = endereco;
	}

	public TipoLogradouro getTipoLogradouro() {
		return tipoLogradouro;
	}

	public void setTipoLogradouro(TipoLogradouro tipoLogradouro) {
		this.tipoLogradouro = tipoLogradouro;
	}

	public List<Endereco> getEnderecos() {
		if (enderecos == null)
			enderecos = dao.lista(); 
		return enderecos;
	}

	public void setEnderecos(List<Endereco> enderecos) {
		this.enderecos = enderecos;
	}

}

Minha intenção é setar um tipo de logradouro dentro de endereço, por isso uso o bean de Endereço ao invez de TipodeLogradouro.

Alguém uma sugestão?

Obrigado.

Pode ter faltado o equals e hashcode da classe TipoLogradouro. Coloca para o IDE gerar eles pelo “id”.

Eu refiz meu hashCode e equals pelo IDE e agora ta dando nullPointerException. Ta acontecendo no momento que eu seto o TipoLogradouro no Endereço. Aparentemente, o converter não ta setando o objeto no MB. Sendo que meu converter ta retornando os objetos esperados. Sabe o que pode estar ocorrendo?

Eu costumo usar assim

<h:selectOneMenu id="tpa" value="#{processoBean.processo.tipoAcao.tpaCodigo}"
                                         requiredMessage="Preenchimento requerido">														
                            <f:selectItems value="#{processoBean.tipoAcao}"/>
                        </h:selectOneMenu>

no bean eu seto um new TioAcao() para a propriedade tipoAcao do Processo antes de chamar o formulario.

public String setup(){
//codigo omitido
return "form";
}

[quote=fnandos]Eu costumo usar assim

<h:selectOneMenu id="tpa" value="#{processoBean.processo.tipoAcao.tpaCodigo}"
                                         requiredMessage="Preenchimento requerido">														
                            <f:selectItems value="#{processoBean.tipoAcao}"/>
                        </h:selectOneMenu>

no bean eu seto um new TioAcao() para a propriedade tipoAcao do Processo antes de chamar o formulario.

public String setup(){ //codigo omitido return "form"; } [/quote]

Não entendi muito bem a sua lógica. Tentei fazer algo semelhando mas não tive sucesso. No Value do SelectOneMenu pasei #{enderecoBean.endereco.tipoLogradouro} e dei um new no meu tipoLogradouro no meu MB mesmo assim não tive sucesso. De qualquer forma não entendi a sua lógica pois o problema do selectOneMenu é que ele não passa o objeto pro ManagedBean e sim algum atributo, por isso a necessidade do converter, para converter esse numero em um objeto e vice-versa. Se puder explicar um pouco mais sobre o seu código.

E obrigado pela atenção.

Ok, tpaCodigo é uma propriedade da classe TipoAcao mapeada como @Id.

@Id
private Long tpaCodigo;

e a classe Processo tem

//annotations, não tenho essas classes no momento pois estão em um JAR separado
private TipoAcao tipoAcao;

Assim a navegação fica: processo -> tipoAcao -> tpaCodigo

no meu bean eu faço o seguinte naquele método setup

getProcesso().setTipoAcao(new TipoAcao());

depois é só mandar persistir o objeto Processo. Com converters num exemplo tosco.

public class TipoAcaoConverter implements Converter{
    
    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        try {
            TipoAcao tipoAcao = new TipoAcao();
            tipoAcao.setTpaCodigo(new Long(value));

            return tipoAcao;
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        try {
            TipoAcao tipoAcao = (TipoAcao) value;

            return String.valueOf(tipoAcao.getTpaCodigo());
        } catch (Exception e) {
            return null;
        }
    }
    
}

[quote=fnandos]Ok, tpaCodigo é uma propriedade da classe TipoAcao mapeada como @Id.

@Id
private Long tpaCodigo;

e a classe Processo tem

//annotations, não tenho essas classes no momento pois estão em um JAR separado
private TipoAcao tipoAcao;

Assim a navegação fica: processo -> tipoAcao -> tpaCodigo

no meu bean eu faço o seguinte naquele método setup

getProcesso().setTipoAcao(new TipoAcao());

depois é só mandar persistir o objeto Processo. Com converters num exemplo tosco.

[code]
public class TipoAcaoConverter implements Converter{

@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
    try {
        TipoAcao tipoAcao = new TipoAcao();
        tipoAcao.setTpaCodigo(new Long(value));

        return tipoAcao;
    } catch (Exception e) {
        return null;
    }
}

@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
    try {
        TipoAcao tipoAcao = (TipoAcao) value;

        return String.valueOf(tipoAcao.getTpaCodigo());
    } catch (Exception e) {
        return null;
    }
}

}
[/code][/quote]

Acho que entendi o seu código mas no meu caso preciso disso tanto para inserir um novo elemento quanto para editá-lo. Por exemplo, imagine que eu persista um novo objeto onde o tipoLogradouro é “AVENIDA”. Quando eu quiser editar esse objeto o SelectOneMenu deve vir preenchido com esse valor para que eu possa mudá-lo de “AVENIDA” para “RUA”. Se entendi bem, a sua solução aborda apenas o momento de inserir um novo objeto no banco, mas não de editá-lo, ou estou errado?

Na hora de editar vc recupera um objeto TipoAcao junto com o objeto Processo, na hora de recuperar passando um id do Processo por exemplo ele já preenche o selectonemenu com o valor do TipoAcao.

Não estou tendo sucesso em usar esse SelectOneMenu. Como uma coisa tão simples consegue ser tão complicada? Pelo menos pra mim…

Mas vms la.

Meu conversor está assim atualmente:

@FacesConverter(value="tipoLogradouroConverter", forClass=TipoLogradouro.class)
public class TipoLogradouroConverter implements Converter {

	@Override
	public Object getAsObject(FacesContext arg0, UIComponent arg1, String valor) {
		
		if (valor != null && !valor.equals("")){
			return new TipoLogradouroDAO().busca(Integer.parseInt(valor));
		}
		
		return null;
	}

	@Override
	public String getAsString(FacesContext arg0, UIComponent arg1, Object objeto) {
		
		if (objeto != null && objeto instanceof TipoLogradouro) {
			return ((TipoLogradouro)objeto).getId().toString();
		}
		
		return null;
	}


}

O trecho de código do SelectOneMenu:

											<h:selectOneMenu  value="#{funcionarioBean.tipoLogradouro}" converter="tipoLogradouroConverter" >
											
												<f:selectItem itemLabel="Selecione um Tipo de Logradouro..." itemValue="" />
												<f:selectItems value="#{funcionarioBean.tiposLogradouros}" var="tipoLogradouro" 
													itemValue="#{tipoLogradouro}" itemLabel="#{tipoLogradouro.descricao}"  />
											
											</h:selectOneMenu>

Meu sistema possui um dataTable que lista todos os funcionários (endereço, tipo logradouro são atributos de funcionário). Abaixo existem dois botões, novo e editar. Eu invoco o botão novo normalmente, o selectOneMenu funciona normalmente e salva o valor selecionado no selectOneMenu. O meu problema está no momento de editar esse campo. Não consigo carregá-lo no momento de editar. Todas as outras informações vem normalmente e consigo carregá-las normalmente, mas o selectOneMenu não consigo.
No botão editar é invocado o seguinte método que :

	public void editar() {		
		
		if (this.pessoa.getEndereco().getTipoLogradouro() != null){
			this.tipoLogradouro = this.pessoa.getEndereco().getTipoLogradouro();
		}
		
	}

desta forma acredito que carrego o tipoLogradouro na minha variável que armazena esse valor na view.

No momento que eu clico dou um render e um update no form que contem esses crud de funcionário.

							<p:commandButton value="Editar Funcionário" action="#{funcionarioBean.editar}" id="editFuncionarioButton" ajax="false" >
								<p:ajax render=":crud-funcionario :listagem" update=":crud-funcionario" />
							</p:commandButton>		

Obrigado.

Você precisa passar para seu selectOneMenu uma lista de “SelectItem”, como abaixo por exemplo

public List<SelectItem> getTipoAcao() throws Exception {
        List<SelectItem> toReturn = new ArrayList<SelectItem>();
        List<TipoAcao> tudo = HibernateUtil.getCurrentSession().createNamedQuery("TipoAcao.FindAllActives").list();
        for (TipoAcao tpa : tudo) {
            toReturn.add(new SelectItem(tpa.getTpaCodigo().toString(), tpa.getDescricao()));
        }
        return toReturn;
    }

daí no XHTML ou JSP o que você estiver usando é só passar

<h:selectOneMenu id="tpa" value="#{processoBean.processo.tipoAcao.tpaCodigo}"
                                         requiredMessage="Preenchimento requerido">	
        <f:selectItem itemLabel="Selecione um Tipo de Ação" itemValue="0" />													
        <f:selectItems value="#{processoBean.tipoAcao}"/>
</h:selectOneMenu>

isso sem conversor, com conversor seria quase o mesmo com a adição do converter

<h:selectOneMenu id="tpa" value="#{processoBean.processo.tipoAcao}"
                                         requiredMessage="Preenchimento requerido" converter="tipoAcaoConverter">	
        <f:selectItem itemLabel="Selecione um Tipo de Ação" itemValue="0" />													
        <f:selectItems value="#{processoBean.tipoAcao}"/>
</h:selectOneMenu>