Skip to content

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 di servlet del server (1 di seguito)
  • gli altri livelli [metier, DAO, jpa] sono ospitati dal contenitore EJB3 del server (2 qui sotto)

Gli elementi [metier, DAO] dell’applicazione in esecuzione nel contenitore EJB3 sono già stati descritti nell’applicazione client/server analizzata nel paragrafo 7.1, la cui architettura era la seguente:

I livelli [metier, DAO] venivano eseguiti nel contenitore EJB3 del server Glassfish e il livello [ui] 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 [metier, DAO, jpa] sono già disponibili.

Nel documento [ref3], 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 Design Pattern MVC (Modello, Vista, Controller). L'elaborazione di una richiesta da parte di un client avviene nel modo seguente:

Se la richiesta viene effettuata tramite un GET, vengono eseguiti i due passaggi seguenti:

  1. richiesta - il browser del cliente invia una richiesta al controller [Faces Servlet]. Quest’ultimo gestisce tutte le richieste dei clienti. È il punto di ingresso dell’applicazione. È la C di MVC.
  2. risposta - il controller C richiede alla pagina JSF selezionata di essere visualizzata. Si tratta della vista, la V di MVC. La pagina JSF utilizza un modello M per inizializzare le parti dinamiche della risposta che deve inviare al cliente. Questo modello è una classe Java che può ricorrere al livello [métier] [4a] per fornire alla vista V i dati di cui ha bisogno.

Se la richiesta viene effettuata con un POST, tra la richiesta e la risposta si inseriscono due fasi aggiuntive:

  1. richiesta - il browser del cliente invia una richiesta al controller [Faces Servlet].
  2. elaborazione - il controller C elabora questa richiesta. Infatti, una richiesta POST è accompagnata da dati che devono essere elaborati. A tal fine, il controller si avvale di gestori di eventi specifici dell’applicazione scritta [2a]. Tali gestori potrebbero aver bisogno del livello di business [2b]. Il gestore dell’evento potrebbe dover aggiornare alcuni modelli M [2c]. Una volta elaborata la richiesta del cliente, questa può generare diverse 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 di caratteri denominata chiave di navigazione.

  1. navigazione - il controller sceglie la pagina JSF (= vista) da inviare al cliente. Questa scelta viene effettuata in base alla chiave di navigazione restituita dal gestore di eventi.
  2. risposta - la pagina JSF selezionata invierà la risposta al cliente. Essa utilizza il proprio modello M per inizializzare le parti dinamiche. Anche questo modello può ricorrere al livello [métier] [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]. Questo 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 denominate «backing beans».
  • Nelle versioni JSF e 1.x, la definizione dei bean e le regole di navigazione da una pagina all'altra sono definite nel file [faces-config.xml]. In esso sono riportati l'elenco delle viste e le regole di transizione da una all'altra. A partire dalla versione JSF 2, le definizioni dei bean possono essere effettuate tramite annotazioni e le transizioni tra le pagine possono essere implementate "in modo rigido" nel codice dei bean.

10.2. Funzionamento dell’applicazione

Quando l’applicazione viene richiamata per la prima volta, viene visualizzata la seguente pagina:

Si compila quindi il modulo e si richiede lo stipendio:

Si ottiene il seguente risultato:

Questa versione calcola uno stipendio fittizio. Non bisogna prestare attenzione al contenuto della pagina, ma alla sua formattazione. Quando si utilizza il pulsante [Raz], si torna alla pagina [A].

Gli inserimenti errati vengono segnalati, come mostra il seguente esempio:

10.3. Il progetto NetBeans

Realizzeremo una prima versione dell’applicazione in cui verrà simulato il livello [métier]. Avremo la seguente architettura:

Quando i gestori di eventi o i modelli richiederanno dati al livello [métier] [2b, 4a], quest’ultimo fornirà loro dati fittizi. L’obiettivo è ottenere un livello web che risponda correttamente alle richieste dell’utente. Una volta raggiunto questo obiettivo, non resterà che installare il livello server sviluppato nel paragrafo 7.1:

Questa sarà la versione 2 della versione web della nostra applicazione PAM.

Il progetto NetBeans della versione 1 è il seguente progetto Maven:

  • in [1], i file di configurazione
  • in [2], le pagine in XHTML e il foglio di stile
  • in [3], le classi del livello in [web]
  • in [4], gli oggetti scambiati tra il livello [web] e il livello [métier] e lo stesso livello [métier]
  • in [5], il file dei messaggi per l’internazionalizzazione dell’applicazione
  • in [6], le dipendenze dell'applicazione

Esaminiamo alcuni di questi elementi.

10.3.1. I file di configurazione

Il file [web.xml] è quello generato di default da NetBeans, con in più la configurazione di una pagina di eccezione:

<?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>
  • riga 30: [index.html] è la pagina iniziale dell’applicazione
  • righe 32-39: configurazione della pagina di eccezione

La pagina [exception.html] deriva da [ref3]. Il suo codice è il seguente:

<?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>

Qualsiasi eccezione non gestita esplicitamente dal codice dell'applicazione web provocherà la visualizzazione di una pagina simile a quella riportata di seguito:

Il file [faces-config.xml] sarà il seguente:

<?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>

Si notino i seguenti punti:

  • righe 9-14: il file [messages.properties] verrà utilizzato per l'internazionalizzazione delle pagine. Sarà accessibile nelle pagine XHTML tramite la chiave msg.
  • riga 15: definisce il file [messages.properties] come file da consultare in via prioritaria per i messaggi di errore visualizzati dai tag <h:messages> e <h:message>. Ciò consente di ridefinire alcuni messaggi di errore predefiniti di JSF. Questa possibilità non viene utilizzata in questo caso.

10.3.2. Il foglio di stile

Il file [styles.css] è 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
}

Ecco alcuni esempi di codice JSF che utilizzano questi stili:


<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. Il file dei messaggi

Il file dei messaggi [messages_fr.properties] è il seguente:


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

Questi messaggi sono tutti utilizzati nella pagina [index.xhtml], ad eccezione di quelli delle righe 11-15, utilizzati nella pagina [exception.xhtml].

10.3.4. L'ambito dei bean

Il bean [web.forms.Form] avrà un ambito «request»:


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

@ManagedBean
@RequestScoped
public class Form implements Serializable {

Il bean [web.utils.ChangeLocale] avrà un ambito «application»:


package web.utils;

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

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // la pagina 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;
  }
  
  
}

10.3.5. Il livello [métier]

Il livello [métier] implementa la seguente interfaccia IMetierLocal:


package metier;

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

@Local
public interface IMetierLocal {
  // ottenere la busta paga
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // elenco dei dipendenti
  List<Employe> findAllEmployes();
}

Questa interfaccia è quella utilizzata nella parte server dell'applicazione client/server descritta al paragrafo 7.1.

La classe Metier che utilizzeremo per testare il livello [web] implementa questa interfaccia nel modo seguente:


package metier;

...
public class Metier implements IMetierLocal {
  
  // dizionario dei dipendenti indicizzato per numero SS
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  // elenco dei dipendenti 
  private List<Employe> listEmployes;
  
  // ottenere la busta paga
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // si recupera il dipendente con n. SS
    Employe e=hashEmployes.get(SS);
    // si genera una busta paga fittizia
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
  }
  
  // elenco dei dipendenti
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // creazione di un elenco di due dipendenti
      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)));
      // dizionario dei dipendenti indicizzato in base al n. SS
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
    }
    // viene restituito l'elenco dei dipendenti
    return listEmployes;
  }
}

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 [métier]. Quando il livello [web] sarà dichiarato corretto, potremo sostituirlo con il vero livello [métier].

10.4. Il modulo [index.xhtml] e il relativo modello [Form.java]

Ora creiamo la pagina XHTML del modulo e il relativo modello.

Letture consigliate in [ref3]:

  • esempio n. 3 (mv-jsf2-03) per l’elenco dei tag utilizzabili in un modulo
  • esempio n. 4 (mv-jsf2-04) per gli elenchi a discesa popolati dal modello
  • esempio n. 6 (mv-jsf2-06) per la convalida dei dati inseriti
  • esempio n. 7 (mv-jsf2-07) per la gestione del pulsante [Raz]

10.4.1. Fase 1


Domanda: Creare il modulo [index.xhtml] e il relativo modello [Form.java] necessari per ottenere la pagina seguente:


I componenti di immissione dati sono i seguenti:

id
tipo JSF
modello
ruolo
1
comboEmployes
<h:selectOneMenu>
Stringa comboEmployesValue
List<Dipendente> getEmployes()
contiene l'elenco dei dipendenti nel formato
"nome cognome".
2
heuresTravaillees
<h:inputText>
Stringa heuresTravaillées
numero di ore lavorate - numero effettivo
3
joursTravailles
<h:inputText>
Stringa joursTravaillés
numero di giorni lavorati - numero intero
4
btnSalaire
<h:commandButton>
 
avvia il calcolo dello stipendio
5
btnRaz
<h:commandButton>
 
ripristina il modulo allo stato iniziale
  • il metodo getEmployes restituirà un elenco di dipendenti che otterrà dal livello [métier]. Gli oggetti visualizzati dal menu a tendina avranno come attributo itemValue il numero SS del dipendente e, come attributo itemLabel, una stringa composta dal nome e dal cognome del dipendente.
  • I pulsanti [Salaire] e [Raz] non saranno per il momento collegati a gestori di eventi.
  • Verrà verificata la validità dei dati inseriti.

Image

Provate questa versione. Verificate in particolare che gli errori di inserimento 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. Fase 2


Domanda: compilare il modulo [index.xhtml] e il relativo modello [Form.java] per ottenere la pagina seguente una volta cliccato il pulsante [Salaire]:


Il pulsante [Salaire] sarà collegato al gestore di eventi calculerSalaire del modello. Questo metodo utilizzerà il metodo calculerFeuilleSalaire del livello [métier]. Questa busta paga verrà generata per il dipendente selezionato in [1].

Nel modello, la busta paga sarà rappresentata dal seguente campo privato:


  private FeuilleSalaire feuilleSalaire;

dotato dei metodi get e set.

Per ottenere le informazioni contenute in questo oggetto, nella pagina JSF sarà possibile inserire espressioni come la seguente:


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

L'espressione dell'attributo "value" verrà valutata come segue:

[form].getFeuilleSalaire().getEmploye().getNom(), dove [form] rappresenta un'istanza della classe [Form.java]. Il lettore potrà verificare che i metodi get qui utilizzati esistano effettivamente, rispettivamente, nelle classi [Form], [FeuilleSalaire] e [Employe]. Se così non fosse, verrebbe generata un'eccezione durante la valutazione dell'espressione.

Provate questa nuova versione.

10.4.3. Fase 3


Domanda: compilare il modulo [index.xhtml] e il relativo modello [Form.java] per ottenere le seguenti informazioni aggiuntive:


Si seguirà la stessa procedura di prima. C'è una difficoltà con il simbolo monetario dell'euro che si trova, ad esempio, in [1]. Nel contesto di un'applicazione internazionalizzata, sarebbe preferibile disporre del formato di visualizzazione e del simbolo monetario del locale utilizzato (en, de, fr, ...). Ciò può essere ottenuto nel modo seguente:


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

Si sarebbe potuto scrivere:


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

ma con l'impostazione locale en_GB (inglese GB) si continuerebbe ad avere una visualizzazione in euro, mentre si dovrebbe utilizzare la sterlina £. Il tag <h:outputFormat> consente di visualizzare informazioni in base alla locale della pagina JSF visualizzata:

  • riga 1: visualizza il parametro {0}, che è un numero (number) che rappresenta una somma di denaro (currency)
  • riga 2: il tag <f:param> assegna un valore al parametro {0}. Un secondo tag <f:param> assegnerebbe un valore al parametro indicato con {1} e così via.

10.4.4. Fase 4

Letture consigliate: esempio n. 7 (mv-jsf2-07) in [ref3].


Domanda: completare il modulo [index.xhtml] e il relativo modello [Form.java] per gestire il pulsante [Raz].


Il pulsante [Raz] riporta il modulo allo stato in cui si trovava quando è stato richiesto per la prima volta tramite un GET. Ci sono diverse difficoltà in questo caso. Alcune sono state spiegate in [ref3].

Il modulo restituito dal pulsante [Raz] non è l'intero modulo, ma solo la parte saisie di esso:

Image

Questo risultato può essere ottenuto utilizzando un tag <f:subview> nel modo seguente:


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

Il tag <f:subview> racchiude l'intera parte del modulo che può essere visualizzata o nascosta. Qualsiasi componente può essere visualizzato o nascosto tramite l'attributo rendered. Se rendered="true", il componente viene visualizzato; se rendered="false", non viene visualizzato. Se l’attributo rendered assume un valore nel modello, la visualizzazione del componente può essere controllata a livello di programma.

Nell’esempio sopra riportato, la visualizzazione della vista viewInfos verrà controllata tramite il seguente campo:


  private boolean viewInfosIsRendered;

insieme ai relativi metodi get e set. I metodi che gestiscono i clic sui pulsanti [Salaire] e [Raz] aggiorneranno questo valore booleano a seconda che la vista viewInfos debba essere visualizzata o meno.