19. Caso di studio – Versione 1
19.1. Il livello [aziendale] simulato
Esaminiamo l'architettura dell'applicazione che stiamo realizzando:
![]() |
Abbiamo gli archivi dei livelli [business, DAO, JPA] e abbiamo presentato gli elementi di questi livelli che il livello [web] deve conoscere. Siamo pronti a scrivere il livello [web] utilizzando il framework Struts.
Per semplificare il collaudo della nostra applicazione durante lo sviluppo, creeremo un livello business simulato che implementerà l'interfaccia del livello [business]. L'architettura sarà la seguente:
![]() |
Svilupperemo il livello [web] utilizzando il livello [business] simulato. Il collaudo sarà più semplice perché nell'architettura non è più presente un database. Grazie a Spring e all'uso delle interfacce, la sostituzione del livello [business] simulato con l'architettura effettiva [business, DAO, JPA] in una fase successiva non avrà alcun impatto sul codice del livello [web / Struts2]. Il livello [web / Struts2] che stiamo per sviluppare potrà essere utilizzato così com'è.
Il livello [business] simulato che useremo è il seguente:
package metier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;
public class MetierSimule implements IMetier {
// list of employees
private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
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);
// a fictitious payslip is returned
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
for (Employe e : listEmployes) {
hashEmployes.put(e.getSS(), e);
}
}
// we return the list of employees
return listEmployes;
}
}
- riga 11: la classe [MetierSimule] implementa l'interfaccia [IMetier] che il vero e proprio livello [business] implementa.
- riga 14: un dizionario dei dipendenti indicizzato in base al loro numero INSEE
- riga 15: l'elenco dei dipendenti
- righe 27–39: implementazione del metodo findAllEmployees dell'interfaccia [IMetier].
- righe 30–33: creazione di un elenco di due dipendenti
- Righe 34–36: creazione del dizionario dei dipendenti indicizzato per numero INSEE
- Righe 18–24: implementazione del metodo `calculerSalaire` dell'interfaccia [IMetier]. Qui restituiamo una busta paga fittizia.
19.2. Il progetto NetBeans
Il progetto NetBeans è il seguente:
![]() |
- in [1]:
- [applicationContext.xml] è il file di configurazione di Spring
- [tiles.xml] è il file di configurazione per un framework chiamato Tiles.
- [web.xml] è il file di configurazione dell'applicazione web
- in [2]: le diverse viste dell'applicazione
- in [3]:
- [messages.properties]: il file dei messaggi
- [struts.xml]: il file di configurazione di Struts
![]() |
- in [4]: il codice sorgente dell'applicazione. Le azioni Struts si trovano nel pacchetto [web.actions].
- in [5]: il livello [business] simulato
- in [6]: gli archivi utilizzati. Ecco gli archivi per i vari strumenti utilizzati: Spring, Tiles, Struts 2, il plugin di integrazione Struts 2/Spring e il plugin di integrazione Struts 2/Tiles.
- in [7]: l'archivio del vero e proprio livello [business, DAO, JPA]. Ci dà accesso alle entità JPA, all'interfaccia [IMetier] e alle classi [PayrollSheet] e [PayrollItems]. Tutti questi elementi sono effettivamente utilizzati dalla nostra classe [SimulatedBusiness].
19.3. Configurazione del progetto
Il progetto è configurato da vari file:
- [web.xml], che configura l'applicazione web
- [struts.xml], che configura il framework Struts
- [applicationContext.xml], che configura il framework Spring
- [tiles.xml], che configura il framework Tiles
19.3.1. Configurazione dell'applicazione web
Il file [web.xml] è il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="pam_struts_01" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Pam</display-name>
<!-- Tiles -->
<context-param>
<param-name> org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG </param-name>
<param-value>/WEB-INF/tiles.xml</param-value>
</context-param>
<listener>
<listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
</listener>
<!-- Struts 2 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- righe 13–20: configurazione del filtro Struts 2 – già visto
- righe 22–24: configurazione del listener Spring – già visto
- righe 9–11: configurazione del listener Tiles. La classe [org.apache.struts2.tiles.StrutsTilesListener] verrà istanziata all'avvio dell'applicazione web. Utilizzerà quindi il proprio file di configurazione. Questo è definito dalle righe 5–8. Il file di configurazione Tiles è quindi il file [WEB-INF/tiles.xml].
In definitiva, all'avvio dell'applicazione Struts, vengono istanziate tre classi:
- una per il filtro Struts 2. Questo è il componente che gestisce la "C" in MVC.
- un'altra per il listener Spring. Spring utilizzerà il file [applicationContext.xml] per istanziare i livelli [business, DAO, JPA] dell'applicazione. Spring istanzierà inoltre, come nell'esempio precedente, una classe [Config] che conterrà i dati a livello di applicazione. Infine, Spring inietterà un riferimento a questa singola istanza [Config] in ogni azione Struts che ne ha bisogno.
- Un altro per il listener Tiles. Questo framework gestirà la gestione delle viste. Torneremo su questo argomento tra poco.
19.3.2. Configurazione del framework Struts
Il framework Struts è configurato dal seguente file [struts.xml]:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<!-- internationalization -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- spring integration -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- struts /Tiles actions -->
<package name="default" namespace="/" extends="tiles-default">
<!-- default action -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- action Form -->
<action name="Formulaire" class="web.actions.Formulaire" method="input">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
</action>
<!-- action FaireSimulation -->
<action name="FaireSimulation" class="web.actions.Formulaire" method="calculSalaire">
<result name="success" type="tiles">simulation</result>
<result name="exception" type="tiles">exception</result>
<result name="input" type="tiles">saisie</result>
</action>
<!-- action EnregistrerSimulation -->
<action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
<result name="error" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
<!-- action RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- action VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
<!-- action RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer" method="execute">
<result name="erreur" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
<!-- action TerminerSession -->
<action name="TerminerSession" class="web.actions.Terminer" method="execute">
<result name="success" type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
</package>
</struts>
Discuteremo le varie azioni Struts man mano che le esamineremo. Per ora, prendete nota dei seguenti punti:
- riga 8: definisce il file dei messaggi
- riga 10: definisce come i bean Spring vengono iniettati nelle azioni Struts. L'iniezione si basa sul nome del bean. Il campo dell'azione Struts che deve essere inizializzato da Spring deve avere lo stesso nome del bean da iniettare.
- riga 25: definisce la vista da visualizzare per il tasto di navigazione "success" dell'azione [Form]. Notiamo che l'elemento <result> ha un attributo type='tiles' che non conosciamo. Conoscevamo il tipo redirect, che permette al client di essere reindirizzato a una vista. Qui, la vista di tipo tiles è gestita dal framework Tiles. Il tipo tiles è definito nel file [struts-plugin.xml] all'interno dell'archivio [struts2-tiles-plugin-2.2.3.1.jar]:
<struts>
<package name="tiles-default" extends="struts-default">
<result-types>
<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult"/>
</result-types>
</package>
</struts>
- Righe 3–5: La definizione del tipo di risultato tiles.
- riga 2: questo tipo è definito nel pacchetto [tiles-default], che estende il pacchetto [struts-default].
- riga 14: definisce il pacchetto [default], che conterrà tutte le azioni dell'applicazione. Per sfruttare la definizione del tipo di vista tiles, il pacchetto estende [tiles-default].
19.3.3. Configurazione del framework Spring
Il framework Spring è configurato dal seguente file [WEB-INF/applicationContext.xml]:
<?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"
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">
<!-- application layers -->
<!-- web -->
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- business -->
<bean id="metier" class="metier.MetierSimule"/>
</beans>
- riga 13: il livello [business] simulato istanziato dalla classe [business.SimulatedBusiness]
- righe 9–11: configurazione di un bean denominato config. Come nell'esempio studiato in precedenza, questo bean verrà utilizzato per incapsulare le informazioni a livello di applicazione. La classe associata a questo bean è la seguente classe [Config]:
package web;
import java.util.List;
import jpa.Employe;
import metier.IMetier;
public class Config {
// business layer initialized by Spring
private IMetier metier;
// list of employees
private List<Employe> employes;
// errors
private Exception initException;
// manufacturer
public Config() {
}
// spring method for object initialization
public void init() {
// we ask for the list of employees
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
// getters and setters
...
}
Torniamo alla configurazione del bean config:
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- métier -->
<bean id="metier" class="metier.Metier">
...
</bean>
Nella riga 2, possiamo vedere che il business bean della riga 5 viene iniettato (ref) nel campo denominato business (name) dell'oggetto [Config]. Il business bean è un riferimento al livello [business]:
![]() |
Per interagire con il livello [business], tutte le azioni Struts nel livello [web] avranno bisogno di un riferimento ad esso. Possiamo dire che il riferimento al livello [business] è un dato a livello di applicazione. Tutte le richieste di tutti gli utenti ne avranno bisogno. Ecco perché inseriamo questo riferimento nell'oggetto [Config]. Inoltre, alla riga 1, la configurazione del bean config include un attributo init-method. Questo attributo specifica il metodo del bean da eseguire dopo l'istanziazione del bean. Qui, specifichiamo che dopo l'istanziazione della classe [web.Config], deve essere eseguito il suo metodo init. Questo metodo è il seguente:
// business layer initialized by Spring
private IMetier metier;
// list of employees
private List<Employe> employes;
// errors
private Exception initException;
// manufacturer
public Config() {
}
// spring method for object initialization
public void init() {
// we ask for the list of employees
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
Quando viene eseguito il metodo init, il campo business della classe è stato istanziato da Spring. Il metodo init ha quindi accesso all'interfaccia del livello business [IMetier] (riga 2):
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// get your payslip
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// list of employees
List<Employe> findAllEmployes();
}
- Riga 8: il metodo calcola lo stipendio di un dipendente
- riga 10: il metodo recupera l'elenco dei dipendenti
Possiamo vedere che il metodo init richiede l'elenco dei dipendenti dal livello [business]. Questo elenco è memorizzato nel campo alla riga 4. Se si verifica un'eccezione, questa viene memorizzata nel campo alla riga 6.
In conclusione, il singolo oggetto [Config] contiene:
- un riferimento al livello [business]
- l'elenco dei dipendenti
19.4. Generazione delle viste delle tessere
Come abbiamo visto nel file di configurazione di Struts, le viste saranno generate dal framework Tiles. Spiegheremo solo ciò che è strettamente necessario per scrivere la nostra applicazione.
Tiles consente di generare viste da una pagina master. Questa pagina, qui denominata [MasterPage.jsp], sarà un insieme dei seguenti frammenti JSP:
Questi frammenti JSP sono definiti nel progetto NetBeans:

Il framework Tiles ci permette di definire quali frammenti verranno inseriti nella pagina master.
La pagina master [MasterPage.jsp] è la seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="styles.css" rel="stylesheet" type="text/css"/>
<title>
<tiles:insertAttribute name="titre" ignore="true" />
</title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<tiles:insertAttribute name="entete" />
<hr/>
<tiles:insertAttribute name="saisie" />
<tiles:insertAttribute name="simulation" />
<tiles:insertAttribute name="exception" />
<tiles:insertAttribute name="erreur" />
<tiles:insertAttribute name="simulations" />
</body>
</html>
La pagina master è un contenitore per i frammenti JSP. In questo caso, si tratta di un insieme di sei frammenti, quelli presenti nelle righe da 17 a 23. Al momento della generazione, nella pagina master possono essere assemblati da 0 a 6 frammenti. Questa generazione è regolata dal file [WEB-INF/tiles.xml], che definisce le viste Tiles:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
<!-- the master page -->
<definition name="masterPage" template="/MasterPage.jsp">
<put-attribute name="entete" value="/Entete.jsp"/>
<put-attribute name="titre" value="Pam"/>
<put-attribute name="saisie" value=""/>
<put-attribute name="simulation" value=""/>
<put-attribute name="simulations" value=""/>
<put-attribute name="exception" value=""/>
<put-attribute name="erreur" value=""/>
</definition>
<!-- input view -->
<definition name="saisie" extends="masterPage">
<put-attribute name="saisie" value="/Saisie.jsp"/>
</definition>
<!-- simulation view -->
<definition name="simulation" extends="saisie">
<put-attribute name="simulation" value="/Simulation.jsp"/>
</definition>
<!-- the simulations view -->
<definition name="simulations" extends="masterPage">
<put-attribute name="simulations" value="/Simulations.jsp"/>
</definition>
<!-- the exceptional view -->
<definition name="exception" extends="masterPage">
<put-attribute name="exception" value="/Exception.jsp"/>
</definition>
<!-- error view -->
<definition name="erreur" extends="masterPage">
<put-attribute name="erreur" value="/Erreur.jsp"/>
</definition>
</tiles-definitions>
- Il file sopra riportato definisce sei viste Tiles denominate: masterPage (riga 9), input (riga 20), simulation (riga 25), simulations (riga 30), exception (riga 35), error (riga 40).
- Righe 9–17: definiscono una vista denominata masterPage (nome) e associata alla pagina master [MasterPage.jsp] (modello). Abbiamo visto che questa pagina JSP definiva sei sottoviste. Una vista Tiles associata alla pagina master deve specificare il frammento JSP associato a ciascuna delle sei sottoviste. Notiamo che ad alcune sottoviste è assegnata la stringa vuota come valore. Queste sottoviste non saranno incluse nella pagina master [MasterPage.jsp]. La vista Tiles denominata masterPage consiste quindi esclusivamente nel sottoframmento [Entete.jsp].
- Righe 20–22: definiscono una vista denominata `saisie` che estende la vista `masterPage` definita in precedenza. Ciò significa che eredita tutte le definizioni dalla vista `masterPage`. La sua definizione è equivalente alla seguente:
<definition name="saisie" template="/MasterPage.jsp">
<put-attribute name="entete" value="/Entete.jsp"/>
<put-attribute name="titre" value="Pam"/>
<put-attribute name="saisie" value=""/>
<put-attribute name="simulation" value=""/>
<put-attribute name="simulations" value=""/>
<put-attribute name="exception" value=""/>
<put-attribute name="erreur" value=""/>
<put-attribute name="saisie" value="/Saisie.jsp"/>
</definition>
Possiamo notare che è associata alla pagina JSP [MasterPage.jsp] e che, in quanto tale, deve definire le sei sottoviste di questa pagina. Possiamo notare che la definizione alla riga 9 sovrascrive quella alla riga 4. La vista Tiles denominata "saisie" è quindi composta dai frammenti JSP [Entete.jsp, Saisie.jsp]
Se proseguiamo con questo ragionamento, otteniamo la seguente tabella:
Vista Tiles | Pagine JSP |
19.5. File dei messaggi
L'applicazione è stata internazionalizzata. I messaggi si trovano nei file [messages.properties] e [Formulaire.properties].
Il file [messages.properties] è il seguente:
Pam.titre=Calcul du salaire des assistantes maternelles
Pam.Erreurs.titre=Les erreurs suivantes se sont produites :
Pam.Erreurs.classe=Exception
Pam.Erreurs.message=Message
Pam.Erreur.libelle=L''erreur suivante s''est produite
Pam.Saisie.Heures.libell\u00e9=Heures travaill\u00e9es
Pam.Saisie.Jours.libell\u00e9=Jours travaill\u00e9s
Pam.Saisie.employ\u00e9=Employ\u00e9
Pam.BtnSalaire.libell\u00e9=Salaire
Pam.BtnEffacer.libell\u00e9=Effacer
Simulation.Infos.employe=Informations Employ\u00e9
Simulation.Employe.nom=Nom
Simulation.Employe.prenom=Pr\u00e9nom
Simulation.Employe.adresse=Adresse
Simulation.Employe.indice=Indice
Simulation.Employe.ville=Ville
Simulation.Employe.codePostal=Code Postal
Simulation.Infos.cotisations=Cotisations Sociales
Simulation.Cotisations.csgrds=CsgRds
Simulation.Cotisations.csgrds=Csgd
Simulation.Cotisations.retraite=Retraite
Simulation.Cotisations.secu=S\u00e9cu
Form.Infos.indemnites=Indemnit\u00e9s
Simulation.Indemnites.salaireHoraire=Salaire horaire
Simulation.Indemnites.entretienJour=Entretien/Jour
Simulation.Indemnites.repasJour=Repas/Jour
Simulation.Indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
Simulation.Infos.Salaire=Salaire
Simulation.Salaire.salaireBase=Salaire de base
Simulation.Salaire.cotisationsSociales=Cotisations sociales
Simulation.Salaire.entretien=Indemnit\u00e9s d''entretien
Simulation.Salaire.repas=Indemnit\u00e9s de repas
Simulation.salaireNet=Salaire net
# formats
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# liste des simulations
Pam.Simulations.titre=Liste des simulations
Pam.Simulations.num=Num\u00e9ro
Pam.Simulations.nom=Nom
Pam.Simulations.prenom=Pr\u00e9nom
Pam.Simulations.heures=Heures
Pam.Simulations.jours=Jours
Pam.Simulations.salairebase=Salaire de base
Pam.Simulations.indemnites=Indemnites
Pam.Simulations.cotisationsociales=Cotisations
Pam.Simulations.salairenet=Salaire
Pam.SimulationsVides.titre=La liste des simulations est vide
# menu
Menu.FaireSimulation=Faire la simulation
Menu.EffacerSimulation=Effacer la simulation
Menu.VoirSimulations=Voir les simulations
Menu.RetourFormulaire=Retour au formulaire de navigation
Menu.EnregistrerSimulation=Enregistrer la simulation
Menu.TerminerSession=Terminer la session
# msg d'erreur
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# erreur de conversion
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
Il file [Form.properties] è il seguente:
# pour que les doubles soient au format local
double.format={0,number,#0.00##}
# msg d'erreur
joursTravaill\u00e9s.error=Tapez un nombre entier compris entre 1 et 31
heuresTravaill\u00e9es.error=Tapez un nombre r\u00e9el entre 0 et 300
19.6. Il foglio di stile
Le viste a riquadri utilizzano il seguente foglio di stile [styles.css]:
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold;;
padding-right: 5px;
padding-left: 5px;
padding-bottom: 5px;
padding-top: 5px;
}
.info{
background-color: #99cc00;;
padding-right: 5px;
padding-left: 5px;
padding-bottom: 5px;
padding-top: 5px;
}
.titreInfos{
background-color: #ffcc00
}
19.7. La vista iniziale
Per esplorare l'applicazione, la esamineremo in base alle varie azioni dell'utente. Per ogni azione, analizzeremo l'azione Struts che la esegue e la vista Tiles restituita in risposta.
In [struts.xml] abbiamo le seguenti azioni:
<!-- action par défaut -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
<result name="input" type="tiles">saisie</result>
<result name="simulation" type="tiles">simulation</result>
</action>
- righe 2-8: l'azione predefinita dell'applicazione è [Form!input].
La classe [Form] è la seguente:
package web.actions;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
// configuration initialized by Spring
private Config config;
// list of employees
private List<Employe> employes;
// error list
private List<Erreur> erreurs;
// payslip
private FeuilleSalaire feuilleSalaire;
// foreclosures
private String comboEmployesValue;
private Double heuresTravaillees;
private Integer joursTravailles;
// session
private Map<String, Object> session;
// menu
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
....
}
// wage calculation
public String calculSalaire() {
...
}
}
@Override
public void validate() {
...
}
@Override
public void setSession(Map<String, Object> map) {
session = map;
}
// getters and setters
...
}
- Riga 4: L'azione [Form] implementa l'interfaccia Preparable. Questa interfaccia ha un solo metodo, il metodo prepare alla riga 24. Questo metodo viene eseguito una volta prima di qualsiasi altro metodo dell'azione. Viene generalmente utilizzato per inizializzare il modello dell'azione.
Il modello dell'azione è definito nelle righe 6–21:
- riga 7: il campo config viene inizializzato da Spring come spiegato. Fornisce l'accesso ai dati a livello di applicazione:
- un riferimento al livello [business]
- un riferimento all'elenco dei dipendenti
- un riferimento all'eccezione che potrebbe essersi verificata durante l'istanziazione dell'oggetto [Config]
- riga 9: un elenco di dipendenti. Questo popolerà il menu a tendina dei dipendenti nel frammento [Saisie.jsp].
- riga 11: un elenco di errori. Questo popolerà il frammento [Error.jsp].
- Riga 21: l'elenco delle opzioni di menu per il frammento [Entete.jsp]
![]() |
In [1], i link nel menu visualizzato sono controllati dal campo menu dell'azione [Form].
Il metodo prepare viene eseguito prima del metodo input. È il seguente:
@Override
public void prepare() throws Exception {
// configuration error?
Exception initException = config.getInitException();
if (initException != null) {
erreurs = new ArrayList<Erreur>();
Throwable th = initException;
while (th != null) {
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
th = th.getCause();
}
} else {
employes = config.getEmployes();
}
}
- riga 4: recuperiamo l'eccezione dall'oggetto [Config] istanziato da Spring
- riga 5: se si è verificata un'eccezione durante l'istanziazione dell'oggetto [Config], inizializziamo l'elenco degli errori alla riga 11. La classe [Error] è la seguente:
package web.entities;
import java.io.Serializable;
public class Erreur implements Serializable{
public Erreur() {
}
// fields
private String classe;
private String message;
// manufacturer
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// getters and setters
...
}
La classe viene utilizzata per memorizzare lo stack delle eccezioni:
- riga 11: la classe dell'eccezione
- riga 12: il messaggio dell'eccezione
Torniamo al metodo prepare:
- riga 13: l'elenco dei dipendenti dall'oggetto [Config] viene memorizzato nel campo employees dell'azione.
Una volta eseguito il metodo prepare, verrà eseguito il metodo input. È il seguente:
@Override
public String input() {
if (erreurs == null) {
// menu
menu = new Menu(true, false, false, true, false, true);
return SUCCESS;
} else {
// menu
menu = new Menu(false, false, false, false, false, false);
return "exception";
}
}
Il metodo di input imposta semplicemente l'elenco delle opzioni di menu da visualizzare. La classe [Menu] è la seguente:
package web.entities;
import java.io.Serializable;
public class Menu implements Serializable {
// menu items
private boolean faireSimulation;
private boolean effacerSimulation;
private boolean enregistrerSimulation;
private boolean voirSimulations;
private boolean retourFormulaire;
private boolean terminerSession;
public Menu() {
}
public Menu(boolean faireSimulation, boolean effacerSimulation, boolean enregistrerSimulation, boolean voirSimulations, boolean retourFormulaire, boolean terminerSession) {
this.faireSimulation = faireSimulation;
this.effacerSimulation = effacerSimulation;
this.enregistrerSimulation = enregistrerSimulation;
this.voirSimulations = voirSimulations;
this.retourFormulaire = retourFormulaire;
this.terminerSession = terminerSession;
}
// getters and setters
...
}
- righe 8–13: nel menu sono presenti 6 possibili collegamenti
- righe 18-25: il costruttore della classe consente di specificare quali collegamenti devono essere visualizzati e quali no.
I link del menu vengono visualizzati in [Entete.jsp], un frammento JSP presente in tutte le viste di Tiles. Ogni azione avrà un campo menu per controllare la visualizzazione del menu in [Entete.jsp].
Torniamo al metodo di input:
@Override
public String input() {
if (erreurs == null) {
// menu
menu = new Menu(true, false, false, true, false, true);
return SUCCESS;
} else {
// menu
menu = new Menu(false, false, false, false, false, false);
return "exception";
}
}
- righe 3-6: se l'elenco degli errori è vuoto, verrà visualizzato il menu [Esegui simulazione, Visualizza simulazioni, Termina sessione] e verrà restituito il tasto di input.
- righe 9-10: se l'elenco degli errori non è vuoto, il menu sarà vuoto e verrà restituito il tasto di eccezione.
Torniamo alla configurazione dell'azione [Form] in [struts.xml]:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
<result name="input" type="tiles">saisie</result>
<result name="simulation" type="tiles">simulation</result>
</action>
- Riga 5: la chiave input visualizza la vista Tiles denominata input
- riga 4: la chiave exception visualizza la vista Tiles denominata exception
Cominciamo con la vista Tiles denominata "input". È costituita dai frammenti JSP [Entete.jsp] e [Saisie.jsp].
Il frammento [Entete.jsp] è il seguente:

Il suo codice è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
<s:if test="menu.faireSimulation">
|<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
</s:if>
<s:if test="menu.effacerSimulation">
|<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
</s:if>
<s:if test="menu.voirSimulations">
|<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
</s:if>
<s:if test="menu.retourFormulaire">
|<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
</s:if>
<s:if test="menu.enregistrerSimulation">
|<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
</s:if>
<s:if test="menu.terminerSession">
|<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
</s:if>
</td>
</tr>
</table>
- righe 8-25: visualizzazione dei sei collegamenti del menu [Esegui simulazione (righe 8-10), Cancella simulazione (righe 11-13), Visualizza simulazioni (righe 14–16), Torna al modulo (righe 17–19), Salva simulazione (righe 20–22), Termina sessione (righe 23–25).
- righe 8, 11, 14, 17, 20, 23: la visualizzazione dei collegamenti è controllata dal campo menu dell'azione corrente.
Si noti che il frammento [Entete.jsp] visualizza una tabella HTML (righe 4–28) ma non è una pagina HTML completa. Tenere presente che tutte le viste dell'applicazione sono incorporate nella seguente pagina master [MasterPage.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="styles.css" rel="stylesheet" type="text/css"/>
<title>
<tiles:insertAttribute name="titre" ignore="true" />
</title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<tiles:insertAttribute name="entete" />
<hr/>
<tiles:insertAttribute name="saisie" />
<tiles:insertAttribute name="simulation" />
<tiles:insertAttribute name="exception" />
<tiles:insertAttribute name="erreur" />
<tiles:insertAttribute name="simulations" />
</body>
</html>
Il frammento [Entete.jsp] viene inserito alla riga 17, all'interno di una normale pagina HTML.
Il frammento [Saisie.jsp] viene inserito alla riga 19. Questa è la vista risultante:

Il codice del frammento [Saisie.jsp] è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<script language="javascript" type="text/javascript">
function doSimulation(){
// mail the form
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- data entry -->
<s:form name="Saisie" id="Saisie">
<s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
<s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
<s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
<input type="hidden" name="action"/>
</s:form>
- Riga 17: Il modulo non ha un attributo action. Per impostazione predefinita, action='Form'.
- Riga 18: Visualizzazione del menu a tendina dei dipendenti. Il contenuto del menu a tendina (attributo list) è fornito dal campo employees dell'azione corrente. L'attributo value delle opzioni sarà il numero di previdenza sociale del dipendente (attributo listKey). L'etichetta visualizzata per ciascuna opzione sarà il nome e il cognome del dipendente (attributo listValue). Il numero di previdenza sociale del dipendente selezionato nella casella combinata verrà inviato al campo [Form].comboEmployeesValue (attributo name).
- Riga 19: campo di immissione per le ore lavorate. Il valore visualizzato (attributo value) è quello del campo heuresTravaillees nell'azione [Form] nel seguente formato (Form.properties):
double.format={0,number,#0.00##}
Il valore verrà inserito nel campo [Form].hoursWorked (attributo name).
- Riga 20: campo per l'inserimento dei giorni lavorati. Il valore visualizzato (attributo value) è quello del campo daysWorked nell'azione [Form].
Il valore verrà inviato al campo [Form].daysWorked (attributo name).
In definitiva, la vista Tiles visualizzata all'avvio in assenza di errori è la seguente:

Torniamo alla configurazione dell'azione [Form]:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
<result name="input" type="tiles">saisie</result>
<result name="simulation" type="tiles">simulation</result>
</action>
Abbiamo visto che l'azione [Form].input può anche attivare la chiave exception alla riga 4. In questo caso, viene visualizzata la vista Tiles denominata exception. Questa vista è composta dai frammenti [Header.jsp] e [Exception.jsp]. Abbiamo già presentato il frammento [Header.jsp]. Il frammento [Exception.jsp] è il seguente:

Questa è la pagina di avvio della versione 2 dell'applicazione quando il DBMS non è stato avviato. Il codice JSP per il frammento [Error.jsp] è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<h2><s:text name="Pam.Erreurs.titre"/></h2>
<table>
<tr class="titreInfos">
<th><s:text name="Pam.Erreurs.classe"/></th>
<th><s:text name="Pam.Erreurs.message"/></th>
</tr>
<s:iterator value="erreurs">
<tr>
<td class="libelle"><s:property value="classe"/></td>
<td class="info"><s:property value="message"/></td>
</tr>
</s:iterator>
</table>
- Righe 10–14: un iteratore sulla collezione List<Error> degli errori provenienti dall'azione [Form]. Ricordiamo che, in caso di errore, lì veniva memorizzato uno stack di eccezioni.
19.8. Esegui una simulazione
Una volta visualizzata la vista iniziale, è possibile calcolare uno stipendio tramite il link [Esegui una simulazione].
19.8.1. Convalida degli inserimenti
Si consideri la seguente sequenza:
![]() |
- in [1], un dato errato
- in [2], la risposta inviata.
Consideriamo la configurazione dell'azione [Form] in [struts.xml]:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
<result name="input" type="tiles">saisie</result>
<result name="simulation" type="tiles">simulation</result>
</action>
Sappiamo che in caso di errore di convalida, l'intercettatore di convalida restituisce la chiave input. Pertanto, viene restituita la vista di input Tiles. Il processo di convalida garantisce che i campi con errori siano accompagnati da messaggi di errore.
La convalida dell'azione [Form] è gestita dal seguente file [Form-validation.xml]:
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="heuresTravaillees" >
<field-validator type="required" short-circuit="true">
<message key="heuresTravaillées.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="heuresTravaillées.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="minInclusive">0</param>
<param name="maxInclusive">300</param>
<message key="heuresTravaillées.error"/>
</field-validator>
</field>
<field name="joursTravailles" >
<field-validator type="required" short-circuit="true">
<message key="joursTravaillés.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="joursTravaillés.error"/>
</field-validator>
<field-validator type="int" short-circuit="true">
<param name="min">0</param>
<param name="max">31</param>
<message key="joursTravaillés.error"/>
</field-validator>
</field>
</validators>
- Le righe 6–20 verificano che il campo hoursWorked sia un numero reale compreso nell'intervallo [0,300].
- Le righe 22–36 verificano che il campo `joursTravaillés` sia un numero intero compreso nell'intervallo [0,31].
Torniamo alla configurazione dell'azione [Form] in [struts.xml]:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="input" type="tiles">saisie</result>
</action>
Sappiamo che in caso di errore di convalida, l'intercettatore di convalida restituisce la chiave "input". Pertanto, viene restituita la vista Tiles "Input".
Ricordiamo che questa vista è costituita dai frammenti [Header.jsp] e [Input.jsp], dove [Header.jsp] contiene un titolo e un elenco di opzioni, mentre [Input.jsp] contiene il modulo di immissione. In caso di errori di immissione, il processo di convalida assicura che i campi errati siano accompagnati da messaggi di errore e visualizzi anche i loro valori errati. Il frammento [Entete.jsp] non svolge alcun ruolo nel processo di convalida. Diamo un'occhiata al suo codice:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
<s:if test="menu.faireSimulation">
|<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
</s:if>
<s:if test="menu.effacerSimulation">
|<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
</s:if>
<s:if test="menu.voirSimulations">
|<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
</s:if>
<s:if test="menu.retourFormulaire">
|<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
</s:if>
<s:if test="menu.enregistrerSimulation">
|<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
</s:if>
<s:if test="menu.terminerSession">
|<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
</s:if>
</td>
</tr>
</table>
I sei link sono configurati dal campo menu del template (righe 8, 11, 14, 17, 20, 23). Quando si verifica un errore, questo template non viene aggiornato dall'azione, con il risultato di una pagina senza menu. Per risolvere questo problema, la classe [Form] dispone del seguente metodo di validazione:
package web.actions;
import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
...
// menu
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
...
}
// wage calculation
public String calculSalaire() {
...
}
@Override
public void validate() {
// mistakes?
if (!getFieldErrors().isEmpty()) {
// menu
menu = new Menu(true, false, false, true, false, true);
}
}
// getters and setters
...
}
- riga 27: sappiamo che quando questo è presente, il metodo validate viene eseguito dal processo di convalida. Cogliamo l'occasione per aggiornare il menu alla riga 4, che fa parte del modello di frammento [Entete.jsp].
- righe 29–32: se ci sono stati errori di validazione, impostiamo il menu in modo da visualizzare nuovamente la vista di input Tiles. Se non ci sono stati errori di validazione, non facciamo nulla. Il metodo `calculSalaire` è quindi responsabile della creazione del modello di vista da visualizzare.
19.8.2. Calcolo dello stipendio
Torniamo al codice JSP nell'intestazione:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
<s:if test="menu.faireSimulation">
|<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
</s:if>
...
</td>
</tr>
</table>
- Riga 9: Quando l'utente fa clic sul link [Esegui simulazione], viene eseguita la funzione JavaScript doSimulation. Questa funzione è definita nel frammento [Saisie.jsp]:
<script language="javascript" type="text/javascript">
function doSimulation(){
// mail the form
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- data entry -->
<s:form name="Saisie" id="Saisie">
<s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
<s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
<s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
<input type="hidden" name="action"/>
</s:form>
- riga 14: un campo nascosto denominato action verrà inviato all'azione [Form]. Questo campo ci permette di specificare l'azione e il metodo da eseguire quando il modulo viene inviato. Come ricorderemo dai primi esempi, questi possono essere specificati in un parametro denominato action:Action!method. Il valore di questo parametro non ha importanza; deve solo essere presente.
- Righe 2–6: la funzione JavaScript che viene eseguita quando l'utente fa clic sul link [Esegui simulazione] nel frammento [Header.jsp].
- Riga 4: Modifichiamo l'attributo name del campo action nascosto. Ci assicuriamo che sia nel formato action:Action!method previsto da Struts.
- Riga 5: Il modulo denominato "Saisie de la ligne 5" viene inviato. Di conseguenza, viene inviata la seguente stringa di parametri:
SS1: numero INSEE del dipendente selezionato nella casella combinata
heuresTravaillees: numero di ore lavorate
giorniLavorati: numero di giorni lavorati
action:Form!calculateSalary: gli elementi sopra indicati verranno inviati all'azione [Form], dopodiché verrà eseguito il metodo calculateSalary di tale azione.
Il metodo [Form].calculateSalary è il seguente:
// wage calculation
public String calculSalaire() {
try {
// salary calculation
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// put the simulation in the session
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menu
menu = new Menu(true, true, true, true, false, true);
// finish
return "simulation";
} catch (Throwable th) {
...
}
}
- Riga 5: Il calcolo delle buste paga viene richiesto dal livello [business]
- riga 7: un oggetto Simulation viene aggiunto alla sessione dell'utente. Questo perché potrebbe essere necessario per una richiesta successiva. La classe [Simulation] è la seguente:
package web.entities;
import java.io.Serializable;
import metier.FeuilleSalaire;
public class Simulation implements Serializable{
public Simulation() {
}
// simulation fields
private Integer num;
private FeuilleSalaire feuilleSalaire;
private String heuresTravaillées;
private String joursTravaillés;
// manufacturer
public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire feuilleSalaire){
this.setNum(num);
this.setFeuilleSalaire(feuilleSalaire);
this.setHeuresTravaillées(heuresTravaillées);
this.setJoursTravaillés(joursTravaillés);
}
public double getIndemnites(){
return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
}
// getters and setters
...
}
- riga 12: il numero della simulazione. Viene incrementato ad ogni nuova simulazione salvata.
- riga 13: la busta paga del dipendente
- riga 14: il numero di ore lavorate
- riga 15: il numero di giorni lavorati
- riga 25: il metodo getIndemnites restituisce l'indennità totale del dipendente
Vedremo che la classe [Simulation] è il modello per il frammento [Simulations.jsp], che visualizza tutte le simulazioni eseguite.
Torniamo al metodo [Form].calculateSalary:
// wage calculation
public String calculSalaire() {
try {
// salary calculation
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// put the simulation in the session
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menu
menu = new Menu(true, true, true, true, false, true);
// finish
return "simulation";
} catch (Throwable th) {
...
}
- riga 9: aggiorna il menu
- riga 11: restituisci il tasto di navigazione "simulazione".
Torna alla configurazione dell'azione [Form]:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="simulation" type="tiles">simulation</result>
</action>
La riga 4 mostra che il tasto di navigazione "simulation" visualizza la vista Tiles denominata "simulation". Questa vista è costituita dai seguenti frammenti JSP: [Header, Input, Simulation].
La vista renderizzata è la seguente:
![]() |
- in [1], il frammento [Header.jsp]
- in [2], il frammento [Input.jsp]
- in [3], il frammento [Simulation.jsp]. Si noti che la busta paga visualizzata è quella fittizia generata dal livello [business].
I primi due frammenti sono già stati presentati. Il frammento [Simulation.jsp] è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<hr/>
<!-- information Employees -->
<span class="titreInfos">
<s:text name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
<!-- line 1 -->
<tr>
<th class="libelle">
<s:text name="Simulation.Employe.nom"/>
</th>
<th class="libelle">
<s:text name="Simulation.Employe.prenom"/>
</th>
<th class="libelle">
<s:text name="Simulation.Employe.adresse"/>
</th>
</tr>
<!-- line 2 -->
<tr>
<td class="info">
<s:property value="feuilleSalaire.employe.nom"/>
</td>
<td class="info">
<s:property value="feuilleSalaire.employe.prenom"/>
</td>
<td class="info">
<s:property value="feuilleSalaire.employe.adresse"/>
</td>
</table>
<table>
<!-- line 1 -->
<tr>
<th class="libelle"><s:text name="Simulation.Employe.ville"/></th>
<th class="libelle">
<s:text name="Simulation.Employe.codePostal"/>
</th>
<th class="libelle">
<s:text name="Simulation.Employe.indice"/>
</th>
</tr>
<!-- line 2 -->
<tr>
<td class="info">
<s:property value="feuilleSalaire.employe.ville"/>
</td>
<td class="info">
<s:property value="feuilleSalaire.employe.codePostal"/>
</td>
<td class="info">
<s:property value="feuilleSalaire.employe.indemnite.indice"/>
</td>
</table>
<!-- information Contributions -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.cotisations"/>
</span>
<br/><br/>
<table>
<!-- line 1 -->
<tr>
<th class="libelle">
<s:text name="Simulation.Cotisations.csgrds"/>
</th>
<th class="libelle">
<s:text name="Simulation.Cotisations.csgrds"/>
</th>
<th class="libelle">
<s:text name="Simulation.Cotisations.retraite"/>
</th>
<th class="libelle">
<s:text name="Simulation.Cotisations.secu"/>
</th>
</tr>
<!-- line 2 -->
<tr>
<td class="info">
<s:text name="Format.pourcent">
<s:param value="feuilleSalaire.cotisation.csgrds"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.pourcent">
<s:param value="feuilleSalaire.cotisation.csgd"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.pourcent">
<s:param value="feuilleSalaire.cotisation.retraite"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.pourcent">
<s:param value="feuilleSalaire.cotisation.secu"/>
</s:text>
</td>
</table>
<!-- information Indemnities -->
<br/>
<span class="titreInfos">
<s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
<!-- line 1 -->
<tr>
<th class="libelle">
<s:text name="Simulation.Indemnites.salaireHoraire"/>
</th>
<th class="libelle">
<s:text name="Simulation.Indemnites.entretienJour"/>
</th>
<th class="libelle">
<s:text name="Simulation.Indemnites.repasJour"/>
</th>
<th class="libelle">
<s:text name="Simulation.Indemnites.congésPayés"/>
</th>
</tr>
<!-- line 2 -->
<tr>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.employe.indemnite.baseHeure"/>
</s:text>
</td>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.employe.indemnite.entretienJour"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.employe.indemnite.repasJour"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.employe.indemnite.indemnitesCP"/>
</s:text>
</td>
</tr>
</table>
<!-- salary information -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
<!-- line 1 -->
<tr>
<th class="libelle">
<s:text name="Simulation.Salaire.salaireBase"/>
</th>
<th class="libelle">
<s:text name="Simulation.Salaire.cotisationsSociales"/>
</th>
<th class="libelle">
<s:text name="Simulation.Salaire.entretien"/>
</th>
<th class="libelle">
<s:text name="Simulation.Salaire.repas"/>
</th>
</tr>
<!-- line 2 -->
<tr>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.indemnitesEntretien"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.indemnitesRepas"/>
</s:text>
</td>
</tr>
</table>
<!-- Net salary-->
<br/>
<table>
<tr>
<td class="libelle">
<s:text name="Simulation.salaireNet"/>
<td></td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
</s:text>
</td>
</tr>
</table>
È lungo... ma funzionalmente semplice. Questo frammento di codice mostra le varie proprietà del campo [Form].paystub, che rappresenta la busta paga del dipendente.
Torniamo al metodo [Form].calculatePay:
// wage calculation
public String calculSalaire() {
try {
...
return "simulation";
} catch (Throwable th) {
erreurs = new ArrayList<Erreur>();
while (th != null) {
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
th = th.getCause();
}
// menu
menu = new Menu(false, false, false, false, true, true);
return "exception";
}
}
Il calcolo dello stipendio può andare storto. Questo potrebbe accadere, ad esempio, se la connessione al DBMS dovesse fallire. In questo caso, gestiamo l'eccezione che si verifica. Abbiamo già incontrato questo scenario quando abbiamo studiato il metodo [Form].input.
- Righe 7–11: creiamo un elenco di oggetti Error dallo stack delle eccezioni
- riga 13: impostiamo il menu
- riga 14: impostiamo la chiave dell'eccezione.
La chiave di eccezione visualizzerà la vista delle eccezioni di Tiles:
<!-- action Formulaire -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="exception" type="tiles">exception</result>
...
</action>
Questa vista Tiles è già stata presentata. Ha questo aspetto:

19.9. Salva una simulazione
Dopo aver eseguito una simulazione, l'utente potrebbe volerla salvare nella sessione.
![]() |
![]() |
- In [1], la simulazione viene salvata
- In [2], la risposta mostra l'elenco delle simulazioni già eseguite, a cui viene aggiunta la nuova simulazione
Il link [Salva simulazione] si trova nel frammento [Entete.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
...
<s:if test="menu.enregistrerSimulation">
|<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
</s:if>
...
</td>
</tr>
</table>
Possiamo vedere che cliccando sul link si attiva l'azione [SaveSimulation]. Questa azione è configurata nel file [struts.xml] come segue:
<!-- action EnregistrerSimulation -->
<action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
<result name="error" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
- Riga 1: L'azione [SaveSimulation] è associata alla classe [Save] e al suo metodo execute.
La classe [Save] è la seguente:
package web.actions;
...
public class Enregistrer extends ActionSupport implements SessionAware {
// session
private Map<String, Object> session;
// menu
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// action execution
public String execute() {
// retrieve the last simulation in the session
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
...
}
// getters and setters
...
}
- riga 4: poiché l'azione necessita di accedere alla sessione, implementa l'interfaccia SessionAware.
- riga 7: la sessione
- riga 9: il menu
Quando l'azione [Save] viene istanziata, viene chiamato il suo metodo execute. Ricordiamo che il suo compito è quello di inserire l'ultima simulazione nella sessione. Questa simulazione verrà aggiunta all'elenco delle simulazioni completate, anch'esso memorizzato nella sessione.
- riga 19: recuperiamo l'ultima simulazione inserita nella sessione.
- righe 20–22: se non viene trovata, è probabile che la sessione sia scaduta. Infatti, la sessione dura solo un certo periodo di tempo, che può essere impostato nel file [web.xml] che configura l'applicazione.
- Riga 21: restituiamo la chiave di errore.
Torniamo alla configurazione dell'azione [SaveSimulation]:
<!-- action EnregistrerSimulation -->
<action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
<result name="error" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
Possiamo vedere che la chiave "error" (riga 3) attiva la visualizzazione della vista Tiles denominata "error" . Questa vista è composta dai frammenti [Entete.jsp] e [Erreur.jsp] e si presenta così:

Il frammento [Erreur.jsp] è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<h2><s:text name="Pam.Erreur.libelle"/></h2>
<h4><s:text name="Erreur.sessionexpiree"/></h4>
Torniamo al metodo [Save].execute:
// action execution
public String execute() {
// retrieve the last simulation in the session
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
// retrieve the number of the last simulation
Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
if (numDerniereSimulation == null) {
numDerniereSimulation = 0;
}
// increment it
numDerniereSimulation++;
// we give it the new number in the session
session.put("numDerniereSimulation", numDerniereSimulation);
// retrieve the list of simulations
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
simulations = new ArrayList<Simulation>();
session.put("simulations", simulations);
}
// we add the current simulation
simulation.setNum(numDerniereSimulation);
simulations.add(simulation);
// the list of simulations is displayed
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
- righe 9–16: Le varie simulazioni sono numerate a partire da 1. L'ultimo numero assegnato viene memorizzato nella sessione sotto la chiave numDerniereSimulation. Il codice nelle righe 9–16 recupera questa chiave e incrementa il valore ad essa associato.
- righe 18–22: L'elenco delle simulazioni viene memorizzato nella sessione sotto la chiave simulations. Le righe 18–22 recuperano questo elenco se esiste o lo creano se non esiste.
- Righe 24–25: Una volta ottenuto l'elenco delle simulazioni, la simulazione corrente viene aggiunta ad esso (riga 25). In precedenza, alla simulazione corrente era stato assegnato un numero (riga 24).
- Riga 27: Viene impostato il menu da visualizzare
- Riga 28: Restituiamo la chiave di navigazione "simulations".
Torniamo alla configurazione dell'azione [EnregistrerSimulation] in [struts.xml]:
<!-- action EnregistrerSimulation -->
<action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
<result name="error" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
Riga 4: La chiave "simulations" attiva la visualizzazione della vista Tiles denominata "simulations" . Questa vista è costituita dai frammenti [Entete.jsp] e [Simulations.jsp]. La vista visualizzata è la seguente:
![]() |
- in [1], il frammento [Entete.jsp], che ormai conosciamo bene.
- in [2], il frammento [Simulations.jsp]
Il frammento [Simulations.jsp] è il seguente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<s:if test="#session['simulations'].size()!=0">
<h2><s:text name="Pam.Simulations.titre"/></h2>
<table>
<tr class="titreInfos">
<th><s:text name="Pam.Simulations.num"/></th>
<th><s:text name="Pam.Simulations.nom"/></th>
<th><s:text name="Pam.Simulations.prenom"/></th>
<th><s:text name="Pam.Simulations.heures"/></th>
<th><s:text name="Pam.Simulations.jours"/></th>
<th><s:text name="Pam.Simulations.salairebase"/></th>
<th><s:text name="Pam.Simulations.indemnites"/></th>
<th><s:text name="Pam.Simulations.cotisationsociales"/></th>
<th><s:text name="Pam.Simulations.salairenet"/></th>
</tr>
<s:iterator value="#session['simulations']">
<s:url action="SupprimerSimulation" var="url">
<s:param name="id" value="num"/>
</s:url>
<tr>
<td class="libelle"><s:property value="num"/></td>
<td class="info"><s:property value="feuilleSalaire.employe.nom"/></td>
<td class="info"><s:property value="feuilleSalaire.employe.prenom"/></td>
<td class="info"><s:property value="heuresTravaillées"/></td>
<td class="info"><s:property value="joursTravaillés"/></td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="indemnites"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
</s:text>
</td>
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
</s:text>
</td>
<td class="info"><a href="<s:property value="#url"/>">Retirer</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- righe 5-7: se non ci sono simulazioni nella sessione, viene visualizzata la seguente schermata:

- righe 13-21: visualizza le intestazioni delle colonne della tabella
![]()
- righe 23-55: iterazione sull'elenco delle simulazioni presenti nella sessione
- righe 24-26: creazione di un URL denominato url (attributo id). Il link HTML generato da questo URL è il seguente:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>
Possiamo notare che il link punta all'azione [DeleteSimulation] con il parametro id, che rappresenta il numero della simulazione da rimuovere dall'elenco.
- Righe 28–54: Per ogni iterazione attraverso l'elenco delle simulazioni, vengono visualizzate le proprietà della simulazione corrente.

19.10. Rimozione di una simulazione
L'utente potrebbe voler rimuovere una simulazione dall'elenco delle simulazioni:
![]() |
![]() |
- in [1], la simulazione n. 1 è stata eliminata
- in [2], la simulazione n. 1 è stata rimossa
Il link [Rimuovi] si trova nel frammento [Simulations.jsp] che abbiamo già esaminato:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<s:if test="#session['simulations'].size()!=0">
<h2><s:text name="Pam.Simulations.titre"/></h2>
<table>
<tr class="titreInfos">
...
</tr>
<s:iterator value="#session['simulations']">
<s:url action="SupprimerSimulation" var="url">
<s:param name="id" value="num"/>
</s:url>
<tr>
...
<td class="info">
<s:text name="Format.monnaie">
<s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
</s:text>
</td>
<td class="info"><a href="<s:property value="#url"/>">Retirer</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- Righe 16-18: generano il link HTML
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>
dove num è il numero della simulazione da rimuovere.
L'azione [DeleteSimulation] è definita come segue nel file [struts.xml]:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<!-- internationalization -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- spring integration -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- struts /Tiles actions -->
<package name="default" namespace="/" extends="tiles-default">
...
<!-- action RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
...
</package>
<!-- Add packages here -->
</struts>
- Riga 16: L'azione [SupprimerSimulation] è associata alla classe [Supprimer]. Poiché non è specificato alcun metodo, verrà eseguito il suo metodo execute. La classe [Supprimer] è la seguente:
package web.actions;
...
public class Supprimer extends ActionSupport implements SessionAware {
// session
private Map<String, Object> session;
// id of simulation to be deleted
private String id;
// menu
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// action execution
public String execute() {
// simulations are retrieved from the
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
// abnormal case - session must have expired
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// id test
int num = 0;
boolean erreur = false;
try {
num = Integer.parseInt(id);
erreur = num <= 0;
} catch (NumberFormatException ex) {
// abnormal
erreur = true;
}
// mistake?
if (erreur) {
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// search for the simulation to be deleted
for (int i = 0; i < simulations.size(); i++) {
if (num == simulations.get(i).getNum()) {
simulations.remove(i);
break;
}
}
// the list of simulations is displayed
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
// getters and setters
...
}
- Riga 4: L'azione [Delete] implementa l'interfaccia [SessionAware] per accedere alla sessione.
- riga 7: la sessione
- riga 9: il numero della simulazione da eliminare. Ricordiamo che istanziamo la classe [Delete] tramite l'URL HTML:
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>
dove num è il numero della simulazione da rimuovere. Questo numero verrà memorizzato nel campo id alla riga 9.
- Riga 11: il menu per la vista che verrà visualizzato in risposta alla richiesta
- Riga 19: il metodo `execute` che genererà la risposta alla richiesta.
- Riga 21: recuperiamo l'elenco delle simulazioni già eseguite nella sessione
- righe 22-26: se non si riesce a recuperare questo elenco dalla sessione, significa probabilmente che la sessione è scaduta. Abbiamo già riscontrato questo scenario in precedenza. Restituiamo la chiave di errore, che visualizza la vista di errore di Tiles:
<!-- action RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
...
</action>
La vista degli errori di Tiles è stata introdotta nella Sezione 19.9.
- righe 28–36: verifichiamo che la stringa id alla riga 9 sia effettivamente un numero intero maggiore di 0.
- righe 38-40: se così non fosse, viene restituita nuovamente la chiave di errore, che visualizzerà la vista Tiles di errore
- righe 43–48: la simulazione da rimuovere viene cercata nell'elenco delle simulazioni. Se trovata, viene eliminata.
- Riga 50: Aggiornamento del menu per la vista Tiles delle simulazioni.
- Riga 51: viene restituita la chiave `simulations`. Questo visualizzerà la vista delle simulazioni Tiles:
<!-- action RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
...
<result name="simulations" type="tiles">simulations</result>
</action>
La vista Tessere delle simulazioni è stata introdotta nella Sezione 19.9.
19.11. Torna al modulo
Dalla vista "Simulazioni a riquadri", l'utente può tornare al modulo:
![]() |
![]() |
- in [1], clicca sul link per tornare al modulo
- in [2], appare un modulo vuoto
Il link [Torna al modulo di simulazione] è definito nel frammento [Entete.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
...
<s:if test="menu.retourFormulaire">
|<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
</s:if>
...
</td>
</tr>
</table>
- Riga 10: Il link punta all'azione [ReturnForm]. Questa è definita come segue nel file [struts.xml]:
<!-- action RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
Possiamo vedere che questa azione non è associata ad alcuna classe. Si limita a reindirizzare il browser del client all'azione [/Form!input]. Ci troviamo quindi nella stessa situazione della visualizzazione della vista iniziale spiegata nella sezione 19.7. Torniamo quindi a questa vista iniziale [2].
19.12. Visualizza l'elenco delle simulazioni
Dalle schede "Simulazione" o dalle schermate "Input", l'utente può richiedere di visualizzare le simulazioni:
![]() |
![]() |
- in [1], fare clic sul collegamento [Visualizza simulazioni]
- in [2], viene visualizzato l'elenco delle simulazioni
Il link [Visualizza simulazioni] è definito nel frammento [Entete.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
...
<s:if test="menu.voirSimulations">
|<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
</s:if>
...
</td>
</tr>
</table>
- Riga 10: Il link [Visualizza simulazioni] richiama l'azione [ViewSimulations]. Questa è definita come segue nel file [struts.xml]:
<!-- action VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
L'azione [ViewSimulations] è associata alla classe [View] senza specificare un metodo. Pertanto, verrà eseguito il metodo [View].execute. La classe [View] è la seguente:
package web.actions;
import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;
public class Voir extends ActionSupport{
// menu
private Menu menu=new Menu(false,false,false,false,true,true);
// getters and setters
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
}
}
L'azione [View] fa una sola cosa: imposta il menu per la vista delle simulazioni Tiles (riga 8). Non esiste un metodo execute. Pertanto, verrà eseguito quello della classe padre [ActionSupport]. Sappiamo che non fa altro che restituire il flag di successo.
Torniamo all'azione in [struts.xml]:
<!-- action VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
Alla riga 3, vediamo che la chiave "success" attiva la visualizzazione della vista delle simulazioni Tiles. Questo è stato descritto a pagina 157.
19.13. Cancella la simulazione corrente
Dalla vista di simulazione Tiles, l'utente può richiedere di cancellare la simulazione corrente:
![]() |
![]() |
- in [1], la simulazione corrente viene cancellata
- in [2], il modulo di immissione viene cancellato
Il link [Cancella simulazione] è definito nel frammento [Entete.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
...
<s:if test="menu.effacerSimulation">
|<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
</s:if>
...
</td>
</tr>
</table>
Alla riga 10, vediamo che il link [Clear Simulation] attiva l'azione [Form!input]. Sappiamo che questa azione porta alla vista iniziale [2].
19.14. Termina la sessione corrente
Da qualsiasi vista Tiles, l'utente può richiedere di terminare la sessione:
![]() |
![]() |
![]() |
- In [1], partiamo dalla vista delle simulazioni e terminiamo la sessione
- In [2], il modulo di immissione è vuoto. Richiediamo di visualizzare le simulazioni.
- In [3], l'elenco delle simulazioni è ora vuoto.
Il link [Fine sessione] è definito nel frammento [Entete.jsp]:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<table>
<tr>
<td><h1><s:text name="Pam.titre"/></h1></td>
<td>
...
<s:if test="menu.terminerSession">
|<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
</s:if>
</td>
</tr>
</table>
Alla riga 10, vediamo che il link [End Session] attiva l'azione [EndSession]. Questa è definita come segue nel file [struts.xml]:
<action name="TerminerSession" class="web.actions.Terminer">
<result name="success" type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
- Riga 1: Possiamo vedere che la classe [Terminer] verrà istanziata e verrà chiamato il suo metodo execute.
- Righe 2–5: Dopo l'esecuzione del metodo [Terminer].execute, l'utente verrà reindirizzato alla vista di input iniziale. Questo spiega la Schermata 2.
La classe [Terminer] è la seguente:
package web.actions;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
public class Terminer extends ActionSupport implements SessionAware {
// session
private Map<String, Object> session;
@Override
public String execute() {
// quit current session
session.clear();
return SUCCESS;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
Il ruolo dell'azione [Finish] è quello di cancellare gli attributi della sessione corrente.
- Riga 7: L'azione [Terminate] implementa l'interfaccia [SessionAware] per accedere alla sessione.
- riga 10: il dizionario della sessione
- riga 13: viene chiamato il metodo execute
- riga 15: cancella il dizionario della sessione. Di conseguenza, l'elenco delle simulazioni attualmente presenti nella sessione scomparirà. Questo spiega la schermata n. 3.
- Riga 16: restituisce la chiave "success", che, come abbiamo visto, visualizzerà la vista Tiles con il valore [2].
19.15. Conclusione
Abbiamo commentato in modo esaustivo la versione 1 del nostro caso di studio, che funziona con un livello [aziendale] simulato:
![]() |
Non resta che "collegare" il vero livello aziendale al livello [web].






















