Posts Tagged ‘converter’

O que todo bom desenvolvedor JSF deveria saber

Monday, January 19th, 2009

Dificuldades todos temos, principalmente quando estamos aprendendo uma nova tecnologia, framework ou paradigma, e isso torna-se ainda pior quando não temos qualquer interesse em aprender sobre os mesmos.

Não é de hoje que percebo que a maioria das dúvidas postadas sobre JSF em listas de discussão e fóruns estão intimamente ligadas a falta de conhecimento base sobre a tecnologia ou mesmo sobre os conceitos na qual o framework (JSF) foi construído - sim, estou falando de conceitos sobre desenvolvimento web e component-based.

É óbvio que JSF existe para tentar abstrair toda a complexidade existente no desenvolvimento web Java, complexidade esta que deveríamos evitar, mas é indispensável termos o mínimo de conhecimento sobre a mesma. Pois se você acha que apenas conhecendo os componentes você construirá uma aplicação decente então você está enganado.

Assusto-me com o número de dúvidas na javasf em que o mínimo de conhecimento sobre web seria suficiente para solucionar o problema, independente do framework utilizado. Evidente que muitas das dúvidas estão ligadas a tecnologia, JSF no caso, mas estes mesmos problemas também seriam facilmente resolvidos se os desenvolvedores se dessem ao trabalho de tentar entender os fundamentos do framework.

Quando falo dos fundamentos de JSF eu estou me referindo ao ciclo de vida, comunicação entre managed beans, escopos de conversação, árvore de componentes, a idéia por trás dos converters e validators, phase listeners, modelo dos componentes etc.

Desenvolver aplicações JSF guiado por “brute-force” é algo que todo desenvolvedor deveria evitar, de fato, ver um exemplinho (ou demo) básico de um componente, copiar o código, joga-lo na aplicação e ainda por cima achar que deveria funcionar é algo ingénuo demais até mesmo para um programador iniciante.

Pior que isso é continuar tentando fazer o componente ou bloco de código funcionar a todo custo sem nem ao menos ler a documentação do mesmo [por isso o termo "brute-force"].

Não adianta correr para tentar usar os componentes se você não possui os fundamentos básicos sobre JSF ou mesmo sobre desenvolvimento web.

Saber o básico é importante

Saber o básico de qualquer tecnologia é obrigatório, e com JSF não seria diferente. Acredito que em primeiro lugar é importante entender o que é JSF e qual a diferença entre uma implementação e um conjunto de componentes.

Criar e configurar um projeto web JSF faz parte do aprendizado, independente da IDE, claro. Como disse, o interessante é não depender exclusivamente da IDE, mas sim conhecer o mínimo necessário para configurar e rodar um projeto com faces (como as libs necessárias e configuração do web.xml).

Entenda que um managed bean é apenas um POJO e que deveria ser o mais simples possível (refletindo estritamente o necessário da GUI). E por favor, coloque uma vez por todas na sua cabeça que os componentes (através de EL) acessam os valores dos managed beans através dos métodos assessores (getters e/ou setters) e não das propriedades.

Depois disso eu aconselharia o entendimento sobre converters e validators, pois eles tornam o desenvolvimento bem mais simples se comparado a outros frameworks MVC. Principalmente os converters, pois eles sim “quebram um galho” danado!

Entenda como o framework e os componentes disparam/tratam os eventos e listeners, e principalmente quando utiliza-los, pois utilizar um listener incorreto para um determinado cenário pode dificultar muito a sua vida, mesmo que no final a coisa funcione. Praticamente tudo o que você precisa saber estará na documentação do componente.

Procure entender o sistema de regras de navegação entre páginas do JSF, principalmente quando e porquê você deveria usar redirect ao invés de um simples forward. E claro, lembre-se que JSF trabalha (submete formulários) apenas com o método POST, não com GET.

Quando as páginas tornam-se grandes e até complexas vale a pena organizar os blocos de componentes através de componentes “maiores”, ou seja, componentes do tipo naming containers, com eles se torna possível manipular os componentes tanto no lado cliente quanto no lado servidor de forma mais simples.

Os fundamentos chaves do framework também fazem parte do conhecimento básico, ou você acha que apenas saber utilizar os componentes, navegar entre páginas e criar seu próprio validator são suficientes?

Sendo, é muito importante que você os compreenda da melhor forma possível. Provavelmente alguns deles você terá alguma dificuldade de entender no inicio, isto é normal, mas é interessante que você ao menos conheça superficialmente cada um deles.

Comece entendendo como e quando a árvore de componentes é gerada (está muito ligado ao ciclo de vida), busque entender como o JSF persiste o estado da árvore de componentes entre requests e porque utilizar JSTL no momento certo é importante.

Os passos necessários para estender ou construir seu próprio componente é algo também importante, mesmo que você não seja um expert nisso, conheça estes passos. Eu particularmente nunca tive a necessidade de criar um componente, apenas estendi um [uma única vez], e com a gama de componentes hoje em dia eu acho muito difícil -mas não improvável- você precisar criar seu próprio componente.

Um fundamento extremamente importante é conhecer as maneiras como os objetos e managed beans se comunicam entre si numa aplicação JSF, pois sem este conhecimento provavelmente você cometeria erros e subutilizaria o framework na sua aplicação.

E por fim, o mais importante fundamento, sem sombra de dúvidas, é entender o ciclo de vida (lifecycle) das requisições no JSF. Procure entende-lo muito bem, pois assim você evitará horas quebrando a cabeça com problemas relativamente simples (quem nunca teve dificuldades de entender como funciona o atributo immediate?).

Vale salientar que independente do(s) conjunto(s) de componentes que você pretenda utilizar os conceitos acima te permitirão trabalhar de forma prática, correta e produtiva.

Mas vá além do básico

Os conhecimentos básicos são obrigatórios quando se pretende desenvolver qualquer aplicação com JSF, por menor que ela seja, mas a partir do momento que se desenvolve aplicações maiores e até complexas se faz necessário adotar certos frameworks, abordagens e até conhecer ou saber que existem algumas soluções mais interessantes.

Assim como a maioria dos frameworks web, JSF trabalha com páginas, muitas páginas para falar a verdade, e isto nos leva a necessidade de um framework para templating. E sempre que falamos em um framework com esta finalidade para JSF nós não devemos esquecer: Aplicações sérias em JSF usam Facelets.

Para quem ainda acha que deve criar seu converter apenas para formatar e remover máscaras de strings deve se interessar por abordagens mais práticas e produtivas de se trabalhar com componentes e conversão de entidades de domínio da aplicação.

Um dos grandes problemas para quem está começando com JSF e componentes AJAX é a maneira como estes desenvolvedores subutilizam os recursos AJAX do framework e dos componentes. Sendo, vale a pena estudar e adotar uma abordagem mais eficiente para isso, uma abordagem com foco em navegação orientada a estados.

Uma abordagem orientada a estados é realmente eficaz e produtiva, mas em certos momentos precisamos “tunar” as requisições AJAX dos componentes ou mesmo diminuir os gargalos em determinadas páginas, buscar melhores práticas para os conjuntos de componentes e/ou frameworks AJAX adotados para um projeto é dever do desenvolvedor, sejam estas práticas para JBoss Richfaces, Myfaces Trinidad ou qualquer outro.

A Expression Language (EL) do JSF por padrão ainda é bem limitada, porém muito extensível, e muitas vezes precisamos de features bem mais arrojadas, logo, não podemos abrir mão de extensões como a JBoss EL para que assim evitemos implementar soluções bizarras.

Como havia dito antes, JSF trabalha apenas com POST, porém algumas vezes precisamos submeter formulários via GET ou disponibilizar links (bookmarking) que acessam recursos ou executam algum método no managed bean antes de renderizar uma página. E antes que você implemente seu próprio phase listener ou alguma solução nada elegante eu indico a você o framework Restfaces.

Nem todas as aplicações web necessitam de um mecanismo arrojado de segurança, muitas vezes uma simples página de login para autenticação e um Servlet Filter (ou phase listener) para autorização de recursos resolve o problema. Mas qualquer coisa além disso você deveria, obrigatoriamente, partir para um framework especializado no assunto: como o Spring Security ou JAAS.

Já cansei muito de bater na tecla que não existe arquitetura de referência (aka bala-de-prata) para solucionar todos os problemas de softwares, já discuti muito isso na lista do javasf, cejug e já comentei sobre isso em vários posts meus e de colegas.

Mas também sei que muitos desenvolvedores/arquitetos tem dificuldades em dar inicio numa arquitetura base que se integre bem com JSF, desta forma, eu aconselho a leitura de dois excelentes posts do Cagatay como referência básica. 1) sobre a integração de JSF+JPA+Spring com o uso massivo de anotações e 2) com a integração anterior mais o Spring Security e Myfaces Orchestra.

Se por algum motivo você não gosta ou não se dá bem com Spring então vale a pena dar uma olhada em um projeto recente de integração entre JSF e Guice, o Guicesf. O Guicesf foi uma iniciativa do Volnei, um dos membros do JavaServer Faces International Group (javasf), e que está caminhando muito bem diga-se de passagem.

Ainda assim, acredito que se você tem tempo para estudar e levantar uma arquitetura para seu projeto então corra e dê uma olhada no JBoss Seam, pois todos os problemas (escopos conversacionais, LazyInitializationException, bookmarking etc) que você encontrará com JSF e solucionará com a integração de alguns frameworks e componentes você com toda certeza poderá ter a mesma solução muito mais simples e muito melhor implementada no JBoss Seam, ou seja, soluções prontas!

Concluindo

A maior parte do conhecimento que você precisa para se tornar um bom desenvolvedor JSF está ligada aos fundamentos do framework, e aos fundamentos sobre desenvolvimento web. Com estes conhecimentos alinhados você não terá muitas dificuldades.

Além do mais, não se assuste se você tiver alguma dificuldade no inicio, principalmente se você já trabalhou com algum framework action-like, como Struts ou Webwork, pois a maioria dos desenvolvedores vindos do “mundo” action-like são os que mais possuem dificuldades em desenvolver aplicações com JSF.

Não pare por aqui, continue pesquisando e estudando sobre a tecnologia, principalmente sobre as novidades do JSF2.0 que estão por vir. Se você já possui experiência com JSF integrado a outros frameworks então aproveite seu tempo e dê uma chance ao JBoss Seam, pois sem dúvida alguma ele está trazendo grande produtivade no desenvolvimento “Enterprisey”.

Enfim, o intuito do post é abrir os olhos de novos desenvolvedores -e até de alguns veteranos- sobre como desenvolver melhor com JSF, e claro, disponibilizar fontes de estudos (quase todos os links levam a outros posts).

No final de tudo, este post nada mais é do que a minha opinião.

Entity Converters pra dar e vender

Saturday, July 26th, 2008

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.