Archive

Archive for May, 2009

URLs amigáveis no JSF 2.0

May 27th, 2009

Hoje vou falar de mais uma novidade do JSF 2, cuja falta era motivo de muita reclamação: URLs amigávies, bookmarking, método GET e outros nomes que podemos dar. O suporte a essa nova funcionalidade é dado por dois pares de componentes, de um lado h:button e h:link e do outro f:metadata e f:viewParam.

Os componentes h:button e h:link servem para originar as ações jsf assim como os componentes h:command{Button|Link}, porém usando GET em vez de POST. Esses componentes possuem um atributo chamado outcome, que representa a regra de navegação do JSF, assim como seria colocar uma String diretamente na action do h:command{Button|Link}. Para passar parâmetros colocamos a tag f:param dentro do h:link ou h:button.

Como exemplo vamos ver uma aplicaçãozinha que tem uma tela de listagem e outra de visualização de Pessoas. A seguir um trecho da página de listagem de pessoas, listarPessoas.xhtml:

<h:form>
	<h:dataTable value="#{pessoaController.pessoas}" var="pessoa">
		<h:column>
			#{pessoa.nome}
		</h:column>
		<h:column>
			<h:link outcome="/verPessoa" value="Editar">
				<f:param name="pessoa" value="#{pessoa.nome}"/>
			</h:link>
		</h:column>
	</h:dataTable>
</h:form>

Nesse exemplo, no outcome eu já estou usando o esquema novo de navegação comentado no post passado. O link acima vai gerar uma url parecida com http://localhost:8080/exemplojsf2/verPessoa.jsf?pessoa=fulano_0.

Agora do lado da página que recebe a requisição temos os componentes f:metadata e f:viewParam. A primeira é apenas uma tag que engloba as f:viewParam. Já as tags f:viewParam se comportam de forma muito parecida com um h:inputText, podemos dizer que praticamente a única diferença é no input a gente digita em um formulário, enquanto na f:viewParam escrevemos na URL.

A tag f:viewParam possue os seguintes atributos: converter, converterMessage, required, requiredMessage, validator, validatorMessage, value, valueChangeListener, maxlength e for (este último voltado para o novo esquema de component composition do Facelets). Como podemos ver, é praticamente um h:inputText.

Vamos ver então um trecho do código da página que recebe a requisição, verPessoa.xhtml:

<f:view>
	<f:metadata>
	    	<f:viewParam name="pessoa" value="#{pessoaController.pessoaSelecionada}" />
   	</f:metadata>
<h:head>
    <title>Ver Pessoa</title>
</h:head>
<h:body>
	<h:form>
		Nome: #{pessoaController.pessoaSelecionada.nome}
	</h:form>
</h:body>
</f:view>

Não precisei colocar um converter no f:viewParam pois configurei um converter “forClass” como veremos mais a frente.

Agora a nossa classe

public class Pessoa {
 
	private String nome;
 
	public Pessoa() {
	}
	public Pessoa(String nome) {
		this.nome = nome;
	}
 
	//getter e setter suprimido
}

Isso mesmo, a classe é complexa desse jeito  :D

Como o intuito é só um exemplo, eu nem me preocupei com banco de dados ou algum mecanismo mais interessante, apenas criei um conversor para mostrar que o novo esquema não permite apenas o uso de Strings. Segue o código do conversor:

@FacesConverter(forClass=Pessoa.class)
public class PessoaConverter implements Converter {
 
	@Override
	public Object getAsObject(FacesContext context, UIComponent component, String string) {
 
		return new Pessoa(string);
	}
 
	@Override
	public String getAsString(FacesContext context, UIComponent component, Object object) {
 
		return ((Pessoa)object).getNome();
	}
 
}

E agora nosso managed bean que recebe não uma String, e sim objetos do nosso domínio. Eu falo isso o tempo todo pois preciso deixar isso bem claro, senão eu fico doido de ver uma aplicação usando JSF passando String e Integer de um lado pro outro :(

Mas tudo bem, deixando o desabafo pra lá vamos ao código:

@ManagedBean(name="pessoaController")
@RequestScoped
public class PessoaController {
 
	private Pessoa pessoaSelecionada;
	private List<pessoa> pessoas;
 
	@PostConstruct
	public void init()
	{
		pessoas = new ArrayList<pessoa>();
		for (int i = 0; i < 10; i++) {
			pessoas.add(new Pessoa("fulano_" + i));
		}
	}
 
	//getters e setters suprimidos
}

Acredito que por hoje seja suficiente. Vou ver se em breve escrevo algo mais específico sobre facelets.

JSF, JavaEE , , ,

implicit navigation do JSF 2.0

May 18th, 2009

O JSF 2 teve o mecanismo de navegação melhorado. Agora além de regras de navegação implícitas foi adicionado um teste que pode ser feito usando a tag <if> dentro do <navigation-case>. E para finalizar a tag <to-view-id> aceita EL, o que torna tudo mais dinâmico.

Mas como são várias coisas, vamos por partes

Implicit Navigation

Agora quando retornamos um outcome na nossa action, caso nenhuma regra de navegação compatível seja encontrada, a navegação implícita entra em cena.

Vamos considerar os seguintes dados

from-view-id outcome to-view-id implícita
/pasta1/view1.xhtml view2 /pasta1/view2.xhtml
/pasta1/view1.xhtml /view2 /view2.xhtml
/pasta1/view1.xhtml /pasta2/view3 /pasta2/view3.xhtml
/pasta1/view1.xhtml view2.groovy /pasta1/view2.groovy
/pasta1/view1.xhtml /outrapasta/view2.groovy /outrapasta/view2.groovy



Acredito que a tabela seja auto explicativa, mas só para consolidar: caso o outcome devolvido comece com “/” será considerado como caminho absoluto, senão a view será procurada na mesma pasta da view que originou a action. Além disso se nenhuma extensão de arquivo for informada, será considerada a mesma extensão da view que originou a action.

E por fim, podemos definir o atributo “faces-redirect=true” para informar que queremos que seja usado um redirect, assim como faríamos com se tivéssemos definido nossa regra de navegação via xml, como por exemplo “meuOutcome?faces-redirect=true“.

Navigation case com <if>

Assim como as implicit navigation, o Seam também tem o <if> como o do JSF 2, porém no Seam esse <if> fica no pages.xml, um arquivo do Seam. Como sempre, vamos ver um exemplo para facilitar o entendimento.

@ManagedBean(name="pessoaBean")
@RequestScoped
public class PessoaBean{
 
	private EntityManager em; //injetado por algum mecanismo
 
	private Pessoa pessoa = new Pessoa();
 
	public void actionSalvar() {
		em.persist(pessoa);
	}
 
	//getters e setters suprimidos
}

agora vamos ver o faces-config.xml

...
<navigation-rule>
	<from-view-id>/cadastroPessoa.xhtml</from-view-id>
	<navigation-case>
		<if>#{pessoaBean.pessoa.id != null}</if>
		<to-view-id>/listagemPessoas.xhtml<to-view-id>
	</navigation-case>
</navigation-rule>
...

No nosso exemplo acima, mesmo sem retornar nenhum outcome, a navegação acontece da view “/cadastroPessoa.xhtml” para a view “/listagemPessoas.xhtml“, graças ao <if> do nosso <navigation-case>. Na expressão do exemplo usei algo bem simples, considerei que se o id da pessoa está diferente de nulo é porque a ação de salvar foi executada com sucesso. Obviamente podemos evoluir esse exemplo, mas como a finalidade aqui é didática acredito que seja sufucuente como está.

EL no <to-view-id>

Para finalizar vamos dar uma olhada no exemplo do uso da EL no <to-view-id>. Vamos ver esse outro exemplo.

@ManagedBean(name="cidadeBean")
@RequestScoped
public class CidadeBean{
 
	private EntityManager em; //injetado por algum mecanismo
 
	private Cidade cidade = new Cidade();
 
	private nextView;
 
	public String actionSalvar() {
		em.persist(cidade);
		nextView = "/listagemCidades.xhtml"
		return "sucesso";
	}
 
	//getters e setters suprimidos
}

E no faces-config.xml temos o seguinte

...
<navigation-rule>
	<from-view-id>/cadastroCidade.xhtml</from-view-id>
	<navigation-case>
		<from-outcome>sucesso</from-outcome>
		<to-view-id>#{cidadeBean.nextView}<to-view-id>
	</navigation-case>
</navigation-rule>
...

Com isso fechamos a parte de NavigationHandler do JSF 2. Na verdade ainda tem como novidade a possibilidade de consultarmos os NavigationCase’s de forma programática. Mas isso eu comento melhor quando for falar do que podemos fazer de forma programática no JSF 2 usando a implementação de referência, Mojarra (pois essas configurações programáticas que irei comentar não são especificadas).

JSF, JavaEE , ,

Conversation Scope usando @CustomScoped do JSF 2.0

May 12th, 2009

Apesar do título, o JSF 2 não vai ter um escopo Conversation ou coisa parecida como tem no Seam ou Orchestra. O que vai ter é o suporte direto a escopos personalizados, o que torna simples a criação de um escopo como Conversation.

Nesse exemplo eu desenvolvi um escopo de conversação simples, mas não é difícil melhorá-lo para suportar diversas conversações simultâneas, assimilando a idéia a gente vê que não é nenhum bicho de sete cabeças. Claro que o principal objetivo é o aprendizado de como tudo funciona, e não ficar implementando o que já tem pronto em diversos frameworks.

Vamos então ao exemplo.

@ManagedBean(name="testeBean")
@CustomScoped("#{conversacao}")
public class TesteBean {
 
	private Integer contador;
 
	@PostConstruct
	public void init()
	{
		System.out.println("TesteBean.init()");
		contador = 0;
	}
 
	public void incrementarContador()
	{
		contador++;
	}
	public void iniciaConversacao()
	{
		Conversacao.instancia().iniciar();
	}
	public void finalizaConversacao()
	{
		Conversacao.instancia().finalizar();
	}
 
	public Integer getContador() {
		return contador;
	}
	public void setContador(Integer contador) {
		this.contador = contador;
	}
}

Acima está um managed bean que chamei de “beanTeste”. Coloquei um println no método init() e anotei esse método com @PostConstruct. Na prática isso quer dizer que toda vez que o managed bean for construído esse método será chamado, e consequentemente aparecerá no console. Isso é útil para vermos que o escopo de conversação realmente está funcionando. Já que estamos falando de escopo personalizado, quem faz isso é a anotação @CustomScoped, que recebe como valor uma EL que será resolvida por um EL Resolver que vamos criar.

Agora vamos ver a tela

Contador atualizado via ajax e mantido com a conversação: <h:outputText id="output3" value="#{testeBean.contador}"/>
 
<br/>
 
<h:commandButton action="#{testeBean.incrementarContador}" value="Incrementar Contador">
	<f:ajax render="output3"/>
</h:commandButton>
<h:commandButton action="#{testeBean.iniciarConversacao}" value="Iniciar Conversação">
	<f:ajax render="@none"/>
</h:commandButton>
<h:commandButton action="#{testeBean.finalizarConversacao}" value="Finalizar Conversação">
	<f:ajax render="@none"/>
</h:commandButton>

Tanto o exemplo da tela quanto do managed bean estão simplificados para o nosso exemplo, mas a versão disponível para download contém uns exemplos para testarmos o funcionamento do ajax também.

A idéia do nosso escopo “conversacao” é o mesmo do Seam, ele funciona como request até que seja explicitamente iniciada a conversação. Quando isso ocorre, o escopo passa a ser statefull até que a conversação seja finalizada, fazendo com que ela funcione como request novamente.

Seguindo essa idéia, o contador que aparece na tela não vai sair de “1″ até que a conversação seja iniciada, pois como o escopo é request, toda vez que executamos a ação “incrementaContador” o managed bean será criado novamente (contador = 0), depois a ação será executada (contador = 1) e então a página será renderizada (exibe contador = 1). Agora se a conversação for iniciada o managed bean será mantido entre as requisições, e o contador não será zerado até que a conversação termine.

Para nosso escopo funcionar, precisamos de um ELResolver, que será quem vai conseguir dizer para o faces de onde virá os objetos relacionados ao escopo “conversacao”. Registramos nosso ELResolver no faces-config.xml assim

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
              version="2.0">
 
    <application>
        <el-resolver>br.eti.gilliard.exemplojsf2.conversacao.ConversacaoELResolver</el-resolver>
    </application>
 
</faces-config>

E a implementação fica assim

 
public class ConversacaoELResolver extends ELResolver {
 
	@Override
	public Object getValue(ELContext elContext, Object base, Object property) {
 
		if (property == null) {
			throw new PropertyNotFoundException("A Propriedade não pode ser nula!");
		}
		if (base == null) {
 
			if(Conversacao.NOME_ESCOPO.equals(property.toString()))
			{
				Conversacao conversacao = Conversacao.instancia();
				elContext.setPropertyResolved(true);
				return conversacao;
			}
 
			Conversacao conversacao = Conversacao.instancia();
			return getValue(conversacao, property.toString(), elContext);
 
		} else if (base instanceof Conversacao) {
			return getValue((Conversacao) base, property.toString(), elContext);
		}
		return null;
	}
 
	private Object getValue(Conversacao conversacao, String property, ELContext elContext) {
		Object objeto = conversacao.get(property);
		elContext.setPropertyResolved(objeto != null);
		return objeto;
	}
 
	// os outros métodos foram suprimidos nesse exemplo
 
}

Essa é uma implementação comum de EL Resolver, onde eu uso o objeto retornado por Conversacao.instancia() para localizar as propriedades solicitadas.

Para o jsf um escopo nada mais é do que um java.util.Map, e de fato a classe Conversacao estende ConcurrentHashMap, ou seja, é um Map como pede o jsf. Fora isso os métodos get e put foram sobrescritos para funcionarem de acordo com nossa especificação de conversação, ou seja, se ela não foi iniciada tudo deve funcionar como request, agora quando a conversação é iniciada, os valores passam a ser guardados dentro da sessão do usuário, fazendo assim ficar statefull. Depois que a conversação é finalizada os valores voltam a ser guardados no request.

Antes de ver a implementação da classe Conversacao, o mais importante é entender como o mecanismo de resolução de EL funciona. Como visto no faces-config.xml, não existe nenhuma ligação da nossa implementação de ELResolver com a El “#{conversacao}” que colocamos na anotação @CustomScoped do nosso managed bean. Toda vez que uma EL é encontrada ela é passada para os ELResolvers contidos na aplicação. Obviamente existem outras implementações padrões já disponíveis, e a nossa vai entrar nessa fila. Como nenhuma das outras implementações vai saber resolver essa EL, ela acaba vindo para a nossa implementação, e então quando encontramos o objeto procurado utilizamos o método “ElContext.setPropertyResolved(boolean b)” passando true para informar que não precisa continuar perguntando para os demais ELResolver’s, pois o nosso já descobriu quem é o objeto.

Existem alguns detalhes que devemos seguir ao implementar um escopo personalizado, como o de avisar, utilizando o novo sistema de eventos do JSF 2, que estamos criando ou destruindo nosso escopo.

Além disso para fazer essa implementação suportar múltiplas conversações seria necessário apenas colocar um nível a mais de mapa na nossa implementação, onde teríamos uma identificação da conversação e então seu contexto. Em vez de um simples Map, ficaria um Map de Map :D . Então para saber qual Map interno devolver a gente buscaria a conversação atual de algum contexto da nossa escolha, e poderíamos deixar um combobox sempre visível na tela para o usuária escolher a conversação que ele quer usar. Novamente nada de novo, tudo igual o funcionamento do Seam, por exemplo.

Agora sim vamos à implementação da classe Conversacao.

public class Conversacao extends ConcurrentHashMap<string,Object>{
 
	private static final long serialVersionUID = 7556965369432050706L;
 
	public static final String NOME_ESCOPO = "conversacao";
 
	private static final String CONVERSACAO_ATUAL = "exemplojsf2.conversacao.ConversacaoAtual";
 
	private boolean conversacaoNaoIniciada = true;
 
 
	private Conversacao() {
	}
 
	public static Conversacao instancia()
	{
		Map<string, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
		Conversacao conversacao = (Conversacao) sessionMap.get(CONVERSACAO_ATUAL);
		if(conversacao == null)
		{
			conversacao = new Conversacao();
			sessionMap.put(CONVERSACAO_ATUAL, conversacao);
		}
		return conversacao;
	}
 
	public Object get(Object propriedade)
	{
		//se a conversacao nao for iniciada funciona como request
		if(conversacaoNaoIniciada)
		{
			return pegarDoRequest(propriedade);
		}
 
		return super.get(propriedade);
	}
 
	@SuppressWarnings("unchecked")
	private Object pegarDoRequest(Object propriedade)
	{
		Map<string, Object> requestConversation = (Map<string, Object>) FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get(CONVERSACAO_ATUAL);
		if(requestConversation != null)
		{
			return requestConversation.get(propriedade);
		}
		return null;
	}
 
	@Override
	public Object put(String key, Object value)
	{
		//se a conversacao nao for iniciada funciona como request
		if(conversacaoNaoIniciada)
		{
			return colocarNoRequest(key, value);
		}
 
		return super.put(key, value);
	}
 
	@SuppressWarnings("unchecked")
	private Object colocarNoRequest(String key, Object value)
	{
 
		Map<string, Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
		Map<string, Object> requestConversation = (Map<string, Object>) requestMap.get(CONVERSACAO_ATUAL);
		if(requestConversation == null)
		{
			requestConversation = new ConcurrentHashMap<string, Object>();
			requestMap.put(CONVERSACAO_ATUAL, requestConversation);
			return requestConversation.put(key, value);
 
		}
 
		return requestConversation.put(key, value);
	}
 
	public void iniciar()
	{
		conversacaoNaoIniciada = false;
		promoverRequestParaConversacao();
		notificarCriacao();
	}
 
	@SuppressWarnings("unchecked")
	private void promoverRequestParaConversacao()
	{
		Map<string, Object> requestConversation = (Map<string, Object>) FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get(CONVERSACAO_ATUAL);
		super.putAll(requestConversation);
	}
 
	private void notificarCriacao()
	{
		ScopeContext context = new ScopeContext(NOME_ESCOPO, this);
		FacesContext facesContext = FacesContext.getCurrentInstance();
		facesContext.getApplication().publishEvent(facesContext, PostConstructCustomScopeEvent.class, context);
	}
 
	public void finalizar()
	{
		notificarFinalizacao();
		conversacaoNaoIniciada = true;
		rebaixarConversacaoParaRequest();
 
	}
 
	@SuppressWarnings("unchecked")
	private void rebaixarConversacaoParaRequest()
	{
		Map<string, Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
		Map<string, Object> requestConversation = (Map<string, Object>) requestMap.get(CONVERSACAO_ATUAL);
		if(requestConversation == null)
		{
			requestConversation = new ConcurrentHashMap<string, Object>();
			requestMap.put(CONVERSACAO_ATUAL, requestConversation);
		}
		requestConversation.putAll(this);
		this.clear();
		FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove(CONVERSACAO_ATUAL);
	}
 
	private void notificarFinalizacao()
	{
		ScopeContext context = new ScopeContext(NOME_ESCOPO, this);
		FacesContext facesContext = FacesContext.getCurrentInstance();
		facesContext.getApplication().publishEvent(facesContext, PreDestroyCustomScopeEvent.class, context);
	}
}

O download do exemplo pode ser feito aqui.

Com certeza deve ter algum errinho nessa implementação, mas se tiver não se desespere, por essas e outras que você certamente deve estar usando um framework mais confiável na tua aplicação do que uma implementação de “fundo de quintal” ;) . De qualquer forma encontrando os errinhos me diga que eu vou corrigindo.

JSF, JavaEE , ,

f:ajax no JSF 2.0

May 11th, 2009

Depois de muito tempo sem escrever estou eu aqui de novo falando de JSF 2.0

Neste post irei mostrar como está ficando o suporte a ajax do JSF 2. Já adianto que está ficando bem parecido com o Ajax4Jsf. Em outros posts eu já havia comentado sobre o suporte a ajax que a nova versão do jsf vai suportar. Mas agora vou falar do componente de tela para facilitar o uso, no outro post falei apenas da API JavaScript, que eu usei nesse exemplo.

O componente <f:ajax> parece o <a4j:support>. Essa nova tag pode ser usada tanto dentro de uma tag específica, tornando-a ajax, assim como fazemos com o <a4j:support> ou pode ser colocada em volta de vários componentes, tornando todos os componentes dentro dela ajax.

Por exemplo:

 
<h:panelGroup id="panelGroupX">
...
</h:panelGroup>
 
<h:outputText id="outputY" value="..."/>
 
<h:commandButton action="...">
    <f:ajax execute="@form" render="panelGroupX outputY"/>
</h:commandButton>

Nesse exemplo temos uma página como já estamos acostumados, até onde o novo componente aparece. Nesse caso a tag <f:ajax> está habilitando ajax no commandButton, e os dois principais atributos da tag são “execute” e “render”. O primeiro serve para informarmos o que será enviado ao servidor na nossa requisição ajax, e o segundo é como o “reRender” do ajax4jsf, e serve para informarmos o que será renderizado novamente. Ambos aceitam uma lista de ids, separados por espaço em branco, ou então os seguintes valores pré-definidos:

  • @this – o próprio componente que dispara a requisição ajax
  • @form – o formulário que envolve o componente @this
  • @all – a view inteira
  • @none – nenhum componente

Lembrando novamente que esses valores servem tanto para informar o que vai (execute) e o que volta (render) em uma requisição ajax.

Agora outro exemplo

 
<f:ajax event="onmouseover">
 
    <h:panelGroup id="panelGroupX">
    ...
    </h:panelGroup>
 
    <h:outputText id="outputY" value="..."/>
 
    <h:commandButton action="...">
        <f:ajax event="action" execute="@form" render="panelGroupX outputY"/>
    </h:commandButton>
 
</f:ajax>

Nesse caso a tag <f:ajax> envolve os demais componentes, fazendo com que tudo que está dentro dela passe a disparar eventos ajax. Cada tipo de componente possui um evento padrão que dispara a requisição ajax: um input dispara a requisição quando tem seu valor alterado e um botão ou link quando é clicado, por exemplo.

Porém nesse exemplo eu especifiquei que o evento padrão que executará o ajax para todos os componentes dentro da tag <f:ajax> é o “onmouseover”, mas como mostrado no commandButton eu posso sobrescrever os valores definidos na tag <f:ajax> de fora com uma tag dentro do próprio componente. Eu usei a propriedade “event“, mas poderia ter usado qualquer outra na tag ajax de fora, fornecendo assim um mesmo comportamente default para todos.

No último exemplo usei a propriedade “event“, que como visto no exemplo serve para dizer qual evento executará a requisição ajax. Essa propríedade suporta todos os eventos DOM e ainda “valueChange” para componentes de entrada de dados (ou mais especificamente um EditableValueHolder) e “action” para componentes de ação (um ActionSource).

A tag <f:ajax> suporta ainda os atributos:

  • listener – serve para fazer binding com um método que a seguinte assinatura “public void (javax.faces.event.AjaxBehaviorEvent event) throws javax.faces.event.AbortProcessingException“. Com isso podemos executar um código java quando um evento qualquer é disparado.
  • disabled – seria o equivalente ao rendered de um componente visual, se o valor definido aqui for true o suporta a ajax fica desabilitado.
  • immediate – igual o immediate dos componentes jsf comuns.
  • onevent – função js que será chamada quando o evento especificado for executado
  • onerror – função js que será chamada quando um erro ocorrer na requisição

Esse post foi curto por falta de tempo, mas novos posts virão em breve (assim espero ). Já estou com um exemplo pronto de implementação de conversation usando o suporte a escopos customizados do JSF 2.0, mas isso vai ter que ficar para o próximo post ;)

JSF, JavaEE , ,