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

  1. 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.
  2. 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:

  1. richiesta - il client browser effettua 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. 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.

  1. 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.
  2. 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:

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

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:

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

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:

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

Ecco alcuni esempi di codice JSF che utilizza questi stili:


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

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

<h:message for="hoursWorked"
 styleClass="error"/>

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:

N.
id
Tipo JSF
modello
ruolo
1
comboEmployees
<h:selectOneMenu>
String comboEmployeesValue
List<Employee> getEmployees()
contiene l'elenco dei dipendenti nel formato
"nome cognome".
2
oreLavorate
<h:inputText>
Stringa oreLavorate
numero di ore lavorate - numero reale
3
giorniLavorati
<h:inputText>
Stringa giorniLavorati
numero di giorni lavorati - numero intero
4
btnSalario
<h:commandButton>
 
avvia il calcolo dello stipendio
5
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.

Image

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:

Image

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.