10. Versão 5 - Aplicação 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 irá hospedar todas as camadas da aplicação:
- a camada [web] é alojada pelo contentor de servlets do servidor (1 abaixo)
- as restantes camadas [metier, DAO, jpa] são alojadas pelo contentor EJB3 do servidor (2 abaixo)
![]() |
Os elementos [metier, DAO] da aplicação em execução no contentor EJB3 já foram descritos na aplicação cliente/servidor analisada no parágrafo 7.1, cuja arquitetura era a seguinte:
![]() |
As camadas [metier, DAO] eram executadas no contentor EJB3 do servidor Glassfish e a camada [ui] numa aplicação de consola ou Swing noutro computador:
![]() |
Na arquitetura da nova aplicação:
![]() |
apenas a camada [web / jsf] precisa de ser escrita. As outras camadas [metier, DAO, jpa] já estão implementadas.
No documento [ref3], é demonstrado que uma aplicação web em que a camada web é implementada com Java Server Faces tem uma arquitetura semelhante à seguinte:
![]() |
Esta arquitetura implementa o Padrão de Design MVC (Modelo, Vista, Controlador). O processamento de um pedido de um cliente decorre da seguinte forma:
Se o pedido for feito através de um GET, são executadas as duas etapas seguintes:
- pedido — o navegador do cliente envia um pedido ao controlador [Faces Servlet]. Este recebe todos os pedidos dos clientes. É a porta de entrada da aplicação. É o C de MVC.
- resposta — o controlador C solicita que a página JSF selecionada seja apresentada. Esta é 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. Este modelo é uma classe Java que pode recorrer à camada [métier] [4a] para fornecer à vista V os dados de que esta necessita.
Se o pedido for feito com um POST, inserem-se duas etapas adicionais entre o pedido e a resposta:
- pedido — o navegador do cliente faz um pedido ao controlador [Faces Servlet].
- processamento — o controlador C processa esta solicitação. Com efeito, uma solicitação POST é acompanhada de dados que têm de ser processados. Para tal, o controlador recorre a gestores de eventos específicos da aplicação escrita [2a]. Estes gestores podem necessitar da camada de negócio [2b]. O gestor do evento pode ter de atualizar determinados modelos M [2c]. Uma vez processado o pedido do cliente, este pode gerar várias respostas. Um exemplo clássico é:
- uma página de erros, caso a solicitação não tenha podido ser processada corretamente
- uma página de confirmação, caso contrário
O gestor de eventos devolve ao controlador [Faces Servlet] um resultado do tipo cadeia de caracteres, denominado chave de navegação.
- navegação — o controlador seleciona a página JSF (= vista) a enviar ao cliente. Esta escolha é feita com base na chave de navegação devolvida pelo gestor de eventos.
- resposta — a página JSF selecionada irá enviar a resposta ao cliente. Ela utiliza o seu modelo M para inicializar as suas partes dinâmicas. Este modelo também pode recorrer à camada [métier] [4a] para fornecer à página JSF os dados de que necessita.
Num projeto JSF:
- o controlador C é o servlet [javax.faces.webapp.FacesServlet]. Este encontra-se na biblioteca [jsf-api.jar].
- as vistas V são implementadas pelas páginas JSF.
- os modelos M e os gestores de eventos são implementados por classes Java frequentemente denominadas «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 ficheiro [faces-config.xml]. Nele encontra-se a lista das vistas e as regras de transição entre elas. A partir da versão JSF 2, as definições dos beans podem ser feitas através de anotações e as transições entre páginas podem ser definidas «directamente» no código dos beans.
10.2. Funcionamento da aplicação
Quando a aplicação é acedida pela primeira vez, é apresentada a seguinte página:
![]() |
Preenche-se então 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 utilizar o botão [Raz], regressa-se à página [A].
As entradas incorretas são assinaladas, como mostra o exemplo seguinte:
![]() |
10.3. O projeto NetBeans
Vamos construir uma primeira versão da aplicação em que a camada [métier] será simulada. Teremos a seguinte arquitetura:
![]() |
Quando os gestores de eventos ou os modelos solicitarem dados à camada [métier] [2b, 4a], esta fornecer-lhes-á dados fictícios. O objetivo é obter uma camada web que responda corretamente às solicitações do utilizador. Quando isso for alcançado, restar-nos-á apenas instalar a camada de servidor desenvolvida no parágrafo 7.1:
![]() |
Esta será a versão 2 da versão web da nossa aplicação PAM.
O projeto NetBeans da versão 1 é o seguinte projeto Maven:
![]() |
- em [1], os ficheiros 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 ficheiro de mensagens para a internacionalização da aplicação
- em [6], as dependências da aplicação
Vamos analisar alguns destes elementos.
10.3.1. Os ficheiros de configuração
O ficheiro [web.xml] é aquele gerado por predefinição pelo NetBeans, incluindo ainda a configuração de uma página de exceção:
- linha 30: [index.html] é a página inicial da aplicação
- linhas 32-39: configuração da página de exceção
A página [exception.html] é derivada de [ref3]. O seu código é o seguinte:
Qualquer exceção que não seja explicitamente tratada pelo código da aplicação web provocará a exibição de uma página semelhante à que se segue:
![]() |
O ficheiro [faces-config.xml] será o seguinte:
É importante destacar os seguintes pontos:
- linhas 9-14: o ficheiro [messages.properties] será utilizado para a internacionalização das páginas. Estará acessível nas páginas XHTML através da chave msg.
- linha 15: define o ficheiro [messages.properties] como o que deve ser consultado em prioridade para as mensagens de erro apresentadas pelas balizas <h:messages> e <h:message>. Isto permite redefinir algumas mensagens de erro predefinidas do JSF. Esta possibilidade não é utilizada aqui.
10.3.2. A folha de estilo
O ficheiro [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
}
Eis alguns exemplos de código JSF que utilizam estes estilos:
| |
| |
| ![]() |
10.3.3. O ficheiro de mensagens
O ficheiro 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 estas 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 âmbito dos beans
O bean [web.forms.Form] terá um âmbito de pedido:
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á um âmbito 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 vencimentos
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 da aplicação cliente/servidor descrita no parágrafo 7.1.
A classe Metier, que iremos utilizar para testar a camada [web], implementa esta interface da seguinte forma:
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 vencimento
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 vencimento 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){
// criar uma lista de 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);
}
}
// apresenta a lista de funcionários
return listEmployes;
}
}
Deixamos ao leitor a tarefa de descodificar este código. De notar o método utilizado: para não termos de 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 o seu modelo [Form.java]
Estamos agora a construir a página XHTML do formulário, bem como o seu modelo.
Leituras recomendadas em [ref3]:
- exemplo n.º 3 (mv-jsf2-03) para a lista de tags utilizáveis num 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 introduzidos
- exemplo n.º 7 (mv-jsf2-07) para a gestão do botão [Raz]
10.4.1. etapa 1
Questão: Construir o formulário [index.xhtml] e o respetivo modelo [Form.java] necessários para obter a seguinte página:
![]() |
Os componentes de introdução de dados são os seguintes:
id | tipo JSF | modelo | função | |
comboEmployes | <h:selectOneMenu> | String comboEmployesValue List<Empregado> getEmployes() | contém a lista de funcionários no formato «nome apelido». | |
heuresTravaillees | <h:inputText> | String heuresTravaillées | número de horas trabalhadas - número real | |
joursTravailles | <h:inputText> | String joursTravaillés | número de dias trabalhados - número inteiro | |
btnSalaire | <h:commandButton> | inicia o cálculo do salário | ||
btnRaz | <h:commandButton> | repor o formulário ao seu estado inicial |
- o método getEmployes irá devolver uma lista de funcionários que obterá da camada [métier]. Os objetos apresentados pela lista suspensa terão como atributo itemValue o n.º SS do funcionário e, como atributo itemLabel, uma cadeia de caracteres composta pelo nome próprio e apelido do funcionário.
- Os botões [Salaire] e [Raz] não estarão, por enquanto, ligados a gestores de eventos.
- A validade dos dados introduzidos será verificada.

Teste esta versão. Verifique, nomeadamente, se os erros de introdução de dados são devidamente assinalados.
Nota: é 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 falhe.
10.4.2. Etapa 2
Pergunta: preencha o formulário [index.xhtml] e o seu modelo [Form.java] para obter a página seguinte depois de clicar no botão [Salaire]:
![]() |
O botão [Salaire] será ligado ao gestor de eventos calculerSalaire do modelo. Este método utilizará o método calculerFeuilleSalaire da camada [métier]. Esta folha de vencimento será gerada para o colaborador selecionado em [1].
No modelo, a folha de vencimento será representada pelo seguinte campo privado:
private FeuilleSalaire feuilleSalaire;
que dispõe dos métodos get e set.
Para obter as informações contidas neste objeto, poderá escrever-se 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(), em que [form] representa uma instância da classe [Form.java]. O leitor poderá verificar que os métodos get aqui utilizados existem efetivamente, respetivamente, nas classes [Form], [FeuilleSalaire] e [Employe]. Se não fosse esse o caso, seria lançada uma exceção durante a avaliação da expressão.
Teste esta nova versão.
10.4.3. Etapa 3
Questão: preencha o formulário [index.xhtml] e o seu modelo [Form.java] para obter as seguintes informações adicionais:
![]() |
Seguiremos o mesmo procedimento que anteriormente. Existe uma dificuldade com o símbolo monetário do euro que temos em [1], por exemplo. No âmbito de uma aplicação internacionalizada, seria preferível ter o formato de exibição e o símbolo monetário do locale utilizado (en, de, fr, ...). Isto pode ser obtido da seguinte forma:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
Poder-se-ia ter escrito:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
mas com a configuração regional en_GB (inglês GB) continuaria a ser apresentado em euros, quando se deveria utilizar a libra £. A baliza <h:outputFormat> permite apresentar informações com base na locale da página JSF apresentada:
- linha 1: apresenta o parâmetro {0}, que é um número (number) que representa uma quantia em dinheiro (currency)
- linha 2: a baliza <f:param> atribui um valor ao parâmetro {0}. Uma segunda baliza <f:param> atribuiria um valor ao parâmetro {1} e assim sucessivamente.
10.4.4. passo 4
Leituras recomendadas: exemplo n.º 7 (mv-jsf2-07) em [ref3].
Pergunta: preencha o formulário [index.xhtml] e o seu modelo [Form.java] para gerir o botão [Raz].
O botão [Raz] repõe o formulário no estado em que se encontrava quando foi solicitado pela primeira vez através de um GET. Existem várias dificuldades aqui. Algumas foram explicadas em [ref3].
O formulário apresentado pelo botão [Raz] não é o formulário completo, mas apenas a parte saisie do mesmo:

Este resultado pode ser obtido com uma baliza <f:subview> utilizada da seguinte forma:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
A baliza <f:subview> enquadra toda a parte do formulário que pode ser exibida ou ocultada. Qualquer componente pode ser exibido ou ocultado através do atributo rendered. Se rendered="true", o componente é exibido; se rendered="false", não é exibido. Se o atributo rendered assumir o seu valor no modelo, a exibição do componente pode ser controlada programaticamente.
No exemplo acima, controlar-se-á a exibição da vista viewInfos com o seguinte campo:
private boolean viewInfosIsRendered;
acompanhado dos seus métodos get e set. Os métodos que gerem os cliques nos botões [Salaire] e [Raz] atualizarão este valor booleano consoante a vista viewInfos deva ou não ser apresentada.

















