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] é hospedada pelo contentor de servlets do servidor (1 abaixo)
- as outras camadas [lógica de negócio, DAO, JPA] são hospedadas pelo contentor EJB3 do servidor (2 abaixo)
![]() |
Os componentes [lógica de negócio, DAO] da aplicação em execução no contentor EJB3 já foram implementados na aplicação cliente/servidor discutida na Secção 7.1, que apresentava a seguinte arquitetura:
![]() |
As camadas [lógica de negócios, DAO] eram executadas no contentor EJB3 do servidor GlassFish, e a camada [UI] era executada 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 [negócio, 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 do cliente decorre da seguinte forma:
Se a solicitação for feita utilizando um GET, são executadas as duas etapas seguintes:
- solicitação - o navegador do cliente envia uma solicitação ao controlador [Faces Servlet]. O controlador lida com todas as solicitações do cliente. É o ponto de entrada da aplicação. É o C em MVC.
- resposta - o controlador C instrui a página JSF selecionada a renderizar. Esta é a vista, o V em 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 [de negócio] [4a] para fornecer à vista V os dados de que necessita.
Se o pedido for feito através de um POST, ocorrem duas etapas adicionais entre o pedido e a resposta:
- pedido - o cliente do navegador faz um pedido ao controlador [Faces Servlet].
- processamento - o controlador C processa esta solicitação. De facto, uma solicitação POST é acompanhada por dados que devem ser processados. Para fazer isso, o controlador é auxiliado por manipuladores de eventos específicos da aplicação [2a]. Esses manipuladores podem requerer a camada de negócios [2b]. O manipulador de eventos pode precisar de atualizar certos modelos M [2c]. Uma vez que a solicitação do cliente tenha sido processada, ela pode desencadear várias respostas. Um exemplo clássico é:
- uma página de erro, se a solicitação não puder ser processada corretamente
- uma página de confirmação, caso contrário
O manipulador de eventos devolve um resultado do tipo string, denominado chave de navegação, ao controlador [Faces Servlet].
- navegação - o controlador seleciona a página JSF (= vista) a enviar ao cliente. Esta seleção baseia-se na chave de navegação devolvida pelo manipulador de eventos.
- resposta - a página JSF selecionada envia a resposta ao cliente. Utiliza o seu modelo M para inicializar as suas partes dinâmicas. Este modelo também pode recorrer à camada [de negócios] [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 por páginas JSF.
- Os modelos M e os manipuladores de eventos são implementados por classes Java frequentemente chamadas de «backing beans».
- Nas versões JSF 1.x, as definições de beans e as regras para navegar de uma página para outra são definidas no ficheiro [faces-config.xml]. Este contém a lista de vistas e as regras para a transição de uma para outra. A partir da versão 2 do JSF, as definições de beans podem ser feitas utilizando anotações, e as transições de página podem ser codificadas diretamente no código do bean.
10.2. Como funciona a aplicação
Quando a aplicação é solicitada pela primeira vez, aparece a seguinte página:
![]() |
Em seguida, preencha o formulário e solicite o salário:
![]() |
É apresentado o seguinte resultado:
![]() |
Esta versão calcula um salário fictício. Não preste atenção ao conteúdo da página, mas sim ao seu layout. Ao clicar no botão [Reiniciar], volta à página [A].
As entradas incorretas são assinaladas, como se pode ver no exemplo seguinte:
![]() |
10.3. O Projeto NetBeans
Iremos construir uma versão inicial da aplicação, na qual a camada [de negócios] será simulada. Teremos a seguinte arquitetura:
![]() |
Quando os manipuladores de eventos ou modelos solicitarem dados à camada [de negócios] [2b, 4a], esta fornecer-lhes-á dados fictícios. O objetivo é obter uma camada web que responda corretamente aos pedidos dos utilizadores. Uma vez alcançado este objetivo, resta apenas instalar a camada de servidor desenvolvida na Secção 7.1:
![]() |
Esta será a versão 2 da versão web da nossa aplicação PAM.
O projeto NetBeans para a versão 1 é o seguinte projeto Maven:
![]() |
- em [1], os ficheiros de configuração
- em [2], as páginas XHTML e a folha de estilo
- em [3], as classes da camada [web]
- em [4], os objetos trocados entre a camada [web] e a camada [negócio], e a própria camada [negócio]
- em [5], o ficheiro de mensagens para a internacionalização da aplicação
- em [6], as dependências da aplicação
Iremos rever alguns destes elementos.
10.3.1. Ficheiros de configuração
O ficheiro [web.xml] é aquele gerado por predefinição pelo NetBeans, juntamente com a configuração para uma página de exceção:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- Apache CXF -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<!-- lower layers -->
<import resource="classpath:spring-config-metier-dao.xml" />
<!-- web service -->
<bean id="wsMetier" class="pam.ws.PamWsMetier">
<property name="metier" ref="metier"/>
</bean>
<jaxws:endpoint id="wsmetier"
implementor="#wsMetier"
address="/metier">
</jaxws:endpoint>
</beans>
- 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] foi retirada de [ref3]. O seu código é o seguinte:
Qualquer exceção que não seja explicitamente tratada pelo código da aplicação web fará com que seja apresentada uma página semelhante à seguinte:
![]() |
O ficheiro [faces-config.xml] terá o seguinte conteúdo:
Tenha em atenção os seguintes pontos:
- Linhas 9–14: O ficheiro [messages.properties] será utilizado para a internacionalização da página. Estará acessível nas páginas XHTML através da chave msg.
- Linha 15: define o ficheiro [messages.properties] como a fonte prioritária para as mensagens de erro apresentadas pelas tags <h:messages> e <h:message>. Isto permite-lhe substituir determinadas mensagens de erro padrão do JSF. Esta funcionalidade não é utilizada aqui.
10.3.2. A folha de estilo
O ficheiro [styles.css] é o seguinte:
Aqui estão 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:
.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
}
Todas estas mensagens são utilizadas na página [index.xhtml], exceto as das linhas 11–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:
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
O bean [web.utils.ChangeLocale] terá âmbito de aplicação:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
10.3.5. A camada [de negócios]
A camada [de negócios] implementa a seguinte interface IMetierLocal:
package web.utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// page locale
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;
}
}
Esta interface é a utilizada no lado do servidor da aplicação cliente/servidor descrita na Secção 7.1.
A classe Business que iremos utilizar para testar a camada [web] implementa esta interface da seguinte forma:
package metier;
import java.util.List;
import javax.ejb.Local;
import jpa.Employe;
@Local
public interface IMetierLocal {
// get your payslip
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// list of employees
List<Employe> findAllEmployes();
}
Deixamos ao leitor a tarefa de decifrar este código. Repare no método utilizado: para evitar ter de implementar a parte EJB da aplicação, simulamos a camada [business]. Assim que a camada [web] for verificada como correta, podemos então substituí-la pela camada [business] real.
10.4. O formulário [index.xhtml] e o seu modelo [Form.java]
Vamos agora construir a página XHTML do formulário e o seu modelo.
Leitura recomendada em [ref3]:
- Exemplo #3 (mv-jsf2-03) para a lista de tags que podem ser usadas num formulário
- Exemplo #4 (mv-jsf2-04) para listas suspensas preenchidas pelo modelo
- Exemplo #6 (mv-jsf2-06) para validação de entradas
- Exemplo #7 (mv-jsf2-07) para o tratamento do botão [Limpar]
10.4.1. Passo 1
Pergunta: Crie o formulário [index.xhtml] e o seu modelo [Form.java] necessários para apresentar a seguinte página:
![]() |
Os componentes de entrada são os seguintes:
id | Tipo JSF | modelo | função | |
comboEmployees | <h:selectOneMenu> | String comboEmployeesValue List<Employee> getEmployees() | contém a lista de funcionários no formato "nome apelido". | |
horasTrabalhadas | <h:inputText> | String horasTrabalhadas | número de horas trabalhadas - número real | |
diasTrabalhados | <h:inputText> | String diasTrabalhados | número de dias trabalhados - inteiro | |
btnSalário | <h:commandButton> | inicia o cálculo do salário | ||
btnReset | <h:commandButton> | redefine o formulário para o seu estado inicial |
- O método getEmployees irá devolver uma lista de funcionários recuperados da camada [business]. Os objetos apresentados pela caixa combinada terão o atributo itemValue definido com o número de segurança social do funcionário e o atributo itemLabel definido com uma cadeia de caracteres composta pelo nome e apelido do funcionário.
- Os botões [Salário] e [Limpar] não estarão ligados a manipuladores de eventos por enquanto.
- A validade da entrada será verificada.

Teste esta versão. Em particular, verifique se os erros de entrada são devidamente sinalizados.
Nota: É importante que os atributos ID dos componentes da página não contenham caracteres acentuados. Com o Glassfish 3.1.2, isto faz com que a aplicação entre em falha.
10.4.2. Passo 2
Pergunta: Preencha o formulário [index.xhtml] e o seu modelo [Form.java] para apresentar a seguinte página assim que o botão [Salary] for clicado:
![]() |
O botão [Salário] será associado ao manipulador de eventos calculateSalary do modelo. Este método utilizará o método calculatePaystub da camada [business]. Este recibo de vencimento será gerado para o funcionário selecionado em [1].
No modelo, o recibo de vencimento será representado pelo seguinte campo privado:
package metier;
...
public class Metier implements IMetierLocal {
// employee dictionary indexed by n° SS
private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
// list of employees
private List<Employe> listEmployes;
// get your payslip
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// we retrieve employee n° SS
Employe e=hashEmployes.get(SS);
// we make a fiictive payslip
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
}
// list of employees
public List<Employe> findAllEmployes() {
if(listEmployes==null){
// create a list of two employees
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)));
// employee dictionary indexed by n° SS
for(Employe e:listEmployes){
hashEmployes.put(e.getSS(),e);
}
}
// we return the list of employees
return listEmployes;
}
}
que possui métodos get e set.
Para recuperar as informações contidas neste objeto, pode escrever expressões como as seguintes na página JSF:
private FeuilleSalaire feuilleSalaire;
A expressão no atributo value será avaliada da seguinte forma:
[form].getPayrollSheet().getEmployee().getName(), em que [form] representa uma instância da classe [Form.java]. O leitor pode verificar que os métodos get aqui utilizados existem efetivamente nas classes [Form], [PayrollSheet] e [Employee], respetivamente. Se assim não fosse, seria lançada uma exceção ao avaliar a expressão.
Teste esta nova versão.
10.4.3. Passo 3
Pergunta: Preencha o formulário [index.xhtml] e o seu modelo [Form.java] para obter as seguintes informações adicionais:
![]() |
Seguiremos a mesma abordagem de antes. Existe um problema com o símbolo da moeda do euro encontrado em [1], por exemplo. Numa aplicação internacionalizada, seria preferível utilizar o formato de exibição e o símbolo da moeda da localização selecionada (en, de, fr, ...). Isto pode ser conseguido da seguinte forma:
<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
Poderíamos ter escrito:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
mas com a localização en_GB (inglês britânico), o montante continuaria a ser apresentado em euros, quando deveria ser em libras (£). A tag <h:outputFormat> permite que a informação seja apresentada com base na localização da página JSF apresentada:
- linha 1: exibe o parâmetro {0}, que é um número representando um valor monetário
- linha 2: a tag <f:param> atribui um valor ao parâmetro {0}. Uma segunda tag <f:param> atribuiria um valor ao parâmetro rotulado como {1}, e assim por diante.
10.4.4. Passo 4
Leitura recomendada: Exemplo #7 (mv-jsf2-07) em [ref3].
Pergunta: Preencha o formulário [index.xhtml] e o seu modelo [Form.java] para tratar do botão [Reset].
O botão [Reset] restaura o formulário para o estado em que se encontrava quando foi solicitado pela primeira vez através de um pedido GET. Existem vários desafios aqui. Alguns foram explicados em [ref3].
O formulário devolvido pelo botão [Raz] não é o formulário completo, mas apenas a parte preenchida do mesmo:

Este resultado pode ser obtido utilizando uma tag <f:subview> da seguinte forma:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
A tag <f:subview> engloba toda a parte do formulário que pode ser exibida ou ocultada. Qualquer componente pode ser exibido ou ocultado utilizando o atributo rendered. Se rendered="true", o componente é exibido; se rendered="false", não é. Se o atributo rendered obtiver o seu valor do modelo, a exibição do componente pode ser controlada programaticamente.
No exemplo acima, iremos controlar a exibição da vista viewInfos utilizando o seguinte campo:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
juntamente com os seus métodos get e set. Os métodos que tratam dos cliques nos botões [Salário] e [Limpar] irão atualizar este valor booleano, dependendo se a vista viewInfos deve ser exibida ou não.

















