SelectOneMenu + converter = erro de validação
Isso é um problema bem comum, e para quem está começando com JSF (ou mesmo já é veterano de guerra) acaba perdendo um tempinho considerável para soluciona-lo. Sendo, vou explicar o cenário abaixo, logo vou explanar o problema e finalizo com a solução para o mesmo.
O cenário
Imagine que você queira uma combobox (h:selectOneMenu) para exibir uma lista de municípios (objetos do tipo Municipio) para o usuário, daí quando o usuário selecionar o valor na combo e submeter o formulário este valor selecionado será inserido em um atributo do tipo Municipio no teu managed bean. Até aí tudo, nada de complicado, basta implementar um JSF custom converter, algo como MunicipioConverter, para converter o valor selecionado no cliente (na maioria das vezes é o id do item) para um objeto do tipo Municipio e pronto, resolvido o problema! A questão é que ao submeter o formulário você receberá de cara este erro:
“Validation Error: Value is not valid“
E você não entenderá o porquê dele está ocorrendo já que teu converter, managed bean e página estão corretamente implementados e configurados.
O problema
Bem, já está óbvio que o erro ocorre na Fase de Validação do ciclo de vida do JSF. Na fase de validação o erro ocorre porque a especificação do JSF diz que o componente UISelectOne deve assegurar que o valor selecionado esteja dentro dos valores selecionáveis exibidos ao usuário, ou seja, que o valor selecionado seja igual a um dos valores avaliados, caso isso não ocorra então será ecoado o erro de validação acima.
De acordo com o Javadoc nós temos:
In addition to the standard validation behavior inherited from
UIInput, ensure that any specified value is equal to one of the available options. If it is not, enqueue an error message and set thevalidproperty tofalse.
Sendo mais especifico ainda, o “erro” ocorre quando o componente (UISelectOne) testa a igualdade do teu objeto (aquele retornado pelo converter de Municipio) -que é uma nova instância- com a lista das opções avaliadas, como você não sobreescreveu os métodos equals() e hashCode() da classe Municipio a igualdade entre objetos é feito por referência, um simples "obj1 == obj2", logo por ser uma nova intância (novo endereço de memória) isso retornará false, e então o erro ecoa.
Como podem ver, isso não é erro no teu converter ou configuração da tua aplicação, mas sim uma validação imposta pela especificação. Acredito que isso ocorra para barrar valores inválidos enviados por algum cracker, enfim, é um medida de segurança básica que JSF já te fornece.
A solução
Se você está recebendo este erro provavelmente você chegou a conclusão que o teu objeto convertido e retornado pela tua implementação do custom converter (MunicipioConverter) não pertence a nenhuma das opções avaliadas pelo componente.
Sendo, a solução é simples, sobreescreva os métodos equals() e hashCode() da tua classe Municipio ![]()
Pronto, assim quando entrar na fase de validação o componente chamará estes métodos e encontrará o valor selecionado entre as opções avalidadas.
Concluindo
Bem senhores, este problema ocorre quando utilizamos nossos objetos (tipos) customizados (Municipio, Pessoa, Item etc) e não sobreescrevemos os métodos para comparação entre objetos. Isso não ocorre quando utilizamos tipos primitivos, wrappers, String, BigDecimal, isto é, objetos que possuem os métodos equals() e hashCode() sobreescritos corretamente.
Enfim, isto foi um resumão de como contornar este problema, acredito que muitos já passaram por isso e quem não passou provavelmente irá passar algum dia
Isso é tudo, boa sorte.
Tags: converter, Java, JavaServer Faces, JSF, SelectOneMenu, validator
February 1st, 2008 at 6:37 pm
Valeu Rafael…
Me salou…
February 2nd, 2008 at 3:45 am
Excelente post!! Grande Ponte, mais uma vez dando um show… Eu não sabia desde detalhe… Muito importante saber disto….
Obrigado, forte abraço!
February 24th, 2008 at 9:28 pm
[...] é que enquanto o Tarso estava fazendo os testes e implementando a solução ele caiu naquele velho probleminha do selectOneMenu, não exatamente como no cenário que eu havia blogado, mas o problema e a solução foram a mesma, [...]
February 28th, 2008 at 3:50 am
Ola amigo sou uma leitora assidua do seu blog, segui suas instrucoes mas ainda continuo tomando um erro de validacao,
onde eu posso conseguir um exemplo disso para que posso comparar com meu codigo?
abraços e parabens pelo excelente post.
February 28th, 2008 at 4:54 am
Olá Marcia, obrigado, é muito bom saber disso XD
Poderia me explicar o cenário do teu problema, assim ficaria mais fácil achar o problema? Outra coisa, existe uma excelente lista de discussão sobre JSF, ela é a http://groups.google.com/group/javasf
Caso você não esteja cadastrada nela você poderia se cadastrar, pessoal lá é bem receptivo e sempre tenta ajudar com o que pode.
Abraços e obrigado.
February 28th, 2008 at 7:59 am
meu problema é o seguinte :tenho uma entidade perfil e uma usuario 1:n de perfil p/ usuario mas na hora de salvar o usuario tem que pegar o que foi selecionado no selectone que contem os perfis e salvar como objeto Perfilusuario so que nao estou conseguindo fazer isso olha o codigo:
usuario…>
package com.NASeguranca.Model;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.faces.model.SelectItem;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import com.NASeguranca.Dao.DaoEstado;
import com.NASeguranca.Idao.IDaoEstado;
@Entity @Table(name=”usuario”)
@SequenceGenerator(name = “geraCodCidade”, sequenceName = “geraCodCidade”)
public class Usuario {
@Id @GeneratedValue(strategy = GenerationType.AUTO, generator = “geraCodCidade”)
private int codigousuario;
// private int codigoPerfil;
private String nome ;
private String sobrenome;
private String setor;
private String cargo;
@Temporal(TemporalType.DATE)
private Date dataInclusao= new Date();
private int senha;
private String login;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name=”codigoperfil”,
insertable=true, updatable=true)
@Fetch(FetchMode.JOIN)
@Cascade(CascadeType.SAVE_UPDATE)
private PerfilUsuario perfilusuario;
@OneToMany(mappedBy=”usuario”, fetch = FetchType.LAZY)
@Cascade(CascadeType.ALL)
private Set pedidoCompra;
@OneToMany(mappedBy=”usuario”, fetch = FetchType.LAZY)
@Cascade(CascadeType.ALL)
private Set pedido;
public Usuario(){}
public int getCodigousuario() {
return codigousuario;
}
public void setCodigousuario(int codigousuario) {
this.codigousuario = codigousuario;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getSobrenome() {
return sobrenome;
}
public void setSobrenome(String sobrenome) {
this.sobrenome = sobrenome;
}
public String getSetor() {
return setor;
}
public void setSetor(String setor) {
this.setor = setor;
}
public String getCargo() {
return cargo;
}
public void setCargo(String cargo) {
this.cargo = cargo;
}
public Date getDataInclusao() {
return dataInclusao;
}
public void setDataInclusao(Date dataInclusao) {
this.dataInclusao = dataInclusao;
}
public int getSenha() {
return senha;
}
public void setSenha(int senha) {
this.senha = senha;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public PerfilUsuario getPerfilusuario() {
return perfilusuario;
}
public void setPerfilusuario(PerfilUsuario perfilusuario) {
this.perfilusuario = perfilusuario;
}
public Set getPedidoCompra() {
return pedidoCompra;
}
public void setPedidoCompra(Set pedidoCompra) {
this.pedidoCompra = pedidoCompra;
}
public Set getPedido() {
return pedido;
}
public void setPedido(Set pedido) {
this.pedido = pedido;
}
private List getLista() throws Exception {
List lista = new LinkedList();
Estado uf;
IDaoEstado idao= new DaoEstado();
List resultado = (List) idao.ListarTodos();
for(Estado teste : resultado){
SelectItem item = new SelectItem(teste.getCodigoUf(),teste.getSigla());
lista.add(item);
}
return lista;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + codigousuario;
result = prime * result
+ ((perfilusuario == null) ? 0 : perfilusuario.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Usuario other = (Usuario) obj;
if (codigousuario != other.codigousuario)
return false;
if (perfilusuario == null) {
if (other.perfilusuario != null)
return false;
} else if (!perfilusuario.equals(other.perfilusuario))
return false;
return true;
}
}
PerfilUsuario—>
package com.NASeguranca.Model;
import java.util.Set;
import javax.persistence.*;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
@Entity @Table(name=”perfilUsuario”)
@SequenceGenerator(name = “geraCodPerfil”, sequenceName = “geraCodPerfil”)
public class PerfilUsuario {
@Id @GeneratedValue(strategy = GenerationType.AUTO, generator = “geraCodPerfil”)
private Long codigoperfil;
private String descricao; //adm — super—limitado
@OneToMany(mappedBy=”perfilusuario”, fetch = FetchType.LAZY)
@Cascade(CascadeType.ALL)
private Set usuario;
public PerfilUsuario(){}
public Long getCodigoperfil() {
return codigoperfil;
}
public void setCodigoperfil(Long codigoperfil) {
this.codigoperfil = codigoperfil;
}
public String getDescricao() {
return descricao;
}
public void setDescricao(String descricao) {
this.descricao = descricao;
}
public Set getUsuario() {
return usuario;
}
public void setUsuario(Set usuario) {
this.usuario = usuario;
}
public String toString() {
// Override Object#toString() so that it returns a human readable String representation.
// It is not required by the Converter or so, it just pleases the reading in the logs.
return “PerfilUsuario[" + codigoperfil + "," + descricao + "]“;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((codigoperfil == null) ? 0 : codigoperfil.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final PerfilUsuario other = (PerfilUsuario) obj;
if (codigoperfil == null) {
if (other.codigoperfil != null)
return false;
} else if (!codigoperfil.equals(other.codigoperfil))
return false;
return true;
}
}
controlUsuario—>
package com.NASeguranca.Controller;
import java.util.LinkedList;
import java.util.List;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;
import javax.persistence.Query;
import org.hibernate.Session;
import com.NASeguranca.Dao.DaoEstado;
import com.NASeguranca.Dao.DaoPerfilUsuario;
import com.NASeguranca.Dao.DaoUsuario;
import com.NASeguranca.Idao.IDaoEstado;
import com.NASeguranca.Idao.IDaoPerfilUsuario;
import com.NASeguranca.Idao.IDaoUsuario;
import com.NASeguranca.Model.Estado;
import com.NASeguranca.Model.PerfilUsuario;
import com.NASeguranca.Model.Usuario;
import com.NASeguranca.Util.HibernateUtil;
public class ControlUsuario {
private DataModel model;
private Usuario usuario = new Usuario();
private SelectItem selectedItem = new SelectItem();
// private PerfilUsuario selectedItem;
public List lista = new LinkedList();
public ControlUsuario(){}
public String novo() {
usuario = new Usuario();
return “novoUsuario”;
}
public Usuario getUsuario() {
return usuario;
}
public void setUsuario(Usuario objeto) {
this.usuario = objeto;
}
public DataModel getTodos() {
IDaoUsuario ldao = new DaoUsuario();
model = new ListDataModel(ldao.ListarTodos());
return model;
}
public DataModel getTodosUsuario(){
IDaoUsuario ldao = new DaoUsuario();
model = new ListDataModel(ldao.ListarTodos());
return model;
}
public Usuario getUsuarioFromEditOrDelete() {
Usuario objeto = (Usuario) model.getRowData();
return objeto;
}
public String editar() {
Usuario usuario = getUsuarioFromEditOrDelete();
setUsuario(usuario);
return “editarUsuario”;
}
public String update() {
IDaoUsuario ldao = new DaoUsuario();
ldao.atualizar(usuario);
return “sucessoUsuario”;
}
public String excluir() {
IDaoUsuario ldao = new DaoUsuario();
Usuario usuario = getUsuarioFromEditOrDelete();
ldao.excluir(usuario);
return “sucessoDeleteUsuario”;
}
public String create() {
IDaoUsuario ldao = new DaoUsuario();
ldao.salvar(usuario);
return “sucesso_incUsuario”;
}
// adicionar os itens abaixo como extras
private String keysearch;
public String getKeysearch() {
return keysearch;
}
public void setKeysearch(String keysearch) {
this.keysearch = keysearch;
}
public DataModel getTodaskeysearch() {
IDaoUsuario idao = new DaoUsuario();
model = new ListDataModel(idao.consultar(keysearch));
return model;
}
public DataModel getTodosComNome() {
IDaoUsuario idao = new DaoUsuario();
model = new ListDataModel(idao.ListarTodos());
return model;
}
public javax.faces.model.DataModel getModel() {
return model;
}
public void setModel(javax.faces.model.DataModel model) {
this.model = model;
}
public List getLista() throws Exception {
List lista = new LinkedList();
PerfilUsuario perfil = new PerfilUsuario();
IDaoPerfilUsuario idao = new DaoPerfilUsuario();
List resultado = (List) idao.ListarTudo();
for(PerfilUsuario teste : resultado){
SelectItem item = new SelectItem(teste.getCodigoperfil(),teste.getDescricao());
lista.add(item);
}
return lista;
}
public PerfilUsuario Listar(String value) {
// TODO Auto-generated method stub
Session session = HibernateUtil.getInstance();
PerfilUsuario list =
(PerfilUsuario) session.createQuery(”from PerfilUsuario where codigoperfil= :value”).uniqueResult();
return list;
// return (PerfilUsuario) list;
}
/* public SelectItem getChangeValue() {
return changeValue;
}
public void setChangeValue(SelectItem changeValue) {
this.changeValue = changeValue;
}
*/
public void setLista(List lista) {
this.lista = lista;
}
public SelectItem getSelectedItem() {
return selectedItem;
}
public void SelectItem(SelectItem selectedItem) {
this.selectedItem = selectedItem;
}
}
e o daoUsuario—>
package com.NASeguranca.Dao;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.NASeguranca.Idao.IDaoUsuario;
import com.NASeguranca.Model.Cliente;
import com.NASeguranca.Model.PerfilUsuario;
import com.NASeguranca.Model.Usuario;
import com.NASeguranca.Util.HibernateUtil;
public class DaoUsuario implements IDaoUsuario {
private Session session;
public void atualizar(Usuario objeto) {
session = HibernateUtil.getInstance();
Transaction tx = null;
try {
tx= session.beginTransaction();
session.update(objeto);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx.rollback();
} finally {
session.close();
}
}
public List consultar(String descricao) {
// TODO Auto-generated method stub
session = HibernateUtil.getInstance();
Query query = session.createQuery(
“from Usuario l where l.descricao like :descr” );
List list = query.setString(”descr”, “%”+descricao+”%”).list();
return list;
}
public void excluir(Usuario objeto) {
session = HibernateUtil.getInstance();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.delete(objeto);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx.rollback();
} finally {
session.close();
}
}
public void salvar(Usuario objeto) {
session = HibernateUtil.getInstance();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.save(objeto);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx.rollback();
} finally {
session.close();
}
}
public List ListarTodos() {
// TODO Auto-generated method stub
session = HibernateUtil.getInstance();
List list = session.createQuery(”from Usuario”).list();
return list;
}
}
meu conversor —->
package com.NASeguranca.Util;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import com.NASeguranca.Model.PerfilUsuario;
public class GenericConverter implements Converter{
public Object getAsObject(FacesContext context, UIComponent
component, String value) {
if (value != null) {
System.out.println(value+” esse aqui é o super valor”);
return getAttributes(component).get(value);
}else{
System.out.println(”tudo errado”);
}
return null;
}
public String getAsString(FacesContext context, UIComponent
component, Object value) {
if (value instanceof PerfilUsuario) {
System.out.println(value.getClass());
System.out.println(”3″);
PerfilUsuario vo = (PerfilUsuario) value;
getAttributes(component).put(String.valueOf(vo.getCodigoperfil()),
vo);
return String.valueOf(vo.getCodigoperfil());
}
return “0″;
}
private Map getAttributes(UIComponent component) {
return component.getAttributes();
}
}
ja tentei de td mas nao consegui vc pode me ajudar?
há desculpe por postar este monte de código aqui, abraços.
March 2nd, 2008 at 7:14 am
Olá Marcia, fica complicado ler essa enorme quantidade de código, eu até tentei, mas fica complicado
Deixa eu ver se entendi, você montou uma combo-box com objetos do tipo PerfilUsuario, certo? E quando você seleciona você quer que o valor selecionado seja do tipo Usuário? É isso?
Abraços.
March 19th, 2008 at 2:19 pm
Opa Ponte, tudo beleza? A pergunta é, e qual seria uma boa solução quando preciso criar options dinâmicos através de javascript.
March 20th, 2008 at 5:16 am
Bem, ao se trabalhar com JSF tem que ter em mente que o que importa é o estado dos componentes no servidor, o que você faz no cliente (como criar inputs dinamicos, hiddens, combo-boxes etc) não refletem na árvore de componentes do JSF. Então para criar options dinâmicas para teu componente h:selectOneMenu por exemplo você teria que alterar o estado deste componente também no servidor. Uma solução seria através do Ajax4jsf, assim depois de inserido um novo item você reRenderizaria o componente.
Alguns componentes como o t:pickList do Tomahawk consegue fazer isso no cliente (browser) pois o componente foi preparado para isso.
Outra maneira, mas não a recomendada seria obter os valores através do proprio request, porém assim você estaria abrindo mão das vantagens do JSF.
Enfim, existem algumas soluções, outras simples e outras mais “fora do convencional com JSF”, escolher a melhor depende da tua necessidade e prazo
Mas por que você iria querer gerar options dinâmicos?
March 20th, 2008 at 7:02 am
Rapaz, estamos desenvolvendo um sistema com diversos casos de uso não muito convencionais. Na verdade eu vou ter diversos combos com options dinâmicos, pois o caso de uso é um mestre detalhe com sub detalhe, os valores do combo vão está no intervalo de 0..Maior número digitado em vários inputText. Ainda ontem resolvi dessa maneira: ao digitar o número em algum desses inputText é feito uma chamada ao um método no servidor que verifica se o valor digitado é maior do que os demais e depois é renderizado os combos.
Valeu
April 14th, 2008 at 1:02 pm
Rafael, parabéns! Seu blog foi o único site que eu encontrei a solução para este problema. Em nenhum outro atentaram para fato de que o problema estava em como funciona o método equals().
June 11th, 2008 at 5:27 am
POxa, tô começando JSF eu meio q tô boiando em Converter…..
Teria como voce me mandar por e-mail um exemplo só pra eu ver como é a implementação….
July 22nd, 2008 at 6:08 am
Olá vc poderia mostrar um exemplo da utilizacao desse selectOneMenu com a tag saveState do tomahawk, nao sei como resolver o problema usando essa tag. Apesar de ter encontrado vários posts dizendo isso.
Sou iniciante em JSF e tenho uma nocao pequena da sintaxe e do funcionamento do framework.
Obrigada!
July 25th, 2008 at 7:58 am
Cara, após 3 dias apanhando com esse problema, encontrei o seu post através do Google.
Em 5 minutos resolvi o problema.
Excelente post !
July 26th, 2008 at 11:16 am
[...] favor, não esqueçam de implementar os métodos equals e hashCode, evitemos cair naquele velho probleminha, [...]