10. Versione 5 - Applicazione Web PAM / JSF
10.1. Architettura dell'applicazione
L'architettura dell'applicazione web PAM sarà la seguente:
![]() |
In questa versione, il server GlassFish ospiterà tutti i livelli dell'applicazione:
- il livello [web] è ospitato dal contenitore servlet del server (1 sotto)
- gli altri livelli [logica di business, DAO, JPA] sono ospitati dal contenitore EJB3 del server (2 sotto)
![]() |
I componenti [logica di business, DAO] dell'applicazione in esecuzione nel contenitore EJB3 sono già stati implementati nell'applicazione client/server discussa nella Sezione 7.1, che presentava la seguente architettura:
![]() |
I livelli [logica di business, DAO] giravano nel contenitore EJB3 del server GlassFish, mentre il livello [UI] girava in un'applicazione console o Swing su un'altra macchina:
![]() |
Nell'architettura della nuova applicazione:
![]() |
è necessario scrivere solo il livello [web / jsf]. Gli altri livelli [business, DAO, JPA] sono già presenti.
Nel documento [rif. 3] viene illustrato che un'applicazione web in cui il livello web è implementato con Java Server Faces presenta un'architettura simile alla seguente:
![]() |
Questa architettura implementa il modello di progettazione MVC (Model, View, Controller). L'elaborazione di una richiesta del client procede come segue:
Se la richiesta viene effettuata utilizzando un GET, vengono eseguiti i due passaggi seguenti:
- richiesta - il browser del client invia una richiesta al controller [Faces Servlet]. Il controller gestisce tutte le richieste dei client. È il punto di ingresso dell'applicazione. È la C in MVC.
- risposta - il controller C istruisce la pagina JSF selezionata a eseguire il rendering. Questa è la vista, la V in MVC. La pagina JSF utilizza un modello M per inizializzare le parti dinamiche della risposta che deve inviare al client. Questo modello è una classe Java che può richiamare il livello [business] [4a] per fornire alla vista V i dati di cui ha bisogno.
Se la richiesta viene effettuata tramite un POST, tra la richiesta e la risposta si verificano due passaggi aggiuntivi:
- richiesta - il client browser effettua una richiesta al controller [Faces Servlet].
- elaborazione - il controller C elabora questa richiesta. Infatti, una richiesta POST è accompagnata da dati che devono essere elaborati. Per farlo, il controller è assistito da gestori di eventi specifici dell'applicazione [2a]. Questi gestori potrebbero richiedere il livello business [2b]. Il gestore di eventi potrebbe dover aggiornare alcuni modelli M [2c]. Una volta che la richiesta del client è stata elaborata, può innescare varie risposte. Un esempio classico è:
- una pagina di errore se la richiesta non è stata elaborata correttamente
- una pagina di conferma in caso contrario
Il gestore di eventi restituisce al controller [Faces Servlet] un risultato di tipo stringa chiamato chiave di navigazione.
- navigazione - il controller seleziona la pagina JSF (= vista) da inviare al client. Questa selezione si basa sulla chiave di navigazione restituita dal gestore di eventi.
- risposta - la pagina JSF selezionata invia la risposta al client. Utilizza il proprio modello M per inizializzare le parti dinamiche. Questo modello può anche richiamare il livello [business] [4a] per fornire alla pagina JSF i dati di cui ha bisogno.
In un progetto JSF:
- il controller C è il servlet [javax.faces.webapp.FacesServlet]. Si trova nella libreria [jsf-api.jar].
- Le viste V sono implementate dalle pagine JSF.
- I modelli M e i gestori di eventi sono implementati da classi Java spesso chiamate "backing beans".
- Nelle versioni JSF 1.x, le definizioni dei bean e le regole per la navigazione da una pagina all'altra sono definite nel file [faces-config.xml]. Esso contiene l'elenco delle viste e le regole per la transizione da una all'altra. A partire dalla versione 2 di JSF, le definizioni dei bean possono essere effettuate utilizzando annotazioni, e le transizioni tra le pagine possono essere codificate direttamente nel codice del bean.
10.2. Come funziona l'applicazione
Quando l'applicazione viene richiesta per la prima volta, appare la seguente pagina:
![]() |
A questo punto si compila il modulo e si richiede lo stipendio:
![]() |
Viene visualizzato il seguente risultato:
![]() |
Questa versione calcola uno stipendio fittizio. Non prestare attenzione al contenuto della pagina, ma piuttosto al suo layout. Quando clicchi sul pulsante [Reset], torni alla pagina [A].
I dati inseriti in modo errato vengono segnalati, come mostrato nell'esempio seguente:
![]() |
10.3. Il progetto NetBeans
Realizzeremo una versione iniziale dell'applicazione in cui verrà simulato il livello [business]. Avremo la seguente architettura:
![]() |
Quando i gestori di eventi o i modelli richiedono dati al livello [business] [2b, 4a], questo fornirà loro dati fittizi. L'obiettivo è ottenere un livello web che risponda correttamente alle richieste degli utenti. Una volta raggiunto questo obiettivo, non resterà che installare il livello server sviluppato nella Sezione 7.1:
![]() |
Questa sarà la versione 2 della versione web della nostra applicazione PAM.
Il progetto NetBeans per la versione 1 è il seguente progetto Maven:
![]() |
- in [1], i file di configurazione
- in [2], le pagine XHTML e il foglio di stile
- in [3], le classi del livello [web]
- in [4], gli oggetti scambiati tra il livello [web] e il livello [business], e il livello [business] stesso
- in [5], il file dei messaggi per l'internazionalizzazione dell'applicazione
- in [6], le dipendenze dell'applicazione
Esamineremo alcuni di questi elementi.
10.3.1. File di configurazione
Il file [web.xml] è quello generato di default da NetBeans, insieme alla configurazione per una pagina di eccezione:
<?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>
- riga 30: [index.html] è la pagina iniziale dell'applicazione
- righe 32–39: configurazione della pagina di eccezione
La pagina [exception.html] è tratta da [rif3]. Il suo codice è il seguente:
Qualsiasi eccezione non gestita esplicitamente dal codice dell'applicazione web causerà la visualizzazione di una pagina simile a quella riportata di seguito:
![]() |
Il file [faces-config.xml] sarà il seguente:
Si prega di tenere presente quanto segue:
- Righe 9–14: il file [messages.properties] verrà utilizzato per l'internazionalizzazione della pagina. Sarà accessibile nelle pagine XHTML tramite la chiave msg.
- Riga 15: definisce il file [messages.properties] come fonte prioritaria per i messaggi di errore visualizzati dai tag <h:messages> e <h:message>. Ciò consente di sovrascrivere alcuni messaggi di errore JSF predefiniti. Questa funzionalità non viene utilizzata in questo contesto.
10.3.2. Il foglio di stile
Il file [styles.css] è il seguente:
Ecco alcuni esempi di codice JSF che utilizza questi stili:
| |
| |
| ![]() |
10.3.3. Il file dei messaggi
Il file dei messaggi [messages_fr.properties] è il seguente:
.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
}
Questi messaggi vengono tutti utilizzati nella pagina [index.xhtml], ad eccezione di quelli nelle righe 11–15, che vengono utilizzati nella pagina [exception.xhtml].
10.3.4. L'ambito dei bean
Il bean [web.forms.Form] avrà un ambito di richiesta:
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
Il bean [web.utils.ChangeLocale] avrà ambito applicativo:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
10.3.5. Il livello [aziendale]
Il livello [business] implementa la seguente interfaccia 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;
}
}
Questa interfaccia è quella utilizzata nel lato server dell'applicazione client/server descritta nella Sezione 7.1.
La classe Business che useremo per testare il livello [web] implementa questa interfaccia come segue:
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();
}
Lasciamo al lettore il compito di decifrare questo codice. Si noti il metodo utilizzato: per evitare di dover implementare la parte EJB dell'applicazione, simuliamo il livello [business]. Una volta verificata la correttezza del livello [web], potremo sostituirlo con il vero e proprio livello [business].
10.4. Il modulo [index.xhtml] e il relativo modello [Form.java]
Ora creeremo la pagina XHTML del modulo e il suo modello.
Lettura consigliata in [ref3]:
- Esempio #3 (mv-jsf2-03) per l'elenco dei tag che possono essere utilizzati in un modulo
- Esempio #4 (mv-jsf2-04) per gli elenchi a discesa popolati dal modello
- Esempio #6 (mv-jsf2-06) per la convalida degli input
- Esempio #7 (mv-jsf2-07) per la gestione del pulsante [Cancella]
10.4.1. Passaggio 1
Domanda: Crea il modulo [index.xhtml] e il relativo modello [Form.java] necessari per visualizzare la seguente pagina:
![]() |
I componenti di input sono i seguenti:
id | Tipo JSF | modello | ruolo | |
comboEmployees | <h:selectOneMenu> | String comboEmployeesValue List<Employee> getEmployees() | contiene l'elenco dei dipendenti nel formato "nome cognome". | |
oreLavorate | <h:inputText> | Stringa oreLavorate | numero di ore lavorate - numero reale | |
giorniLavorati | <h:inputText> | Stringa giorniLavorati | numero di giorni lavorati - numero intero | |
btnSalario | <h:commandButton> | avvia il calcolo dello stipendio | ||
btnReset | <h:commandButton> | ripristina il modulo allo stato iniziale |
- Il metodo getEmployees restituirà un elenco di dipendenti recuperati dal livello [business]. Gli oggetti visualizzati dalla casella combinata avranno l'attributo itemValue impostato sul numero di previdenza sociale del dipendente e l'attributo itemLabel impostato su una stringa composta dal nome e dal cognome del dipendente.
- I pulsanti [Salario] e [Cancella] non saranno collegati a gestori di eventi per il momento.
- Verrà verificata la validità dell'input.

Prova questa versione. In particolare, verifica che gli errori di input vengano segnalati correttamente.
Nota: è importante che gli attributi ID dei componenti della pagina non contengano caratteri accentati. Con Glassfish 3.1.2, ciò causa il crash dell'applicazione.
10.4.2. Passaggio 2
Domanda: Completa il modulo [index.xhtml] e il relativo modello [Form.java] in modo che, una volta cliccato il pulsante [Salary], venga visualizzata la seguente pagina:
![]() |
Il pulsante [Stipendio] sarà collegato al gestore di eventi calculateSalary del modello. Questo metodo utilizzerà il metodo calculatePaystub del livello [business]. Questa busta paga verrà generata per il dipendente selezionato in [1].
Nel modello, la busta paga sarà rappresentata dal seguente campo privato:
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;
}
}
che dispone di metodi get e set.
Per recuperare le informazioni contenute in questo oggetto, è possibile scrivere espressioni come la seguente nella pagina JSF:
private FeuilleSalaire feuilleSalaire;
L'espressione nell'attributo value verrà valutata come segue:
[form].getPayrollSheet().getEmployee().getName() dove [form] rappresenta un'istanza della classe [Form.java]. Il lettore può verificare che i metodi get qui utilizzati esistano effettivamente nelle classi [Form], [PayrollSheet] e [Employee], rispettivamente. Se così non fosse, verrebbe generata un'eccezione durante la valutazione dell'espressione.
Prova questa nuova versione.
10.4.3. Passaggio 3
Domanda: Completa il modulo [index.xhtml] e il relativo modello [Form.java] per ottenere le seguenti informazioni aggiuntive:
![]() |
Seguiremo lo stesso approccio di prima. Ad esempio, c'è un problema con il simbolo della valuta euro che si trova in [1]. In un'applicazione internazionalizzata, sarebbe preferibile utilizzare il formato di visualizzazione e il simbolo della valuta della lingua selezionata (en, de, fr, ...). Ciò può essere ottenuto come segue:
<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
Avremmo potuto scrivere:
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
ma con le impostazioni locali en_GB (inglese britannico), l'importo verrebbe comunque visualizzato in euro quando invece dovrebbe essere in sterline (£). Il tag <h:outputFormat> consente di visualizzare le informazioni in base alle impostazioni locali della pagina JSF visualizzata:
- riga 1: visualizza il parametro {0}, che è un numero che rappresenta un importo monetario
- riga 2: il tag <f:param> assegna un valore al parametro {0}. Un secondo tag <f:param> assegnerebbe un valore al parametro denominato {1}, e così via.
10.4.4. Passaggio 4
Lettura consigliata: Esempio #7 (mv-jsf2-07) in [ref3].
Domanda: Completa il modulo [index.xhtml] e il relativo modello [Form.java] per gestire il pulsante [Reset].
Il pulsante [Reset] riporta il modulo allo stato in cui si trovava quando è stato richiesto per la prima volta tramite una richiesta GET. Ci sono diverse difficoltà in questo caso. Alcune sono state spiegate in [rif3].
Il modulo restituito dal pulsante [Raz] non è il modulo completo, ma solo la parte in cui sono stati inseriti dei dati:

Questo risultato può essere ottenuto utilizzando un tag <f:subview> come segue:
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">
Il tag <f:subview> racchiude l'intera porzione del modulo che può essere visualizzata o nascosta. Qualsiasi componente può essere visualizzato o nascosto utilizzando l'attributo rendered. Se rendered="true", il componente viene visualizzato; se rendered="false", non viene visualizzato. Se l'attributo rendered prende il suo valore dal modello, la visualizzazione del componente può essere controllata a livello di programmazione.
Nell'esempio sopra riportato, controlleremo la visualizzazione della vista viewInfos utilizzando il seguente campo:
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>
insieme ai relativi metodi get e set. I metodi che gestiscono i clic sui pulsanti [Salario] e [Cancella] aggiorneranno questo valore booleano a seconda che la vista viewInfos debba essere visualizzata o meno.

















