Skip to content

10. Versão 5 - Aplicativo Web PAM / JSF

10.1. Arquitetura da aplicação

A arquitetura da aplicação web PAM será a seguinte:

Nesta versão, o servidor Glassfish hospedará todas as camadas da aplicação:

  • a camada [web] é hospedada pelo contêiner de servlets do servidor (1 abaixo)
  • as demais camadas [metier, DAO, jpa] são hospedadas pelo contêiner EJB3 do servidor (2 abaixo)

Os elementos [metier, DAO] do aplicativo em execução no contêiner EJB3 já foram descritos no aplicativo cliente/servidor analisado no parágrafo 7.1, cuja arquitetura era a seguinte:

As camadas [metier, DAO] eram executadas no contêiner EJB3 do servidor Glassfish e a camada [ui] em um aplicativo de console ou Swing em outra máquina:

Na arquitetura do novo aplicativo:

apenas a camada [web / jsf] precisa ser escrita. As demais camadas [metier, DAO, jpa] já estão implementadas.

No documento [ref3], é mostrado que um aplicativo web em que a camada web é implementada com Java Server Faces possui uma arquitetura semelhante à seguinte:

Essa arquitetura implementa o Padrão de Design MVC (Modelo, Visão, Controlador). O processamento de uma solicitação de um cliente ocorre da seguinte maneira:

Se a solicitação for feita por meio de um GET, as duas etapas a seguir são executadas:

  1. solicitação — o navegador do cliente envia uma solicitação ao controlador [Faces Servlet]. Este processa todas as solicitações dos clientes. É a porta de entrada do aplicativo. É o C de MVC.
  2. resposta — o controlador C solicita que a página JSF selecionada seja exibida. Essa é a vista, o V de MVC. A página JSF utiliza um modelo M para inicializar as partes dinâmicas da resposta que deve enviar ao cliente. Esse modelo é uma classe Java que pode recorrer à camada [métier] [4a] para fornecer à vista V os dados de que ela precisa.

Se a solicitação for feita com um POST, duas etapas adicionais são inseridas entre a solicitação e a resposta:

  1. solicitação — o navegador do cliente faz uma solicitação ao controlador [Faces Servlet].
  2. processamento — o controlador C processa essa solicitação. De fato, uma solicitação POST é acompanhada de dados que precisam ser processados. Para isso, o controlador conta com a ajuda de gerenciadores de eventos específicos da aplicação [2a]. Esses gerenciadores podem precisar da camada de negócios [2b]. O gerenciador de eventos pode precisar atualizar certos modelos M [2c]. Uma vez processada a solicitação do cliente, ela pode gerar diversas respostas. Um exemplo clássico é:
    • uma página de erros, caso a solicitação não tenha sido processada corretamente
    • uma página de confirmação, caso contrário

O gerenciador de eventos retorna ao controlador [Faces Servlet] um resultado do tipo cadeia de caracteres, chamado de chave de navegação.

  1. navegação — o controlador seleciona a página JSF (= visualização) a ser enviada ao cliente. Essa escolha é feita com base na chave de navegação retornada pelo gerenciador de eventos.
  2. resposta — a página JSF selecionada enviará a resposta ao cliente. Ela utiliza seu modelo M para inicializar suas partes dinâmicas. Esse modelo também pode recorrer à camada [métier] [4a] para fornecer à página JSF os dados de que ela necessita.

Em um projeto JSF:

  • o controlador C é o servlet [javax.faces.webapp.FacesServlet]. Ele se encontra na biblioteca [jsf-api.jar].
  • as visualizações V são implementadas pelas páginas JSF.
  • os modelos M e os gerenciadores de eventos são implementados por classes Java frequentemente chamadas de “backing beans”.
  • Nas versões JSF e 1.x, a definição dos beans, bem como as regras de navegação de uma página para outra, estão definidas no arquivo [faces-config.xml]. Nele está a lista das visualizações e as regras de transição entre elas. A partir da versão JSF 2, as definições dos beans podem ser feitas por meio de anotações, e as transições entre páginas podem ser definidas “diretamente” no código dos beans.

10.2. Funcionamento do aplicativo

Quando a aplicação é acessada pela primeira vez, é exibida a seguinte página:

Em seguida, preenche-se o formulário e solicita-se o salário:

O resultado obtido é o seguinte:

Esta versão calcula um salário fictício. Não se deve prestar atenção ao conteúdo da página, mas sim à sua formatação. Ao clicar no botão [Raz], retorna-se à página [A].

As entradas incorretas são sinalizadas, conforme mostra o exemplo a seguir:

10.3. O projeto NetBeans

Vamos construir uma primeira versão do aplicativo em que a camada [métier] será simulada. Teremos a seguinte arquitetura:

Quando os gerenciadores de eventos ou os modelos solicitarem dados à camada [métier] [2b, 4a], esta fornecerá dados fictícios. O objetivo é obter uma camada web que responda corretamente às solicitações do usuário. Quando isso for alcançado, restará apenas instalar a camada de servidor desenvolvida no parágrafo 7.1:

Essa será a versão 2 da versão web do nosso aplicativo PAM.

O projeto NetBeans da versão 1 é o seguinte projeto Maven:

  • em [1], os arquivos de configuração
  • no [2], as páginas no XHTML e a folha de estilo
  • em [3], as classes da camada em [web]
  • em [4], os objetos trocados entre a camada [web] e a camada [métier] e a própria camada [métier]
  • em [5], o arquivo de mensagens para a internacionalização do aplicativo
  • em [6], as dependências do aplicativo

Vamos examinar alguns desses elementos.

10.3.1. Os arquivos de configuração

O arquivo [web.xml] é aquele gerado por padrão pelo NetBeans, além de conter a configuração de uma página de exceção:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>
</web-app>
  • linha 30: [index.html] é a página inicial do aplicativo
  • linhas 32-39: configuração da página de exceção

A página [exception.html] é derivada de [ref3]. Seu código é o seguinte:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <h3><h:outputText value="#{msg['exception.header']}"/></h3>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['exception.httpCode']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
          <h:outputText value="#{msg['exception.message']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
          <h:outputText value="#{msg['exception.requestUri']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
          <h:outputText value="#{msg['exception.servletName']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
        </h:panelGrid>
      </h:form>
    </h:body>
  </f:view>
</html>

Qualquer exceção que não seja explicitamente tratada pelo código do aplicativo web fará com que seja exibida uma página semelhante à seguinte:

O arquivo [faces-config.xml] será o seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
              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">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

Observe os seguintes pontos:

  • linhas 9-14: o arquivo [messages.properties] será utilizado para a internacionalização das páginas. Ele estará acessível nas páginas XHTML por meio da chave msg.
  • linha 15: define o arquivo [messages.properties] como aquele a ser consultado prioritariamente para as mensagens de erro exibidas pelas tags <h:messages> e <h:message>. Isso permite redefinir algumas mensagens de erro padrão do JSF. Essa possibilidade não é utilizada aqui.

10.3.2. A folha de estilo

O arquivo [styles.css] é o seguinte:


.libelle{
   background-color: #ccffff;
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}
body{
   background-color: #ffccff
}

.error{
   color: #ff3333
}

.info{
   background-color: #99cc00
}

.titreInfos{
   background-color: #ffcc00
}

Aqui estão alguns exemplos de código JSF que utilizam esses estilos:


<h:outputText value="#{msg['form.infos.employé']}"
 styleClass="titreInfos"/>

<h:panelGrid columns="3" 
rowClasses="libelle,info">

<h:message for="heuresTravaillées"
 styleClass="error"/>

10.3.3. O arquivo de mensagens

O arquivo de mensagens [messages_fr.properties] é o seguinte:


form.titre=Feuille de salaire
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net

Todas essas mensagens são utilizadas na página [index.xhtml], com exceção das das linhas 11 a 15, que são utilizadas na página [exception.xhtml].

10.3.4. O escopo dos beans

O bean [web.forms.Form] terá escopo de solicitação:


import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class Form implements Serializable {

O bean [web.utils.ChangeLocale] terá escopo de aplicação:


package web.utils;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // a localização das páginas
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  public String setFrenchLocale(){
    locale="fr";
    return null;
  }
  
  public String setEnglishLocale(){
    locale="en";
    return null;
  }

  public String getLocale() {
    return locale;
  }

  public void setLocale(String locale) {
    this.locale = locale;
  }
  
  
}

10.3.5. A camada [métier]

A camada [métier] implementa a seguinte interface IMetierLocal:


package metier;

import java.util.List;
import javax.ejb.Local;
import jpa.Employe;

@Local
public interface IMetierLocal {
  // obter a folha de pagamento
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // lista de funcionários
  List<Employe> findAllEmployes();
}

Esta interface é a utilizada na parte do servidor do aplicativo cliente/servidor descrito no parágrafo 7.1.

A classe Metier, que utilizaremos para testar a camada [web], implementa essa interface da seguinte maneira:


package metier;

...
public class Metier implements IMetierLocal {
  
  // dicionário de funcionários indexado pelo nº SS
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  // lista de funcionários 
  private List<Employe> listEmployes;
  
  // obter a folha de pagamento
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // recuperar o funcionário com o n.º SS
    Employe e=hashEmployes.get(SS);
    // gerar uma folha de pagamento fictícia
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
  }
  
  // lista de funcionários
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // cria-se uma lista com dois funcionários
      listEmployes=new ArrayList<Employe>();
      listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
      listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
      // dicionário de funcionários indexado pelo nº SS
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
    }
    // retornamos a lista de funcionários
    return listEmployes;
  }
}

Deixamos a cargo do leitor a tarefa de decifrar esse código. Observe o método utilizado: para não precisarmos implementar a parte EJB da aplicação, simulamos a camada [métier]. Quando a camada [web] for declarada correta, poderemos então substituí-la pela verdadeira camada [métier].

10.4. O formulário [index.xhtml] e seu modelo [Form.java]

Agora, estamos criando a página XHTML do formulário, bem como seu modelo.

Leituras recomendadas em [ref3]:

  • exemplo nº 3 (mv-jsf2-03) para a lista de tags utilizáveis em um formulário
  • exemplo nº 4 (mv-jsf2-04) para as listas suspensas preenchidas pelo modelo
  • exemplo nº 6 (mv-jsf2-06) para a validação dos dados inseridos
  • exemplo nº 7 (mv-jsf2-07) para o gerenciamento do botão [Raz]

10.4.1. Etapa 1


Pergunta: Construa o formulário [index.xhtml] e seu modelo [Form.java] necessários para obter a página a seguir:


Os componentes de entrada são os seguintes:

id
tipo JSF
modelo
função
1
comboEmployes
<h:selectOneMenu>
String comboEmployesValue
List<Empregado> getEmployes()
contém a lista de funcionários no formato
“nome sobrenome”.
2
heuresTravaillees
<h:inputText>
String heuresTravaillées
número de horas trabalhadas — número real
3
joursTravailles
<h:inputText>
String joursTravaillés
número de dias trabalhados - número inteiro
4
btnSalaire
<h:commandButton>
 
inicia o cálculo do salário
5
btnRaz
<h:commandButton>
 
recoloca o formulário em seu estado inicial
  • o método getEmployes retornará uma lista de funcionários obtida da camada [métier]. Os objetos exibidos pela lista suspensa terão como atributo itemValue o número SS do funcionário e, como atributo itemLabel, uma sequência de caracteres composta pelo nome e sobrenome do funcionário.
  • Os botões [Salaire] e [Raz] não estarão, por enquanto, conectados a manipuladores de eventos.
  • A validade das entradas será verificada.

Image

Teste esta versão. Verifique, principalmente, se os erros de entrada são devidamente sinalizados.

Observação: é importante que os atributos id dos componentes da página não contenham caracteres acentuados. Com o Glassfish 3.1.2, isso faz com que a aplicação trave.

10.4.2. Etapa 2


Pergunta: preencha o formulário [index.xhtml] e seu modelo [Form.java] para obter a página a seguir após clicar no botão [Salaire]:


O botão [Salaire] será conectado ao gerenciador de eventos calculerSalaire do modelo. Esse método utilizará o método calculerFeuilleSalaire da camada [métier]. Esta folha de pagamento será gerada para o funcionário selecionado em [1].

No modelo, a folha de pagamento será representada pelo seguinte campo privado:


  private FeuilleSalaire feuilleSalaire;

com os métodos get e set.

Para obter as informações contidas nesse objeto, é possível escrever na página JSF expressões como a seguinte:


<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>

A expressão do atributo **value** será avaliada da seguinte forma:

[form].getFeuilleSalaire().getEmploye().getNom(), onde [form] representa uma instância da classe [Form.java]. O leitor poderá verificar que os métodos get utilizados aqui existem, de fato, nas classes [Form], [FeuilleSalaire] e [Employe], respectivamente. Caso contrário, uma exceção seria lançada durante a avaliação da expressão.

Teste esta nova versão.

10.4.3. Etapa 3


Pergunta: preencha o formulário [index.xhtml] e seu modelo [Form.java] para obter as seguintes informações adicionais:


Seguiremos o mesmo procedimento de antes. Há uma dificuldade com o símbolo monetário do euro que aparece em [1], por exemplo. No contexto de um aplicativo internacionalizado, seria preferível ter o formato de exibição e o símbolo monetário do locale utilizado (en, de, fr, ...). Isso pode ser obtido da seguinte maneira:


          <h:outputFormat value="{0,number,currency}">
            <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>

Poderíamos ter escrito:


          <h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">

mas com a configuração regional en_GB (inglês GB), continuaria a ser exibido em euros, quando deveria ser usada a libra £. A tag <h:outputFormat> permite exibir informações de acordo com a locale da página JSF exibida:

  • linha 1: exibe o parâmetro {0}, que é um número (number) representando uma quantia em dinheiro (currency)
  • linha 2: a tag <f:param> atribui um valor ao parâmetro {0}. Uma segunda tag <f:param> atribuiria um valor ao parâmetro {1} e assim por diante.

10.4.4. etapa 4

Leituras recomendadas: exemplo nº 7 (mv-jsf2-07) em [ref3].


Pergunta: preencha o formulário [index.xhtml] e seu modelo [Form.java] para gerenciar o botão [Raz].


O botão [Raz] restaura o formulário ao estado em que se encontrava quando foi solicitado pela primeira vez por meio de um GET. Há várias dificuldades aqui. Algumas delas foram explicadas em [ref3].

O formulário gerado pelo botão [Raz] não é o formulário completo, mas apenas a parte saisie dele:

Image

Esse resultado pode ser obtido com uma tag <f:subview> utilizada da seguinte maneira:


      <f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>

A tag <f:subview> engloba toda a parte do formulário que pode ser exibida ou ocultada. Qualquer componente pode ser exibido ou ocultado por meio do atributo rendered. Se rendered="true", o componente é exibido; se rendered="false", ele não é exibido. Se o atributo rendered assumir seu valor no modelo, a exibição do componente poderá ser controlada programaticamente.

Acima, controlaremos a exibição da vista viewInfos com o seguinte campo:


  private boolean viewInfosIsRendered;

acompanhado de seus métodos get e set. Os métodos que gerenciam os cliques nos botões [Salaire] e [Raz] atualizarão esse valor booleano, dependendo se a visualização viewInfos deve ser exibida ou não.