Entity Converters pra dar e vender

Uma coisa que sempre aconselho aos desenvolvedores é que tentem sempre que possível trabalhar em JSF diretamente com os objetos como se estivessem em um ambiente stateful, pois um dos objetivos da tecnologia é tentar abstrair a natureza stateless do HTTP. Não que seja algo tão simples de se fazer algumas vezes, mas não é tão complexo ao ponto de abrir mão desta abstração.

Pensando no que eu disse acima, um dos problemas comuns e chatinhos quando se trabalha com SelectOneMenu (ou SelectManyMenu) e entidades em JSF ocorre quando queremos que o value do nosso SelectItem seja a própria entidade, e não o “id” da mesma. Bem, o que estou querendo dizer é exatamente isso:

public List<SelectItem> getEmpresas() {
	List<SelectItem> items = new ArrayList<SelectItem>();
	for (Empresa e : this.empresas) {
		// observem que o value do meu SelectItem é a própria entidade
		items.add(new SelectItem(e, e.getNome()));
	}
	return items;
}

Isso acaba se tornando trabalhoso pois somos obrigados a implementar um converter para cada entidade, o que particularmente eu não gosto. Levando em consideração que não queremos criar um converter para cada entidade, quais outras soluções temos?

Cenário

Antes de explicar cada solução, vou demonstrar um cenário qualquer para facilitar o entendimento, ou seja, vou demonstrar os artefatos necessários, eles seguem abaixo:

1) Nosso managed bean

public class EmpresaBean {

	private Empresa selectedEmpresa;
	private List<Empresa> empresas = new ArrayList<Empresa>();

	public EmpresaBean() {
		empresas.add(new Empresa(7, "Triadworks"));
		empresas.add(new Empresa(88, "Ivia"));
		empresas.add(new Empresa(921, "Thoughtworks"));
		empresas.add(new Empresa(15, "Caelum"));
		empresas.add(new Empresa(2, "ImproveIT"));
	}

	public List<Empresa> getEmpresas() {
		return empresas;
	}
	public Empresa getSelectedEmpresa() {
		return selectedEmpresa;
	}
	public void setSelectedEmpresa(Empresa selectedEmpresa) {
		this.selectedEmpresa = selectedEmpresa;
		System.out.println("Empresa selecionada: " + selectedEmpresa.getNome());
	}
}

2) Nossa entidade (já implementando a interface BaseEntity para a solução do SimpleEntityConverter)

public class Empresa implements BaseEntity, Serializable {

	private static final long serialVersionUID = 1L;

	private Integer codigo;
	private String nome;

	public Empresa(Integer codigo, String nome) {
		this.codigo = codigo;
		this.nome = nome;
	}

	public Long getId() {
		return new Long(codigo);
	}

	// Métodos getters e setters
	// Não esquecer os métodos equals e hashCode
}

Por favor, não esqueçam de implementar os métodos equals e hashCode, evitemos cair naquele velho probleminha, ok?

3) Nosso formulário

<h:form id="form">
	<h:panelGrid columns="2" border="1">
		<h:outputLabel value="Empresa" for="empresa"/>
		<h:column>
			<h:selectOneMenu id="empresa"
				value="#{empresaBean.selectedEmpresa}"
				converter="simpleEntityConverter" required="true"
				requiredMessage="Valor é obrigatório">
				<f:selectItem itemValue="" itemLabel="Selecione uma empresa"/>
				<t:selectItems value="#{empresaBean.empresas}" var="o" itemLabel="#{o.nome}" itemValue="#{o}"/>
			</h:selectOneMenu>
			<br/>
			<h:message for="empresa" errorStyle="color:darkred;font-size:11px;"></h:message>
		</h:column>
		<f:facet name="footer">
			<h:commandButton value="Submit"/>
		</f:facet>
	</h:panelGrid>
</h:form>

Reparem que nosso componente h:selectOneMenu possui um converter declarado, ou seja, será necessário alterar o converter de acordo com a solução escolhida.

Nada de complexo, certo? É um básico cenário comum.
Então vamos as possíveis soluções :)

EntityConverter

O componente/feature EntityConverter foi desenvolvido pelo Rogério Araújo, que é um dos coordenadores do JavaServer Faces International Group. O componente em si foi pensado inicialmente para o cenário em que é necessário carregar a entidade de um banco de dados, mas isso não impede que a entidade seja obtida de qualquer outro recurso externo, como um web service ou EJB, por exemplo.

O componente funciona perfeitamente bem e é muito simples de configurar, porém ainda assim eu o acho interessante somente em alguns casos específicos, como no caso em que a entidade precisa ser carregada do banco de dados com todos seus atributos e/ou relacionamentos ao submeter o formulário, evitando-se assim onerar o servidor com grandes entidades em memória.

Alguns desenvolvedores reclamam porque o componente -idealmente- efetua uma requisição ao bando de dados para obter a entidade, mas venhamos e convenhamos, isso não deveria ser uma preocupação, principalmente para quem vem de algum framework “action-like” como o Struts. Além do mais, se você possui algum recurso de cache na camada de persistência -como o fornecido pelo Hibernate– não deveria se preocupar tanto com isso.

Eu não pretendo explicar como configurar o componente, pois na wiki do Myfaces você tem tudo que precisa saber, além do mais o criador do componente é coordenador da Javasf e é brasileiro :)

SimpleEntityConverter

Esta solução foi baseada no caso mais comum de um converter para cada entidade, a única diferença é que tentei torna-la “genérica” para qualquer entidade, evitando-se escrever um converter para cada entidade (é esta nossa intenção, certo?).

Sendo, segue abaixo o código do nosso converter:

public class SimpleEntityConverter implements Converter {

	public Object getAsObject(FacesContext ctx, UIComponent component, String value) {
		if (value != null) {
			return this.getAttributesFrom(component).get(value);
		}
		return null;
	}

	public String getAsString(FacesContext ctx, UIComponent component, Object value) {

		if (value != null
				&& !"".equals(value)) {

			BaseEntity entity = (BaseEntity) value;

			// adiciona item como atributo do componente
			this.addAttribute(component, entity);

			Long codigo = entity.getId();
			if (codigo != null) {
				return String.valueOf(codigo);
			}
		}

		return (String) value;
	}

	protected void addAttribute(UIComponent component, BaseEntity o) {
		String key = o.getId().toString(); // codigo da empresa como chave neste caso
		this.getAttributesFrom(component).put(key, o);
	}

	protected Map<String, Object> getAttributesFrom(UIComponent component) {
		return component.getAttributes();
	}

}

Como podem ver, o que basicamente ocorre é que armazenamos as entidades como atributos do nosso componente no método getAsString(), e recuperamos a entidade correta através da chave (neste caso o Id) associada a ela, no método getAsObject(). Mas quem é este BaseEntity?

public interface BaseEntity {

	public Long getId();

}

BaseEntity é uma interface com um método em comum entre as entidades, ou seja, nossas entidades precisarão implementar esta interface para que nosso converter funcione de acordo.

A solução é funcional, porém há algumas ressalvas que gostaria de explicitar. 1) Eu particularmente não gosto da idéia de “sujar” minhas entidades estendendo alguma interface ou classe para poupar código ou resolver problemas não-funcionais, mas isso é uma opinião minha. 2) Outro problema que vejo é que este converter acaba consumindo um pouco mais de memória no servidor por você está alterando o estado do componente, principalmente se sua lista de itens for muito grande, mas não é nada que você deva se preocupar inicialmente, ou seja, somente esquente a cabeça com isso se for realmente necessário.

SimpleIndexConverter

Esta solução eu tomei emprestada dos componentes do Myfaces Trinidad, que por sinal é excelente. A diferença é que eu a simplifiquei um pouco (o código para ser mais exato), logo ela não está tão robusta quanto a original (que se preocupa com casos mais específicos), mas funciona muito bem na maioria dos casos.

Sua idéia principal é utilizar o index da lista de items como chave para cada entidade, assim o que submetemos no formulário é o index da lista, e não um valor (Id?) da entidade como de costume.

O código do converter ficou grandinho, mas é possível enxuga-lo movendo alguns métodos para alguma classe utils. Segue abaixo o código do nosso SimpleIndexConverter:

public class SimpleIndexConverter implements Converter {

	private int index = -1;

	/* (non-Javadoc)
	 * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
	 */
	public Object getAsObject(FacesContext ctx, UIComponent component, String value) {

		SelectItem selectedItem = this.getSelectedItemByIndex(component, Integer.parseInt(value));
		if (selectedItem != null)
			return selectedItem.getValue();

		return null;
	}

	/* (non-Javadoc)
	 * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
	 */
	public String getAsString(FacesContext ctx, UIComponent component, Object value) {
		index++;
		return String.valueOf(index);
	}

	/**
	 * Obtem o SelecItem de acordo com a opção selecionada pelo usuário
	 */
	protected SelectItem getSelectedItemByIndex(UIComponent component, int index) {

		List<SelectItem> items = this.getSelectItems(component);
		int size = items.size();

		if (index > -1
				&& size > index) {
			return items.get(index);
		}

		return null;
	}

	protected List<SelectItem> getSelectItems(UIComponent component) {

		List<SelectItem> items = new ArrayList<SelectItem>();

		int childCount = component.getChildCount();
	    if (childCount == 0)
	      return items;

	    List<UIComponent> children = component.getChildren();
		for (UIComponent child : children) {
			if (child instanceof UISelectItem) {
				this.addSelectItem((UISelectItem) child, items);
			} else if (child instanceof UISelectItems) {
				this.addSelectItems((UISelectItems) child, items);
			}
		}

		return items;
	}

	protected void addSelectItem(UISelectItem uiItem, List<SelectItem> items) {

		boolean isRendered = uiItem.isRendered();
		if (!isRendered) {
			items.add(null);
			return;
		}

		Object value = uiItem.getValue();
		SelectItem item;

		if (value instanceof SelectItem) {
			item = (SelectItem) value;
		} else {
			Object itemValue = uiItem.getItemValue();
			String itemLabel = uiItem.getItemLabel();
			// JSF throws a null pointer exception for null values and labels,
			// which is a serious problem at design-time.
			item = new SelectItem(itemValue == null ? "" : itemValue,
					itemLabel == null ? "" : itemLabel, uiItem
							.getItemDescription(), uiItem.isItemDisabled());
		}

		items.add(item);
	}

	@SuppressWarnings("unchecked")
	protected void addSelectItems(UISelectItems uiItems, List<SelectItem> items) {

		boolean isRendered = uiItems.isRendered();
		if (!isRendered) {
			items.add(null);
			return;
		}

		Object value = uiItems.getValue();
		if (value instanceof SelectItem) {
			items.add((SelectItem) value);
		} else if (value instanceof Object[]) {
			Object[] array = (Object[]) value;
			for (int i = 0; i < array.length; i++) {
				// TODO test - this section is untested
				if (array[i] instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) array[i], items);
				} else {
					items.add((SelectItem) array[i]);
				}
			}
		} else if (value instanceof Collection) {
			Iterator<SelectItem> iter = ((Collection<SelectItem>) value)
					.iterator();
			SelectItem item;
			while (iter.hasNext()) {
				item = iter.next();
				if (item instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) item, items);
				} else {
					items.add(item);
				}
			}
		} else if (value instanceof Map) {
			for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) {
				Object label = entry.getKey();
				SelectItem item = new SelectItem(entry.getValue(),
						label == null ? (String) null : label.toString());
				// TODO test - this section is untested
				if (item instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) item, items);
				} else {
					items.add(item);
				}
			}
		}
	}

	protected void resolveAndAddItems(SelectItemGroup group, List<SelectItem> items) {
		for (SelectItem item : group.getSelectItems()) {
			if (item instanceof SelectItemGroup) {
				resolveAndAddItems((SelectItemGroup) item, items);
			} else {
				items.add(item);
			}
		}
	}

}

Não há muito a se explicar sobre o converter acima, a “mágica” toda está nos métodos auxiliares (que por sinal maior parte do código foi retirado do Trinidad, porém enxugado para nossas necessidades).

Este converter diferentemente do SimpleEntityConverter não altera o estado do componente, evitando assim a utilização de mais memória, ele busca as entidades -que já existem- dentro do componente (SelectOneMenu?) como estado do mesmo.

Assim como os outros converters, este também possui algumas ressalvas. O que podemos citar é 1) Por ele se utilizar do index da lista de items talvez não seja possível trabalhar com o componente no lado cliente (javascript) se você depender dos valores (Id?) de cada item.

Concluindo

Dizer que não é possível simplificar a vida com JSF é mentir, a tecnologia te fornece recursos para abstrair a natureza stateless do HTTP, e é interessante que se aproveite destes recursos.

As soluções explanadas acima não são únicas, existem outras com toda certeza, eu tentei demonstrar algumas delas quando não se tem algum framework (Seam?) ou conjunto de componentes (Trinidad?) para nos auxiliar, ou seja, quando contamos apenas com a implementação do JSF, o que -acredito eu- na maioria dos casos é o que ocorre.

É possível estende-las e até melhora-las, você é livre para isso, e dependendo da tua necessidade provavelmente será o melhor caminho, só não deixe de contribuir com o código para a comunidade.

Enfim, todas as soluções são plausíveis e funcionam para a maioria dos cenários, porém em determinados cenários cada uma se adequa melhor, resta a você analisar e ver qual se encaixa nas tuas necessidades. Além do mais, elas não são mutuamente excludentes.

Aproveitando os beans do Spring em suas páginas JSF

Esses dias estava tendo uma conversa bem produtiva com um amigo de trabalho, Tarso Bessa, o cara é muito safo em desenvolvimento e sempre me aparece com umas idéias bem bacanas. Nessa conversa ele chegou com uma idéia bem interessante sobre aproveitarmos a integração do Spring com JSF, a idéia se resume em aproveitar o variable resolver do Spring nas páginas JSF, assim poderíamos acessar qualquer bean gerenciado pelo Spring (Service, DAO, Repository, whatever) nas páginas através de JSF EL sem a necessidade de intermediar o acesso à este bean com um managed bean do JSF.

O problema

O problema realmente surgiu enquanto o Tarso implementava um mecanismo de CRUD genérico para nossa aplicação, foi aí que começamos a discutir sobre o problema e em seguida a solução.

Na verdade, no projeto que estamos desenvolvendo nós utilizamos muitas combo-boxes (tr:SelectOneChoice pois utilizamos o Myfaces Trinidad) nas nossas páginas e ter que criar um ou mais métodos nos nossos managed beans somente para popular essas combos com dados trazidos diretamente do banco de dados -sem qualquer lógica-, além de ser uma tafera repetitiva, não era nada cômodo.

No final das contas só queríamos não escrever mais código repetitivo nos nossos managed beans (principalmente os que estendiam o nosso CRUD genérico) simplemente para podermos acessar nas páginas uma lista de objetos trazidos do banco de dados.

A solução

Com a integração do Spring com JSF é possível fazer injeção de dependências (DI) dos beans do Spring dentro dos managed beans da aplicação pelo faces-config.xml, algo como:

[faces-config.xml]

<managed-bean>
    <managed-bean-name>bacalhauBean</managed-bean-name>
    <managed-bean-class>bean.faces.BacalhauBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>someService</property-name>
        <property-class>model.service.SomeService</property-class>
        <value>#{someService}</value>
    </managed-property>
</managed-bean>

Imaginem que nós tenhamos um bean chamado “someService” configurado no spring-config.xml, ok?
Agora observem que o bean gerenciado pelo Spring é resolvido através da EL do JSF, #{someService}, então por que não utilizar-se disso para acessarmos diretamente este bean dentro das páginas? 😀
Será isso que iremos fazer logo abaixo!

Imagine que nossa implementação da interface SomeService seja algo do tipo:

[SomeServiceImpl.java]

public class SomeServiceImpl implements SomerService {

    // outros métodos deste serviço

    public List<Foo> findAll() {
        // executa a pesquisa através de um DAO, ou repositório, whatever..
    }

    public List<Foo> getAll() {
        // faz chamada ao método findAll
        return this.findAll();
    }
}

Assim no código da página nós precisaríamos fazer apenas isto:

[foo.jsp]

<h:selectOneMenu id="fooId" value="#{bacalhauBean.fooId}">
    <t:selectItems value="#{someService.all}" var="o" itemValue="o.id" itemLabel="o.name" />
</h:selectOneMenu>

Observem que estou passando a lista de objetos para o componente através da EL #{someService.all} que reflete o método SomeServiceImpl.getAll() do bean gerenciado pelo Spring, atentem também que estou utilizando o componente t:selectItems do Myfaces Tomahawk para que eu possa iterar uma lista de objetos comuns, pois sem ele seria necessário passar uma lista de SelectItem’s, o que não ficaria legal nas nossas classes de serviços.

O melhor de tudo é que não há a necessidade de utilizar um managed bean para intermediar a lista de dados para o componente, o EL do JSF acha o bean “someService” no contexto do Spring e faz a chamada ao método para você (é necessário que o método comece com “get” para que a EL consiga executar corretamente, por isso criamos um método chamado getAll() na classe do SomeServiceImpl.java).

Graças ao variable resolver do Spring você poderá utilizar o #{someService.all} em qualquer componente que espere uma lista de objetos, como um h:dataTable, h:selectManyMenu ou qualquer outro. Além do mais você poderá obter um objeto qualquer como um entity, ou um mapa, ou uma lista ordenada e/ou filtrada diretamente dos beans gerenciados pelo Spring e utilizá-los nas tuas páginas sem dificuldades.

Uma coincidência é 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, e acabamos resolvendo este problema rapidamente sem perder tempo. É como eu disse no final daquele post, “[…] quem não passou provavelmente irá passar algum dia.” :)

O mais engraçado..

O mais engraçado disso tudo é que já temos tudo isso pronto e muito melhor implementado com o JBoss Seam, o Seam estendeu a EL do JSF e com isso conseguimos acessar qualquer componente no seu contexto através de EL, como DAOs, repositorios, serviços etc, e o melhor é que a sua EL estendida nos fornece a possibilidade de executar métodos parametrizados, ou seja, com ele podemos ter algo como isto:

<h:commandButton action="#{hotelBooking.bookHotel(hotel, user)}" value="Book Hotel"/>

Simplesmente fantástico!
Enfim, para quem não tem o JBoss Seam no projeto, pode ter um pouco do gostinho dele com o Spring.

Concluindo..

Como achei o problema e a solução interessantes eu quis compartilhar com os pouquíssimos leitores deste blog, fica aí a dica senhores.
Valeu Tarso Bessa, valeu gente.

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