Posts Tagged ‘validator’

SelectOneMenu + converter = erro de validação

Friday, February 1st, 2008

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 the valid property to false.

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 :D
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.

Método de validação no managed bean

Friday, December 14th, 2007

Já é sabido de todos como é fácil implementar seu próprio custom validator em JSF, mas para quem não lembra segue os passos abaixo:

  1. Crie uma classe que implementa a interface Validator (javax.faces.validator.Validator);
  2. Implemente o método validate;
  3. Registre seu custom validator no faces-config.xml;
  4. Use a tag <f:validator /> no seu jsp;

Como vocês podem ver não há dificuldades em seguir estes passos básicos, contudo há momentos em que não se quer criar uma classe separada que implemente a interface Validator, logo temos uma segunda alternativa -mais simples- em que você pode implementar um método de validação no seu managed-bean contanto que você use a mesma assinatura do método validate da interface Validator. Seria algo como:

[SomeBackingBean.java]

public void validateEmail(FacesContext context,
    UIComponent toValidate, Object value) {
	String email = (String) value;

	if (email.indexOf('@') == -1) {
		((UIInput)toValidate).setValid(false);

		FacesMessage message = new FacesMessage("Invalid Email");
		context.addMessage(toValidate.getClientId(context), message);
	}
}

Este método então seria utilizado dentro de uma tag JSF através do atributo validator como abaixo:

<h:inputText id="email"
	value="#{SomeBackingBean.user.email}"
	validator="#{SomeBackingBean.validateEmail}"
	required="true">
</h:inputText>

Caso o valor do componente inputText seja inválido, ou seja, caso não seja um formato de endereço de e-mail válido, então será adicionado uma mensagem no faces context associado ao componente.

Bem simples não é? Mais fácil e menos trabalhoso que criar uma classe para cada tipo de validação customizada :D Eu aconselho utilizar-se desta segunda alternativa quando se tratar de validações específicas somente a uma view (tela) ou managed-bean, isto é, quando houver a necessidade de um custom validator que será utilizado em vários componentes e/ou telas então é melhor você usar a primeira alternativa implementando a interface Validator para não ter que reescrever código!

Mas fica uma pulga atrás da orelha..

Por que eu estou utilizando os parâmetros do método validateEmail() que estão associados ao componente inputText quando eu poderia utilizar-me diretamente da propriedade #{SomeBackingBean.user.email} já que meu método de validação está dentro do meu managed-bean?

Bem, você não pode fazer isso pois a validação ocorre antes que os valores dos componentes sejam vinculados ao modelo (na fase Update model values), neste momento o modelo é um estado desconhecido no ciclo de vida do JSF que somente será executado posteriormente caso não ocorra erros durante a fase de conversão e validação. Simplesmente pense neste método como sendo executado de uma objeto que implementa a interface Validator.

Como podem ver, o ciclo de vida do JSF é algo importante que temos que entender para podermos trabalhar de maneira mais adequada com a tecnologia.

Para entender melhor sobre converters e validators basta acessar este link !