Exemplo de cadastro com o JBoss Seam

Este artigo apresenta como fazer um cadastro simples com o JBoss Seam integrando JSF, JPA e componentes POJO. O objetivo é mostrar a principal aplicação deste framework através da explicação de seus conceitos básicos. Para isso, criei um exemplo prático de cadastro de contatos que é utilizado nas explicações.

Abaixo as tecnologias utilizadas no exemplo:

Introdução

Para quem já trabalhou com JSF e EJB 3 juntos sabe que é preciso programar uma camada muitas vezes repetitiva ao criar, por exemplo, um cadastro. Isso ocorre porque é necessário que uma página JSF esteja interligada com um Managed Bean e este por sua vez realize as chamadas propriamente ditas a um Session Bean, já que estes possuem a limitação de não se comunicarem com a camada web e nem compartilharem o mesmo contexto de requisição, sessão ou aplicação.

Com o objetivo de facilitar essa integração foi desenvolvido o JBoss Seam que faz o trabalho “sujo” na integração entre JSF e EJB. Além disso, possui uma série de vantagens no desenvolvimento de aplicações Web 2.0. Como seu modelo foi bem aceito, foi criada a JSR Web Beans para torná-lo um padrão que em breve estará disponível na plataforma Java EE.

Configurações para utilizar o Seam

Não pretendo abordar neste artigo as configurações necessárias para utilizar o Seam, já que isso pode ser facilmente encontrado na internet. Além disso, existe uma ferramenta chamada seam-gen que acompanaha o Seam que, entre outras coisas, automatiza as configurações de um novo projeto. O plugin Seam do JBoss Tools para o Eclipse (veja aqui como instalá-lo) também ajuda na hora do desenvolvimento. No tópico “Referências” no final deste artigo você encontra alguns links que explicam como utilizá-los.

Exemplo prático

Download do Projeto

Existem alguns conceitos que precisam ser compreendidos para desenvolver um projeto com o Seam, mas acho mais fácil mostrá-los através de um exemplo prático. Para isso descreverei como implementar um cadastro básico de contatos utilizando o Seam. Abaixo as imagens de como nosso cadastro ficará ao final desses passos:

Passo 1: Criando nossa entidade

Para o nosso exemplo utilizaremos JPA realizando o mapeamento objeto-relacional. Como se trata de um exemplo simples, coloquei apenas o nome e o e-mail do contato na entidade. Também ocultei algumas coisas como getters e setters que podem ser vistas no código completo.

Contato.java:

@Entity //1
@Name("contato") //2
public class Contato implements Serializable {

    @Id @GeneratedValue //3
    private Long id;

    @Column(length=50, nullable=false) //4
    private String nome;

    @Column(length=50, nullable=false)
    private String email;

    //getters, setters, hashcode, equals, etc...

}

Explicações do código (cada número se refere ao número comentado no código):

  1. Declara que a classe representa uma entidade do banco de dados.
  2. Define que a entidade também é um componente do Seam, ou seja, poderá ser manipulado automaticamente por ele.
  3. Define o campo identificador da entidade e que este deve possuir um valor gerado automático (auto-incremento, por exemplo).
  4. Mesmo sem colocar a anotação @Column um atributo é definido como uma coluna da tabela por padrão, porém neste caso desejamos definir seu tamanho máximo e que não pode ser um valor nulo.

Passo 2: Criando o componente de negócio

Neste componente é onde estão presentes as regras de negócio. O próprio Seam se responsabiliza em integrar nosso componente com a camada de visão (JSF). O interessante é que este componente pode ser puramente Seam ou também um Session Bean EJB 3. No primeiro caso sua aplicação precisa apenas de um Web Container (.war) como o Tomcat ou Jetty para ser implantada. Já no caso de se usar um Session Bean obviamente é necessário um container Java EE completo (.ear).

A vantagem de utilizar Session Beans é que o container  provê todas as funcionalidades do Java EE para você, como controle transacional (JTA) dos recursos e gerenciamento da segurança e escalabilidade dos componentes de negócio.

Entretanto, em um projeto mais simples pode-se utilizar componentes Seam puros, já que este framework traz algumas funcionalidades que encontramos em um container Java EE. Além disso, é simples converter uma aplicação para utilizar EJB 3 posteriormente.

Como o objetivo aqui é apresentar uma visão do funcionamento do Seam optei por não utilizar EJB (em um futuro post pretendo explicar como ficaria o exemplo com essa opção).

Abaixo o código na nossa Action:

ContatoAction.java:

@Name("contatoAction") //1
@Scope(ScopeType.CONVERSATION) //2
public class ContatoAction {

   @In //3
   private EntityManager entityManager; //4

   @DataModel //5
   private List<Contato> contatos;

   @DataModelSelection //6
   @Out(required=false) //7
   private Contato contato;

   @Factory("contatos") //8
   @SuppressWarnings("unchecked") //9
   public void listarContatos() {
      //seleciona todos os contatos
      contatos = entityManager.createQuery("select c from Contato c").getResultList();
   }

   @Begin //10
   public String novoContato() {
      contato = new Contato();

      return "novoContato"; //12
   }

   @Begin //10
   public String editarContato() {
      //atualiza os dadis do contato para fazer a edição
      entityManager.refresh(contato);

      return "editarContato"; //12
   }

   public String removerContato() {
      //remove o contato
      entityManager.remove(contato);

      return "contatoRemovido"; //12
   }

   @End //11
   public String salvarContato() {
      //se não tem id é porque deve ser inserido, caso contrário alterado
      if (contato.getId() == null) {
         entityManager.persist(contato);
      } else {
         entityManager.merge(contato);
      }

     return "contatoSalvo";
   }
}

Explicações do código (cada número se refere ao número comentado no código):

  1. Define que é um componente Seam com o nome “contatoAction”.
  2. O Seam se responsabiliza de armazenar no contexto web seus componentes. Para isso eles precisam ter um escopo definido entre os seguintes:
    • STATELESS: O componente não mantém nenhum tipo de estado, ou seja, sua instância não será armazenada no contexto web.
    • EVENT (request): A instância do componentes é mantido no contexto durante a requisição JSF.
    • PAGE: Associa a instância do componente com uma página renderizada armazenando ela na árvore de componentes JSF.
    • CONVERSATION: Mantém a instância durante uma conversação (unidade de trabalho do ponto de vista so usuário). Veja os comentários 10 e 11 abaixo.
    • SESSION: A instância permanece no contexto da sessão do usuário.
    • BUSINESS_PROCESS: Utilizado para processos longos de negócio através do jBPM.
    • APPLICATION: A instância se mantém desde o início até o final da execução da aplicação dentro do container.
  3. Através da annotation @In o Seam injeta uma instância do componente que está no contexto. Por exemplo, se temos um objeto na sessão do usuário basta colocar @In que teremos disponível a referência a ele.
  4. O framework se responsabiliza por injetar o EntityManager para manipularmos as nossas entidades. Isso é próximo a utilizar a annotation @PersistenceContext em Session Bean, porém a vantagem de utilizar @In é que o Seam estende o contexto de persistência durante o escopo do componente. Dessa forma não precisamos nos preocupar com o controle dos objetos que são desanexados do contexto de persistência entre uma requisição e outra do usuário.
  5. Para carregar em uma tabela os contatos precisamos definimos um Data Model através dessa annotation, ou seja, é o conteúdo da lista contatos que será exibido para o usuário.
  6. De forma simples podemos pegar qual o contato que o usuário selecionou na tabela. Quando ele clicar em “Editar” em um contato o Seam irá injetar na variável qual contato ele deseja editar.
  7. Ao contrário da annotation @In, através de @Out colocamos uma instância no contexto.
  8. Como visto em (5) temos uma lista dos contatos como Data Model. Para carregar esses contatos o Seam chamará o método que tem annotation @Factory(“contatos”).
  9. Na verdade essa annotation não tem relação com o Seam. Apenas é para indicar ao compilador para ignorar um aviso, já que estamos utilizando Generics (List<Contato>) e na query é retornada uma List comum (apenas List).
  10. Quando o usuário deseja criar/editar um contato, para ele é como se uma unidade de trabalho (transação) iniciasse. Essa unidade de trabalho termina em três casos: ao salvar o contato, cancelar a operação ou após um tempo limite. Em um sistema online fica impraticável manter uma transação no banco de dados por tanto tempo, já que isso causaria a trava (lock) de tabelas ou de alguns de seus registros. Para resolver essa situação utilizamos o conceito de conversação simulando uma longa unidade de trabalho para o usuário. Essa unidade de trabalho pode ser mantida, por exemplo, durante a navegação entre várias páginas em um cadastro do tipo wizard que representa um maior caso de uso. Para indicar o inicio dessa conversação utilizamos a annotation @Begin. No momento em que os métodos “novoContato()” e “editarContato()” são chamados a conversação se inicia e uma instância de nosso componente “contatoAction” é mantido no contexto web.
  11. A annotation @End define que a conversação deve ser finalizada na chamada deste método. Além disso, neste momento o Seam pode descartar a instância alocada no contexto do componente “contatoAction”, já que seu escopo é CONVERSATION.
  12. No JSF quando temos métodos chamados pela página (actions) temos que retornar uma string. Essa string é utilizada para definir as regras de navegação entre as páginas.

Passo 3: A parte visual (JSF e Facelets)

Na camada de visão utilizamos JSF com Facelets (veja mais sobre em Referências). A vantagem da utilização do Facelets é que podemos definir templates para as páginas facilitando assim a definição e manutenção do layout. Como este projeto foi gerado pela ferramenta seam-gen alguns templates já foram definidos automaticamente.

O arquivo abaixo é a lista de contatos:

contato_list.xhtml:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:rich="http://richfaces.org/rich" template="layout/template.xhtml"> 

	<ui:define name="body"> <!-- 1 -->

		<h:messages globalOnly="true" styleClass="message" id="globalMessages" /> <!-- 2 -->

		<rich:panel>
			<f:facet name="header">Contatos</f:facet>
<div class="results" id="contatoList">
				<rich:dataTable	id="contatoList" var="_contato" value="#{contatos}"> <!-- 3 -->
					<h:column>
						<f:facet name="header">Id</f:facet>
			            		#{_contato.id} <!-- 4 -->
			        	</h:column>
					<h:column>
						<f:facet name="header">Nome</f:facet>
			            		#{_contato.nome}
			        	</h:column>
					<h:column>
						<f:facet name="header">Email</f:facet>
			            		#{_contato.email}
			        	</h:column>
					<h:column>
						<f:facet name="header">Ação</f:facet>
						<s:link id="editar" action="#{contatoAction.editarContato}" value="Editar" /> <!-- 5 -->
						<s:link id="remover" action="#{contatoAction.removerContato}" value="Remover" /> <!-- 5 -->
					</h:column>
				</rich:dataTable></div>
</rich:panel>

		<s:div styleClass="actionButtons">
			<s:button view="/contato_edit.xhtml" id="novo" value="Novo contato"/>
		</s:div>

	</ui:define>
</ui:composition>

Explicações do código (cada número se refere ao número comentado no código):

  1. Esta é uma tag do Facelets que indica que estamos definindo o conteúdo de body do template.  Neste exemplo utilizei os templates padrões do Seam para as telas.
  2. Local onde mensagens de informações e erros serão exibidas.
  3. O valor da dataTable é “contatos” que declaramos como @DataModel na componente seam contatoAction. O que foi colocado em var (“_contato”) é o nome da variável que representa o contato de cada iteração (linha) para o preenchimento da tabela, ou seja, como funciona em um “for each” em Java. Em geral, é uma boa prática utilizar “_” antes do nome neste caso para não ficar ambíguo com “contato” que é um componente seam que ficará no contexto.
  4. Através da variável “_contato” definimos o conteúdo de cada coluna da tabela referenciando uma de suas propriedades.
  5. Nos links para a execução de uma ação utilizamos o “s:link”. O Seam se encarregará de injetar no atribudo “contato” de “contatoAction” qual foi o contato que o usuário está se referenciando.

Mas como ao clicar em “Editar” a página de edição é carregada? Quando utilizamos somente JSF definimos uma regra de navegação no arquivo faces_config.xml. No caso de utilização do Seam fazemos isso através do arquivo pages.xml. Porém, assim todas as regras ficam em um mesmo arquivo. Outra maneira de fazermos isso é especificar um arquivo por página chamando-o, neste caso, de contato_list.page.xml:

contato_list.page.xml:

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
      login-required="true">

	<navigation>
		<rule if-outcome="novoContato">
			<redirect view-id="/contato_edit.xhtml"/>
		</rule>
		<rule if-outcome="editarContato">
			<redirect view-id="/contato_edit.xhtml"/>
		</rule>
		<rule if-outcome="contatoRemovido">
			<redirect view-id="/contato_list.xhtml"/>
		</rule>
	</navigation>

</page>

Basicamente o que é definido neste XML é que quando os métodos retornarem “novoContato” (linha 8 ) ou “editarContato” (linha 11) o usuário deve ser redirecionado para a página de edição do contato. Já quando um contato for removido continuará na página de listagem. Uma coisa importante também de ser comentada é o atributo “login-required” (linha 5) na tag “page”. Definindo como “true” dizemos ao Seam que somente se o usuário estiver logado poderá acessar a página. Em futuros posts pretendo mostrar como a parte de tratamento da segurança com o JBoss Seam é interessante e simples de ser utilizada.

Abaixo o código da página de criação/edição do contato:

contato_edit.xhtml:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></pre>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
		   xmlns:s="http://jboss.com/products/seam/taglib"
		   xmlns:ui="http://java.sun.com/jsf/facelets"
		   xmlns:f="http://java.sun.com/jsf/core"
		   xmlns:h="http://java.sun.com/jsf/html"
		   xmlns:a="http://richfaces.org/a4j"
		   xmlns:rich="http://richfaces.org/rich"
		   template="layout/template.xhtml">

<ui:define name="body">

 	<h:messages globalOnly="true" styleClass="message" id="globalMessages"/>

 	<h:form id="contato" styleClass="edit">

 		<rich:panel>
 			<f:facet name="header">Contato</f:facet>

 			<s:decorate id="nomeDecoration" template="layout/edit.xhtml"> <!-- 1 -->
 				<ui:define name="label">Nome</ui:define>
 				<h:inputText id="nome"
 						required="true"
 						size="50"
						maxlength="50"
 						value="#{contato.nome}"> <!-- 2 -->
 					<a:support event="onblur" reRender="nomeDecoration"
 						bypassUpdates="true" ajaxSingle="true" /> <!-- 3 -->
 				</h:inputText>
 			</s:decorate>

			<s:decorate id="emailDecoration" template="layout/edit.xhtml">
 				<ui:define name="label">Email</ui:define>
 				<h:inputText id="email"
 						required="true"
 						size="50"
 						maxlength="50"
 						value="#{contato.email}"> <!-- 2 -->
 					<a:support event="onblur" reRender="emailDecoration"
 						bypassUpdates="true" ajaxSingle="true" /> <!-- 3 -->
 				</h:inputText>
 			</s:decorate>
<div style="clear:both">
 			<span class="required">*</span> required fields</div>
</rich:panel>
<div class="actionButtons">
 		<h:commandButton id="salvar"
 				    value="Salvar"
 				    action="#{contatoAction.salvarContato}"/> <!-- 4 -->

 		<s:button id="cancelar" value="Cancelar" propagation="end"
 			     view="/contato_list.xhtml" /> <!-- 5 --></div>
</h:form>

</ui:define>

</ui:composition>
<pre>

Explicações do código (cada número se refere ao número comentado no código):

  1. A tag “s:decorate” do Seam envolve um campo de entrada do JSF e exibe uma mensagem de erro caso sua validação falhar.
  2. Como definimos em nossa action, “contato” se refere ao contato que está sendo criado ou que foi previamente selecionado pelo usuário. O Seam se encarrega de colocar sua instância no contexto. Por isso, para associarmos a propriedade “nome” com o componente JSF inputText basta colocarmos value=”#{contato.nome}”.
  3. Para suportarmos Ajax podemos utilizar o Ajax4JSF. Neste caso, quando o usuário deixar o campo seu preenchimento será validado. Caso esteja em branco uma mensagem de erro é exibido, já que os 2 inputText estão com a propriedade “required” como “true”.
  4. Ao clicar no botão “Salvar” o método “salvarContato” de nossa action é chamado.
  5. Esse componente do Seam é um botão que possui o atributo “propagation”, que como está igual a “end” permite que ao clicar em “Cancelar” a conversação seja finalizada. Além disso, o usuário é redirecionado para a lista de contatos.

Agora a última coisa, o arquivo que define as propriedades da página de edição:

contato_edit.page.xml:

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
      no-conversation-view-id="/contato_list.xhtml"
      login-required="true">

   	<navigation>
		<rule if-outcome="contatoSalvo">
			<redirect view-id="/contato_list.xhtml"/>
		</rule>
	</navigation>
</page>

Como pode ser visto, ele define que após salvar um contato o usuário é redirecionado para a lista de contatos.

Conclusão

Espero que este artigo tenha sido útil para demonstrar alguns dos principais conceitos do JBoss Seam. Este framework possui muitas outras funcionalidades como controle de segurança, suporte a URLs do estilo REST, geração de relatórios PDF/Excel, etc. Se tiver alguma dúvida ou sugestão é só deixar um comentário ou me enviar um e-mail. O código-fonte completo do exemplo pode ser baixado neste link.

Referências:

Sobre o Seam:

Usando o seam-gen para desenvolver projetos Seam:

Usando o JBoss Tools para desenvolver projetos Seam:

Site do JBoss Tools:

Sobre a JSR 299:

Sobre Facelets:

Sobre Ajax4JSF: