19. Studium przypadku – wersja 1
19.1. Symulowana warstwa [metier]
Wróćmy do architektury tworzonej przez nas aplikacji:
![]() |
Dysponujemy archiwum warstw [metier, dao, jpa] i przedstawiliśmy elementy tych warstw, które powinna znać warstwa [web]. Jesteśmy gotowi do napisania tej warstwy przy użyciu frameworka Struts.
Aby uprościć testowanie naszej aplikacji w trakcie tworzenia, utworzymy symulowaną warstwę biznesową, która będzie zgodna z interfejsem warstwy [metier]. Architektura będzie wyglądać następująco:
![]() |
Będziemy rozwijać warstwę [web] wraz z symulowaną warstwą [métier]. Testy będą łatwiejsze do przeprowadzenia, ponieważ w architekturze nie ma już bazy danych. Dzięki frameworkowi Spring i wykorzystaniu interfejsów późniejsze zastąpienie symulowanej warstwy [metier] rzeczywistą architekturą [metier, dao, jpa] nie będzie miało żadnego wpływu na kod warstwy [web / struts2]. Warstwa [web / struts2], którą teraz opracujemy, będzie mogła być wykorzystana w obecnej postaci.
Symulowana warstwa [metier], z której będziemy korzystać, wygląda następująco:
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 {
// lista pracowników
private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
private List<Employe> listEmployes;
// pobieranie listy płac
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// pobieranie pracownika o numerze SS
Employe e = hashEmployes.get(SS);
// generowanie fikcyjnego arkusza płacowego
return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
}
// lista pracowników
public List<Employe> findAllEmployes() {
if (listEmployes == null) {
// tworzy się listę dwóch pracowników
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)));
// słownik pracowników
for (Employe e : listEmployes) {
hashEmployes.put(e.getSS(), e);
}
}
// wyświetlanie listy pracowników
return listEmployes;
}
}
- wiersz 11: klasa [MetierSimule] implementuje interfejs [IMetier], który implementuje rzeczywista warstwa [metier].
- wiersz 14: słownik pracowników indeksowanych według ich numerów INSEE
- wiersz 15: lista pracowników
- wiersze 27–39: implementacja metody findAllEmployes z interfejsu [IMetier].
- wiersze 30–33: utworzenie listy dwóch pracowników
- wiersze 34–36: utworzenie słownika pracowników indeksowanego według numeru INSEE
- wiersze 18–24: implementacja metody calculerSalaire z interfejsu [IMetier]. W tym miejscu zwracamy fikcyjny arkusz płacowy.
19.2. Projekt NetBeans
Projekt NetBeans wygląda następująco:
![]() |
- w pliku [1]:
- [applicationContext.xml] to plik konfiguracyjny Spring
- [tiles.xml] to plik konfiguracyjny frameworka o nazwie Tiles.
- [web.xml] to plik konfiguracyjny aplikacji internetowej
- w pliku [2]: różne widoki aplikacji
- w pliku [3]:
- [messages.properties]: plik komunikatów
- [struts.xml]: plik konfiguracyjny Struts
![]() |
- w [4]: kody źródłowe aplikacji. Akcje Struts znajdują się w pakiecie [web.actions].
- w [5]: symulowana warstwa [metier]
- w [6]: wykorzystane archiwa. Znajdują się tam archiwa różnych użytych narzędzi: Spring, Tiles, Struts 2, wtyczka integracyjna Struts 2 / Spring, wtyczka integracyjna Struts 2 / Tiles.
- w formacie [7]: archiwum rzeczywistej warstwy [metier, dao, jpa]. Umożliwia nam to dostęp do encji JPA, interfejsu [IMetier] oraz klas [FeuilleSalaire] i [ElementsSalaire]. Wszystkie te elementy są bowiem wykorzystywane przez naszą klasę [MetierSimule].
19.3. Konfiguracja projektu
Projekt jest konfigurowany za pomocą różnych plików:
- [web.xml], który konfiguruje aplikację internetową
- [struts.xml], który konfiguruje framework Struts
- [applicationContext.xml], który konfiguruje framework Spring
- [tiles.xml], który konfiguruje framework Tiles
19.3.1. Konfiguracja aplikacji internetowej
Plik [web.xml] ma następującą treść:
<?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>
<!-- Kafelki -->
<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>
- wiersze 13–20: konfigurują filtr Struts 2 – już to widzieliśmy
- wiersze 22–24: konfigurują listener Springa – już omówione
- wiersze 9–11: konfigurują słuchacz Tiles. Klasa [org.apache.struts2.tiles.StrutsTilesListener] zostanie zainicjowana podczas uruchamiania aplikacji internetowej. Następnie wykorzysta ona swój plik konfiguracyjny. Jest on zdefiniowany w wierszach 5–8. Plikiem konfiguracyjnym Tiles jest zatem plik [WEB-INF/tiles.xml].
Ostatecznie podczas uruchamiania aplikacji Struts instancjonowane są trzy klasy:
- jedna dla filtra Struts 2. To właśnie ona odpowiada za literę „C” w nazwie MVC.
- kolejna dla słuchacza Springa. Spring wykorzysta plik [applicationContext.xml] do utworzenia instancji warstw [métier, dao, jpa] aplikacji. Spring utworzy również, podobnie jak w poprzednim przykładzie, instancję klasy [Config], która będzie zawierać dane o zasięgu Application. Wreszcie Spring wstrzykuje do każdej akcji Struts, która tego wymaga, odwołanie do tej jedynej instancji [Config].
- Kolejna instancja jest przeznaczona dla modułu nasłuchującego Tiles. Ten framework zajmie się zarządzaniem widokami. Wrócimy do tego wkrótce.
19.3.2. Konfiguracja frameworka Struts
Framework Struts jest konfigurowany za pomocą następującego pliku [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>
<!-- internacjonalizacja -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- integracja ze Springiem -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- akcje Struts / Tiles -->
<package name="default" namespace="/" extends="tiles-default">
<!-- akcja domyślna -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- akcja formularza -->
<action name="Formulaire" class="web.actions.Formulaire" method="input">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
</action>
<!-- akcja 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>
<!-- akcja 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>
<!-- działanie RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- akcja VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
<!-- akcja 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>
<!-- akcja 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>
Będziemy omawiać poszczególne akcje Struts w miarę ich analizowania. Na razie warto zwrócić uwagę na następujące kwestie:
- wiersz 8: definiuje plik komunikatów
- wiersz 10: określa sposób wstrzykiwania beanów Spring do akcji Struts. Wstrzykiwanie odbywa się na podstawie nazwy beana. Pole akcji Struts, które ma zostać zainicjowane przez Spring, musi nosić nazwę beana, który ma zostać wstrzyknięty.
- wiersz 25: definiuje widok, który ma zostać wyświetlony dla klucza nawigacyjnego success akcji [Formulaire]. Widać, że wynik <result> posiada atrybut type='tiles', którego nie znamy. Znaliśmy typ redirect, który umożliwia przekierowanie klienta do widoku. W tym przypadku widok typu tiles jest obsługiwany przez framework Tiles. Typ tiles jest zdefiniowany w pliku [struts-plugin.xml] w archiwum [struts2-tiles-plugin-2.2.3.1.jar]:
- wiersze 3–5: definicja typu wyniku tiles.
- wiersz 2: ten typ jest zdefiniowany w pakiecie [tiles-default], który rozszerza pakiet [struts-default].
- wiersz 14: definiuje pakiet [default], w którym będą znajdować się wszystkie akcje aplikacji. Aby skorzystać z definicji typu widoku tiles, pakiet rozszerza pakiet [tiles-default].
19.3.3. Konfiguracja frameworka Spring
Framework Spring jest konfigurowany za pomocą następującego pliku [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">
<!-- warstwy aplikacyjne -->
<!-- internet -->
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- warstwa biznesowa -->
<bean id="metier" class="metier.MetierSimule"/>
</beans>
- wiersz 13: symulowana warstwa [metier] instancjonowana przez klasę [metier.MetierSimule]
- wiersze 9–11: definiują bean o nazwie config. Podobnie jak w omówionym wcześniej przykładzie, bean ten posłuży do enkapsulacji informacji o zakresie Application. Klasą powiązaną z tym beanem jest następująca klasa [Config]:
package web;
import java.util.List;
import jpa.Employe;
import metier.IMetier;
public class Config {
// warstwa biznesowa inicjowana przez Spring
private IMetier metier;
// lista pracowników
private List<Employe> employes;
// błędy
private Exception initException;
// konstruktor
public Config() {
}
// metoda Springa do inicjalizacji obiektu
public void init() {
// żądanie listy pracowników
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
// metody getter i setter
...
}
Wróćmy do konfiguracji bean’a config:
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- warstwa biznesowa -->
<bean id="metier" class="metier.Metier">
...
</bean>
W wierszu 2 widać, że bean metier z wiersza 5 jest wstrzykiwany (ref) do pola o nazwie metier (name) obiektu [Config]. Bean metier jest odwołaniem w warstwie [metier]:
![]() |
Aby komunikować się z warstwą [metier], wszystkie akcje Struts w warstwie [web] będą wymagały odwołania do tej warstwy. Można powiedzieć, że odwołanie do warstwy [metier] jest danymi o zasięgu Application. Będą go potrzebować wszystkie żądania wszystkich użytkowników. Dlatego umieszczamy to odwołanie w obiekcie [Config]. Ponadto w wierszu 1 konfiguracja bean’a config zawiera atrybut init-method. Atrybut ten określa metodę bean’a, która ma zostać wykonana po utworzeniu instancji bean’a. W tym przypadku wskazano, że po utworzeniu instancji klasy [web.Config] należy wykonać jej metodę init. Metoda ta ma następującą postać:
// warstwa biznesowa zainicjowana przez Spring
private IMetier metier;
// lista pracowników
private List<Employe> employes;
// błędy
private Exception initException;
// konstruktor
public Config() {
}
// metoda Springa do inicjalizacji obiektu
public void init() {
// żądanie listy pracowników
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
Gdy wykonywana jest metoda init, pole metier klasy zostało zainicjowane przez Spring. Metoda init ma zatem dostęp do warstwy biznesowej interfejsu [IMetier] (wiersz 2):
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// pobieranie listy płac
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// lista pracowników
List<Employe> findAllEmployes();
}
- wiersz 8: metoda umożliwia obliczenie wynagrodzenia pracownika
- wiersz 10: metoda pozwala uzyskać listę pracowników
Widać, że metoda init żąda listy pracowników od warstwy [métier]. Lista ta jest zapisywana w polu w wierszu 4. Jeśli wystąpi wyjątek, jest on zapisywany w polu w wierszu 6.
Podsumowując, pojedynczy obiekt [Config] zawiera:
- odwołanie do warstwy [métier]
- listę pracowników
19.4. Generowanie widoków typu Tiles
Jak widać w pliku konfiguracyjnym Struts, widoki będą generowane przez framework Tiles. Omówimy tylko to, co jest absolutnie niezbędne do napisania naszej aplikacji.
Tiles umożliwia generowanie widoków na podstawie strony głównej. Strona ta, nazwana tutaj [MasterPage.JSP], będzie złożona z następujących fragmentów JSP:
Te fragmenty o nazwach JSP są zdefiniowane w projekcie NetBeans:
![]() |
Framework Tiles pozwala nam określić, które fragmenty zostaną wstawione do strony szablonowej.
Strona główna [MasterPage.JSP] wygląda następująco:
<%@ 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>
Strona główna jest kontenerem fragmentów JSP. W tym przypadku składa się ona z sześciu fragmentów, znajdujących się w wierszach od 17 do 23. Podczas generowania na stronie głównej może znajdować się od 0 do 6 połączonych fragmentów. Proces ten jest regulowany przez plik [WEB-INF/tiles.xml], który definiuje widoki 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>
<!-- strona główna -->
<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>
<!-- widok wprowadzania danych -->
<definition name="saisie" extends="masterPage">
<put-attribute name="saisie" value="/Saisie.JSP"/>
</definition>
<!-- widok symulacji -->
<definition name="simulation" extends="saisie">
<put-attribute name="simulation" value="/Simulation.JSP"/>
</definition>
<!-- widok symulacji -->
<definition name="simulations" extends="masterPage">
<put-attribute name="simulations" value="/Simulations.JSP"/>
</definition>
<!-- widok wyjątku -->
<definition name="exception" extends="masterPage">
<put-attribute name="exception" value="/Exception.JSP"/>
</definition>
<!-- widok błędu -->
<definition name="erreur" extends="masterPage">
<put-attribute name="erreur" value="/Erreur.JSP"/>
</definition>
</tiles-definitions>
- Powyższy plik definiuje sześć widoków Tiles o nazwach: masterPage (wiersz 9), wprowadzanie danych (wiersz 20), symulacja (wiersz 25), symulacje (wiersz 30), wyjątek (wiersz 35), błąd (wiersz 40).
- wiersze 9–17: definiują widok o nazwie masterPage (name), powiązany ze stroną główną [MasterPage.JSP] (template). Widzieliśmy, że strona JSP definiowała sześć podwidoków. Widok Tiles powiązany ze stroną główną musi wskazywać fragment JSP powiązany z każdym z sześciu podwidoków. Widać, że niektóre podwidoki otrzymują jako wartość (value) pusty ciąg znaków. Te podwidoki nie zostaną uwzględnione na stronie głównej [MasterPage.JSP]. Widok Tiles o nazwie masterPage składa się zatem wyłącznie z jednego podfragmentu [Entete.JSP].
- wiersze 20–22: definiują widok o nazwie saisie (name), który rozszerza (extends) widok o nazwie masterPage, omówiony wcześniej. Oznacza to, że przejmuje ona wszystkie definicje widoku masterPage. Jej definicja jest równoważna następującej:
<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>
Widać, że jest ona powiązana ze stroną JSP [MasterPage.JSP] i w związku z tym musi definiować sześć podwidoków tej strony. Widać, że definicja z wiersza 9 unieważnia definicję z wiersza 4. Widok Tiles o nazwie saisie składa się zatem z fragmentów JSP oraz [Entete.JSP, Saisie.JSP]
Jeśli będziemy kontynuować to rozumowanie, otrzymamy następującą tabelę:
widok Tiles | strony JSP |
19.5. Pliki komunikatów
Aplikacja została zinternacjonalizowana. Komunikaty znajdują się w plikach [messages.properties] i [Formulaire.properties].
Plik [messages.properties] ma następującą treść:
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
# formaty
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# lista symulacji
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
# komunikat o błędzie
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# błąd konwersji
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
Plik [Formulaire.properties] ma następującą treść:
# aby duplikaty miały format lokalny
double.format={0,number,#0.00##}
# komunikat o błędzie
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. Arkusz stylów
Widoki typu „Tiles” wykorzystują następujący arkusz stylów [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. Widok początkowy
Aby przeanalizować aplikację, przedstawimy ją na podstawie różnych działań użytkownika. Za każdym razem przyjrzymy się akcji Struts, która wykonuje dane działanie, oraz widokowi Tiles, który jest wysyłany w odpowiedzi.
W akcji [struts.xml] mamy następujące działania:
<!-- działanie domyślne -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
<!-- akcja formularza -->
<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>
- wiersze 2–8: domyślną akcją aplikacji jest [Formulaire!input].
Klasa [Formulaire] wygląda następująco:
package web.actions;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
// konfiguracja zainicjowana przez Spring
private Config config;
// lista pracowników
private List<Employe> employes;
// lista błędów
private List<Erreur> erreurs;
// arkusz płacowy
private FeuilleSalaire feuilleSalaire;
// wpisy
private String comboEmployesValue;
private Double heuresTravaillees;
private Integer joursTravailles;
// sesja
private Map<String, Object> session;
// menu
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
....
}
// obliczanie wynagrodzenia
public String calculSalaire() {
...
}
}
@Override
public void validate() {
...
}
@Override
public void setSession(Map<String, Object> map) {
session = map;
}
// metody pobierające i ustawiające
...
}
- wiersz 4: akcja [Formulaire] implementuje interfejs Preparable. Interfejs ten posiada tylko jedną metodę – metodę prepare z wiersza 24. Metoda ta jest wykonywana jednokrotnie przed każdą metodą akcji. Służy ona zazwyczaj do zainicjowania modelu akcji.
Model akcji obejmuje wiersze 6–21:
- wiersz 7: pole config jest inicjowane przez Spring zgodnie z powyższym opisem. Zapewnia ono dostęp do danych o zasięgu aplikacji:
- odwołanie na warstwie [métier]
- odwołanie do listy pracowników
- odwołanie do wyjątku, który mógł ewentualnie wystąpić podczas instancjonowania obiektu [Config]
- wiersz 9: lista pracowników. Zostanie ona wykorzystana do wypełnienia listy rozwijanej pracowników we fragmencie [Saisie.JSP].
- wiersz 11: lista błędów. Zostanie ona wykorzystana w fragmencie [Erreur.JSP].
- wiersz 21: lista opcji menu fragmentu [Entete.JSP]
![]() |
W [1] linki wyświetlanego menu są kontrolowane przez pole menu akcji [Formulaire].
Metoda prepare jest wykonywana przed metodą input. Oto jej treść:
@Override
public void prepare() throws Exception {
// błąd konfiguracji?
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();
}
}
- wiersz 4: pobieramy wyjątek z obiektu [Config], którego instancję utworzył Spring
- wiersz 5: jeśli wystąpił wyjątek podczas instancjonowania obiektu [Config], wówczas inicjowana jest lista błędów z wiersza 11. Klasa [Erreur] wygląda następująco:
package web.entities;
import java.io.Serializable;
public class Erreur implements Serializable{
public Erreur() {
}
// pola
private String classe;
private String message;
// konstruktor
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// metody pobierające i ustawiające
...
}
Klasa służy do przechowywania stosu wyjątków:
- wiersz 11: klasa wyjątku
- wiersz 12: komunikat wyjątku
Wróćmy do metody prepare:
- wiersz 13: lista pracowników obiektu [Config] jest zapisana w polu employes akcji.
Po wykonaniu metody prepare zostanie z kolei wykonana metoda input. Oto jej treść:
@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";
}
}
Metoda input służy jedynie do ustawienia listy opcji menu, które mają zostać wyświetlone. Klasa [Menu] wygląda następująco:
package web.entities;
import java.io.Serializable;
public class Menu implements Serializable {
// elementy menu
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;
}
// metody pobierające i ustawiające
...
}
- wiersze 8–13: w menu może znajdować się 6 linków
- wiersze 18–25: konstruktor klasy pozwala określić, które linki mają być wyświetlane, a które nie.
Linki menu są wyświetlane w klasie [Entete.JSP], a fragment JSP występuje we wszystkich widokach klasy Tiles. Każda akcja będzie miała pole menu służące do sterowania wyświetlaniem menu z pliku [Entete.JSP].
Wróćmy do metody 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";
}
}
- wiersze 3–6: jeśli lista błędów jest pusta, wyświetlone zostanie menu [Faire la simulation, Voir les simulations, Terminer la session] i zwrócony zostanie klucz input.
- wiersze 9–10: jeśli lista błędów nie jest pusta, menu będzie puste, a zwracany będzie klucz exception.
Wróćmy do konfiguracji akcji [Formulaire] w [struts.xml]:
<!-- akcja Formularz -->
<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>
- wiersz 5: klucz input wyświetla widok kafelkowy o nazwie saisie
- wiersz 4: klucz exception wyświetla widok kafelkowy o nazwie exception
Zacznijmy od widoku kafelkowego o nazwie saisie. Składa się on z fragmentów JSP, [Entete.JSP] i [Saisie.JSP].
Fragment o nazwie [Entete.JSP] wygląda następująco:
![]() |
Jego kod jest następujący:
<%@ 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>
- wiersze 8–25: wyświetlanie sześciu linków menu [Przeprowadź symulację (wiersze 8–10), Usuń symulację (wiersze 11–13), Zobacz symulacje (wiersze 14–16), Powrót do formularza (wiersze 17–19), Zapisz symulację (wiersze 20–22), Zakończ sesję (wiersze 23–25).
- wiersze 8, 11, 14, 17, 20, 23: wyświetlanie linków jest kontrolowane przez pole menu bieżącej akcji.
Należy zauważyć, że fragment [Entete.JSP] wyświetla tabelę HTML (wiersze 4–28), ale nie jest pełną stroną HTML. Nie należy tutaj zapominać, że wszystkie widoki aplikacji są umieszczane w następującej stronie głównej [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>
Fragment [Entete.JSP] jest wstawiany w wierszu 17, wewnątrz zwykłej strony HTML.
Fragment [Saisie.JSP] jest wstawiany w wierszu 19. Jest to następujący widok:
![]() |
Kod fragmentu [Saisie.JSP] jest następujący:
<%@ 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(){
// wysyłanie formularza
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- wprowadzanie danych -->
<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>
- wiersz 17: formularz nie posiada atrybutu action. Domyślnie mamy action='Formulaire'.
- wiersz 18: wyświetlanie listy rozwijanej pracowników. Zawartość listy rozwijanej (atrybut list) jest dostarczana przez pole employes bieżącej akcji. Atrybut value opcji będzie odpowiadał numerowi SS pracowników (atrybut listKey). Nazwa wyświetlana dla każdej opcji będzie stanowić imię i nazwisko pracownika (atrybut listValue). Numer SS pracownika wybranego z listy rozwijanej zostanie zapisany w polu [Formulaire].comboEmployesvalue (atrybut name).
- wiersz 19: pole wprowadzania przepracowanych godzin. Wyświetlana wartość (atrybut value) pochodzi z pola heuresTravaillees akcji [Formulaire] w następującym formacie (Formulaire.properties):
double.format={0,number,#0.00##}
Wartość ta zostanie zapisana w polu [Formulaire].heuresTravaillees (atrybut name).
- wiersz 20: pole wprowadzania dni przepracowanych. Wyświetlana wartość (atrybut value) pochodzi z pola joursTravailles akcji [Formulaire].
Wartość ta zostanie zapisana w polu [Formulaire].joursTravailles (atrybut name).
Ostatecznie widok Tiles saisie wyświetlany przy uruchomieniu, gdy nie ma błędów, wygląda następująco:
![]() |
Wróćmy do konfiguracji akcji [Formulaire]:
<!-- działanie formularza -->
<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>
Widzieliśmy, że akcja [Formulaire].input może również zwrócić klucz exception z wiersza 4. W tym przypadku wyświetlany jest widok Tiles o nazwie exception. Składa się on z fragmentów [Entete.JSP] i [Exception.JSP]. Fragment [Entete.JSP] został już omówiony. Fragment [Exception.JSP] wygląda następująco:
![]() |
Jest to strona startowa wersji 2 aplikacji, gdy system Sgbd nie został uruchomiony. Kod JSP z fragmentu [Erreur.JSP] wygląda następująco:
<%@ 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>
- wiersze 10–14: iterator nad kolekcją List<Erreur> błędów akcji [Formulaire]. Przypomnijmy, że w przypadku błędu zapisano w niej stos wyjątków.
19.8. Przeprowadź symulację
Po wyświetleniu ekranu startowego można obliczyć wynagrodzenie za pomocą linku [Faire une simulation].
19.8.1. Weryfikacja danych
Rozważmy następującą sekwencję:
![]() |
- w [1] – błędne dane
- w [2] – wysłana odpowiedź.
Rozważmy konfigurację akcji [Formulaire] w [struts.xml]:
<!-- działanie formularza -->
<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>
Wiadomo, że w przypadku błędu walidacji interceptor walidacji zwraca klucz input. Zwracany jest zatem widok Tiles saisie. Proces walidacji powoduje, że błędne pola są opatrzone komunikatami o błędach.
Walidację akcji [Formulaire] zapewnia następujący plik [Formulaire-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>
- wiersze 6–20 sprawdzają, czy pole heuresTravaillees jest liczbą rzeczywistą z przedziału [0,300].
- Wiersze 22–36 sprawdzają, czy pole joursTravailles jest liczbą całkowitą z przedziału [0,31].
Wróćmy do konfiguracji akcji [Formulaire] w [struts.xml]:
<!-- działanie związane z formularzem -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="input" type="tiles">saisie</result>
</action>
Wiadomo, że w przypadku błędu walidacji interceptor walidacji zwraca klucz input. Zwracany jest zatem widok Tiles o numerze saisie.
Przypomnijmy, że widok ten składa się z fragmentów [Entete.JSP] i [Saisie.JSP], przy czym [Entete.JSP] zawiera tytuł i listę opcji, a [Saisie.JSP] – formularz do wprowadzania danych. W przypadku błędów wprowadzenia danych proces walidacji powoduje, że przy błędnych polach wyświetlane są komunikaty o błędach oraz ich nieprawidłowe wartości. Fragment [Entete.JSP] nie bierze udziału w procesie walidacji. Przyjrzyjmy się jego kodowi:
<%@ 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>
Sześć linków jest konfigurowanych przez pole menu w szablonie (wiersze 8, 11, 14, 17, 20, 23). W przypadku wystąpienia błędu szablon ten nie jest aktualizowany przez akcję, w wyniku czego wyświetlana jest strona bez menu. Aby rozwiązać ten problem, klasa [Formulaire] posiada następującą metodę validate:
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() {
...
}
// obliczanie wynagrodzenia
public String calculSalaire() {
...
}
@Override
public void validate() {
// czy są jakieś błędy?
if (!getFieldErrors().isEmpty()) {
// menu
menu = new Menu(true, false, false, true, false, true);
}
}
// metody getter i setter
...
}
- wiersz 27: wiadomo, że gdy ta metoda jest obecna, metoda validate jest wykonywana w ramach procesu walidacji. Wykorzystujemy to, aby zaktualizować menu w wierszu 4, które jest częścią szablonu fragmentu [Entete.JSP].
- wiersze 29–32: jeśli wystąpiły błędy walidacji, ustawia się menu tak, aby ponownie wyświetlić widok Tiles saisie. Jeśli nie wystąpiły żadne błędy walidacji, nie wykonuje się żadnych działań. Za utworzenie szablonu widoku, który ma zostać wyświetlony, odpowiada metoda calculSalaire.
19.8.2. Obliczanie wynagrodzenia
Wróćmy do kodu JSP z nagłówka:
<%@ 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>
- wiersz 9: gdy użytkownik kliknie link [Faire la simulation], uruchamiana jest funkcja JavaScript doSimulation. Jest ona zdefiniowana we fragmencie [Saisie.JSP]:
<script language="javascript" type="text/javascript">
function doSimulation(){
// wysyłamy formularz
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- wprowadzanie danych -->
<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>
- wiersz 14: ukryte pole o nazwie action zostanie przesłane do akcji [Formulaire]. Pole to pozwoli nam określić akcję i metodę, które mają zostać wykonane w polu POST formularza. Być może pamiętamy z pierwszych przykładów, że można je określić w parametrze o nazwie action:Action!metoda. Wartość tego parametru nie ma znaczenia. Wystarczy, że jest obecny.
- wiersze 2–6: funkcja JavaScript, która jest wykonywana, gdy użytkownik kliknie link [Faire la simulation] w fragmencie [Entete.JSP].
- wiersz 4: zmieniamy atrybut name ukrytego pola action. Sprawiamy, by miał on postać action:Action!metoda, oczekiwaną przez Struts.
- wiersz 5: formularz o nazwie Saisie z wiersza 5 jest wysyłany. W rezultacie wysyłany jest następujący ciąg parametrów:
SS1 : n° INSEE de l'employé sélectionné dans le combo
heuresTravaillees : nombre d'heures travaillées
joursTravailles : nombre de jours travaillés
action : Formulaire!calculSalaire : les éléments ci-dessus seront postés à l'action [Formulaire] puis la méthode calculSalaire de cette action sera exécutée.
Metoda [Formulaire].calculSalaire wygląda następująco:
// obliczanie wynagrodzenia
public String calculSalaire() {
try {
// obliczanie wynagrodzenia
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// umieszczanie symulacji w sesji
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menu
menu = new Menu(true, true, true, true, false, true);
// koniec
return "simulation";
} catch (Throwable th) {
...
}
}
- wiersz 5: żądanie obliczenia listy płac jest kierowane do warstwy [métier]
- wiersz 7: do sesji użytkownika dodawany jest obiekt typu Simulation. Może on bowiem okazać się potrzebny podczas późniejszego zapytania. Klasa [Simulation] wygląda następująco:
package web.entities;
import java.io.Serializable;
import metier.FeuilleSalaire;
public class Simulation implements Serializable{
public Simulation() {
}
// pola symulacji
private Integer num;
private FeuilleSalaire feuilleSalaire;
private String heuresTravaillées;
private String joursTravaillés;
// konstruktor
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();
}
// metody getter i setter
...
}
- wiersz 12: numer symulacji. Jest on zwiększany przy każdej nowej zarejestrowanej symulacji.
- wiersz 13: karta płacowa pracownika
- wiersz 14: liczba przepracowanych godzin
- wiersz 15: liczba przepracowanych dni
- wiersz 25: metoda getIndemnites zwraca sumę wynagrodzeń pracownika
Zobaczymy, że klasa [Simulation] jest szablonem fragmentu [Simulations.JSP], który zawiera wszystkie przeprowadzone symulacje.
Powrót do metody [Formulaire].calculSalaire:
// obliczanie wynagrodzenia
public String calculSalaire() {
try {
// obliczanie wynagrodzenia
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// umieszczamy symulację w sesji
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menu
menu = new Menu(true, true, true, true, false, true);
// koniec
return "simulation";
} catch (Throwable th) {
...
}
- wiersz 9: aktualizacja menu
- wiersz 11: zwrot klucza nawigacyjnego simulation.
Powrót do konfiguracji akcji [Formulaire]:
<!-- działanie formularza -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="simulation" type="tiles">simulation</result>
</action>
Wiersz 4 pokazuje, że klucz nawigacyjny simulation powoduje wyświetlenie widoku kafelkowego o nazwie simulation. Składa się on z następujących fragmentów JSP: [Entete, Saisie, Simulation].
Wyświetlany widok wygląda następująco:
![]() |
- w [1] fragment [Entete.JSP]
- w [2], fragment [Saisie.JSP]
- w [3], fragment [Simulation.JSP]. Przypominamy, że przedstawiony arkusz płacowy jest fikcyjnym arkuszem płacowym wygenerowanym przez warstwę [metier].
Dwa pierwsze fragmenty zostały już przedstawione. Fragment [Simulation.JSP] ma następującą postać:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<hr/>
<!-- informacje o pracowniku -->
<span class="titreInfos">
<s:text name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- informacje o składkach -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.cotisations"/>
</span>
<br/><br/>
<table>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- informacje o świadczeniach -->
<br/>
<span class="titreInfos">
<s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- informacje o wynagrodzeniu -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
<!-- wiersz 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>
<!-- wiersz 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>
<!-- Wynagrodzenie netto-->
<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>
To długi fragment... ale pod względem funkcjonalnym jest prosty. Fragment ten wyświetla różne właściwości pola [Formulaire].feuilleSalaire, które reprezentuje kartę wynagrodzenia pracownika.
Powrót do metody [Formulaire].calculSalaire:
// obliczenie wynagrodzenia
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";
}
}
Obliczanie wynagrodzenia może przebiegać nieprawidłowo. Dzieje się tak zwłaszcza w przypadku zerwania połączenia z systemem Sgbd. W takiej sytuacji należy obsłużyć wystąpiony wyjątek. Z taką sytuacją mieliśmy już do czynienia podczas analizy metody [Formulaire].input.
- wiersze 7–11: tworzymy listę obiektów Erreur na podstawie stosu wyjątków
- wiersz 13: ustalamy menu
- wiersz 14: zwracamy klucz exception.
Klucz exception spowoduje wyświetlenie widoku Tiles exception:
<!-- działanie Formularz -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="exception" type="tiles">exception</result>
...
</action>
Ten widok Tiles został już przedstawiony. Wygląda on następująco:
![]() |
19.9. Zapisywanie symulacji
Po przeprowadzeniu symulacji użytkownik może chcieć ją zapisać w sesji.
![]() |
![]() |
- W pliku [1] zapisano symulację
- w pliku [2] zapisano odpowiedź zawierającą listę już przeprowadzonych symulacji, do której dodano nową symulację
Link [Enregistrer la simulation] znajduje się we fragmencie [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>
Widać, że kliknięcie linku powoduje wykonanie akcji [EnregistrerSimulation]. Jest ona skonfigurowana w pliku [struts.xml] w następujący sposób:
<!-- akcja 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>
- wiersz 1: akcja [EnregistrerSimulation] jest powiązana z klasą [Enregistrer] i jej metodą execute.
Klasa [Enregistrer] ma następujący wygląd:
package web.actions;
...
public class Enregistrer extends ActionSupport implements SessionAware {
// sesja
private Map<String, Object> session;
// menu
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// wykonanie akcji
public String execute() {
// pobieranie ostatniej symulacji z sesji
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
...
}
// metody pobierające i ustawiające
...
}
- wiersz 4: ponieważ akcja musi mieć dostęp do sesji, implementuje ona interfejs SessionAware.
- wiersz 7: sesja
- wiersz 9: menu
Po utworzeniu instancji akcji [Enregistrer] wykonywana jest jej metoda execute. Przypomnijmy, że jej zadaniem jest umieszczenie w sesji ostatniej symulacji. Zostanie ona dodana do listy już wykonanych symulacji, która również jest przechowywana w sesji.
- wiersz 19: pobieramy z sesji ostatnią symulację, która została tam umieszczona.
- wiersze 20–22: jeśli nie można jej znaleźć, oznacza to prawdopodobnie, że sesja wygasła. Sesja ta trwa bowiem tylko przez określony czas, który można ustawić w pliku [web.xml] konfigurującym aplikację.
- wiersz 21: zwracamy klucz error.
Powrót do konfiguracji akcji [EnregistrerSimulation]:
<!-- akcja 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>
Widać, że klucz error (wiersz 3) powoduje wyświetlenie widoku Tiles o nazwie erreur. Składa się ona z fragmentów [Entete.JSP] i [Erreur.JSP] i wygląda następująco:
![]() |
Fragment o nazwie [Erreur.JSP] wygląda następująco:
<%@ 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>
Powrót do metody [Enregistrer].execute:
// wykonanie akcji
public String execute() {
// pobieranie ostatniej symulacji z sesji
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
// pobieramy numer ostatniej symulacji
Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
if (numDerniereSimulation == null) {
numDerniereSimulation = 0;
}
// zwiększamy ten numer
numDerniereSimulation++;
// zapisywanie nowego numeru w sesji
session.put("numDerniereSimulation", numDerniereSimulation);
// pobieramy listę symulacji
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
simulations = new ArrayList<Simulation>();
session.put("simulations", simulations);
}
// dodajemy do niej bieżącą symulację
simulation.setNum(numDerniereSimulation);
simulations.add(simulation);
// wyświetla się lista symulacji
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
- wiersze 9–16: poszczególne symulacje są numerowane od 1. Ostatni przypisany numer jest zapisywany w sesji pod kluczem numDerniereSimulation. Kod w wierszach 9–16 polega na pobraniu tego klucza i zwiększeniu wartości z nim powiązanej.
- wiersze 18–22: lista symulacji jest przechowywana w sesji powiązanej z kluczem simulations. Wiersze 18–22 służą do odzyskania tej listy, jeśli istnieje, lub do jej utworzenia, jeśli nie istnieje.
- wiersze 24–25: po uzyskaniu listy symulacji dodaje się do niej bieżącą symulację (wiersz 25). Wcześniej bieżącej symulacji przypisano numer (wiersz 24).
- wiersz 27: ustalamy menu, które ma zostać wyświetlone
- wiersz 28: zwracamy klucz nawigacyjny simulations.
Powrót do konfiguracji akcji [EnregistrerSimulation] w [struts.xml]:
<!-- akcja 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>
W wierszu 4 klucz simulations powoduje wyświetlenie widoku Tiles o nazwie simulations. Widok ten składa się z fragmentów [Entete.JSP] i [Simulations.JSP]. Wyświetlany widok wygląda następująco:
![]() |
- w [1], fragment [Entete.JSP], który już dobrze znamy.
- w [2] – fragment [Simulations.JSP]
Fragment [Simulations.JSP] ma następującą postać:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- pusta lista symulacji -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- niepusta lista symulacji -->
<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"/>">Usuń</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- wiersze 5–7: jeśli w sesji nie ma symulacji, wyświetlany jest następujący widok:
![]() |
- wiersze 13–21: wyświetlanie nagłówków kolumn tabeli
- wiersze 23–55: iteracja po liście symulacji znalezionych w sesji
- wiersze 24–26: utworzenie obiektu URL o nazwie URL (atrybut id). Link HTML wygenerowany przez ten obiekt URL jest następujący:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>
Widać, że link kieruje do akcji [SupprimerSimulation] z parametrem id, który reprezentuje numer symulacji, którą należy usunąć z listy.
- wiersze 28–54: przy każdej iteracji nad listą symulacji wyświetlane są właściwości bieżącej symulacji.
![]() |
19.10. Usunięcie symulacji
Użytkownik może chcieć usunąć symulację z listy symulacji:
![]() |
![]() |
- w [1] usuwa się symulację nr 1
- w pliku [2] usunięto symulację nr 1
Link [Retirer] znajduje się we fragmencie [Simulations.JSP], który już omówiliśmy:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- pusta lista symulacji -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- niepusta lista symulacji -->
<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"/>">Usuń</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- wiersze 16–18: generują 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>
gdzie num to numer symulacji, którą należy usunąć.
Akcja [SupprimerSimulation] jest zdefiniowana w pliku [struts.xml] w następujący sposób:
<?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>
<!-- internacjonalizacja -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- integracja ze Springiem -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- akcje Struts /Tiles -->
<package name="default" namespace="/" extends="tiles-default">
...
<!-- akcja RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
...
</package>
<!-- Dodaj tutaj pakiety -->
</struts>
- wiersz 16: akcja [SupprimerSimulation] jest powiązana z klasą [Supprimer]. Ponieważ nie określono żadnej metody, zostanie wykonana jej metoda execute. Klasa [Supprimer] ma następującą postać:
package web.actions;
...
public class Supprimer extends ActionSupport implements SessionAware {
// sesja
private Map<String, Object> session;
// identyfikator symulacji do usunięcia
private String id;
// menu
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// wykonanie akcji
public String execute() {
// pobieranie symulacji z sesji
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
// sytuacja nietypowa – sesja musiała wygasnąć
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// test identyfikatora
int num = 0;
boolean erreur = false;
try {
num = Integer.parseInt(id);
erreur = num <= 0;
} catch (NumberFormatException ex) {
// nieprawidłowość
erreur = true;
}
// błąd?
if (erreur) {
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// szukamy symulacji do usunięcia
for (int i = 0; i < simulations.size(); i++) {
if (num == simulations.get(i).getNum()) {
simulations.remove(i);
break;
}
}
// wyświetlanie listy symulacji
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
// metody pobierające i ustawiające
...
}
- wiersz 4: akcja [Supprimer] implementuje interfejs [SessionAware] w celu uzyskania dostępu do sesji.
- wiersz 7: sesja
- wiersz 9: numer symulacji do usunięcia. Należy pamiętać, że instancja klasy [Supprimer] jest tworzona przez 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>
gdzie num to numer symulacji, którą należy usunąć. Numer ten zostanie zapisany w polu id w wierszu 9.
- wiersz 11: menu dla widoku, który zostanie wyświetlony w odpowiedzi na żądanie
- wiersz 19: metoda execute, która wygeneruje odpowiedź na zapytanie.
- wiersz 21: pobieramy listę symulacji już wykonanych w tej sesji
- wiersze 22–26: brak możliwości pobrania tej listy z sesji prawdopodobnie oznacza, że sesja wygasła. Spotkaliśmy się już z takim przypadkiem. Zwracamy klucz erreur, który powoduje wyświetlenie widoku Tiles erreur:
<!-- akcja RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
...
</action>
Widok Tiles o kluczu erreur został omówiony w paragrafie 19.9.
- wiersze 28–36: sprawdzamy, czy ciąg znaków id z wiersza 9 rzeczywiście reprezentuje liczbę całkowitą >0.
- wiersze 38–40: jeśli tak nie jest, zwracany jest ponownie klucz erreur, który spowoduje wyświetlenie widoku Tiles erreur
- wiersze 43–48: na liście symulacji wyszukiwana jest symulacja, którą należy usunąć. Jeśli zostanie znaleziona, zostaje usunięta.
- wiersz 50: aktualizujemy menu dla widoku Tiles simulations.
- wiersz 51: zwracany jest klucz simulations. Spowoduje to wyświetlenie widoku Tiles simulations:
<!-- akcja RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
...
<result name="simulations" type="tiles">simulations</result>
</action>
Widok kafelkowy simulations został omówiony w paragrafie 19.9.
19.11. Powrót do formularza
Z widoku Tiles simulations użytkownik może powrócić do formularza:
![]() |
![]() |
- w widoku [1] należy kliknąć link powrotu do formularza
- w widoku [2] wyświetla się pusty formularz
Link [Retour au formulaire de simulation] jest zdefiniowany we fragmencie [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>
- wiersz 10: link wskazuje na akcję [RetourFormulaire]. Jest ona zdefiniowana w następujący sposób w pliku [struts.xml]:
<!-- akcja RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
Widać, że ta akcja nie jest powiązana z żadną klasą. Ogranicza się ona jedynie do przekierowania przeglądarki klienta do akcji [/Formulaire!input]. Mamy zatem do czynienia z tą samą sytuacją, co podczas wyświetlania widoku początkowego opisanego w paragrafie 19.7. Znajdujemy więc ten widok początkowy [2].
19.12. Zobacz listę symulacji
Z poziomu widoków kafelkowych simulation lub saisie użytkownik może poprosić o wyświetlenie symulacji:
![]() |
![]() |
- w [1] należy kliknąć link [Voir les simulations]
- w [2] znajduje się lista symulacji
Link [Voir les simulations] jest zdefiniowany we fragmencie [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>
- wiersz 10: link [Voir les simulations] wywołuje akcję [VoirSimulations]. Jest ona zdefiniowana w następujący sposób w pliku [struts.xml]:
<!-- akcja VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
Akcja [VoirSimulations] jest powiązana z klasą [Voir] bez określenia metody. W związku z tym zostanie wykonana metoda [Voir].execute. Klasa [Voir] ma następującą postać:
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);
// metody pobierające i ustawiające
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
}
}
Akcja [Voir] wykonuje tylko jedną czynność: ustawia menu dla widoku „Tiles simulations” (wiersz 8). Nie ma metody execute. Zostanie zatem wykonana metoda klasy nadrzędnej [ActionSupport]. Wiadomo, że nie wykonuje ona żadnych innych czynności poza zwróceniem klucza success.
Wróćmy do działania w metodzie [struts.xml]:
<!-- akcja VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
W wierszu 3 widać, że klucz success powoduje wyświetlenie widoku Tiles simulations. Został on opisany na stronie 161.
19.13. Usunięcie bieżącej symulacji
Z poziomu widoku kafelkowego simulation użytkownik może poprosić o usunięcie bieżącej symulacji:
![]() |
![]() |
- w widoku [1] usuwa się bieżącą symulację
- w widoku [2] pojawia się pusty formularz wprowadzania danych
Link [Effacer la simulation] jest zdefiniowany we fragmencie [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>
W wierszu 10 widać, że link [Effacer la simulation] uruchamia akcję [Formulaire!input]. Wiemy, że ta akcja prowadzi do widoku początkowego [2].
19.14. Zakończenie bieżącej sesji
Z poziomu wszystkich widoków typu „Tiles” użytkownik może poprosić o zakończenie sesji:
![]() |
![]() |
![]() |
- w [1] przechodzi się z widoku symulacji i kończy sesję
- w [2] pojawia się pusty formularz wprowadzania danych. Wybieramy opcję wyświetlenia symulacji.
- w [3] lista symulacji jest teraz pusta.
Link [Terminer la session] jest zdefiniowany we fragmencie [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>
W wierszu 10 widać, że powiązanie [Terminer la session] uruchamia akcję [TerminerSession]. Jest ona zdefiniowana w następujący sposób w pliku [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>
- wiersz 1: widać, że zostanie utworzony egzemplarz klasy [Terminer] i wykonana jej metoda execute.
- wiersze 2–5: po wykonaniu metody [Terminer].execute nastąpi przekierowanie do początkowego widoku wprowadzania danych. To wyjaśnia ekran nr 2.
Klasa [Terminer] ma następującą postać:
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 {
// sesja
private Map<String, Object> session;
@Override
public String execute() {
// zakończenie bieżącej sesji
session.clear();
return SUCCESS;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
Zadaniem akcji [Terminer] jest wyczyszczenie atrybutów bieżącej sesji.
- wiersz 7: akcja [Terminer] implementuje interfejs [SessionAware] w celu uzyskania dostępu do sesji.
- wiersz 10: słownik sesji
- wiersz 13: wykonywana jest metoda execute
- wiersz 15: opróżnia słownik sesji. W rezultacie zniknie lista symulacji obecnych w sesji. To wyjaśnia zawartość ekranu nr 3.
- wiersz 16: zwraca klucz success, który – jak widzieliśmy – wyświetli widok Tiles saisie [2].
19.15. Conclusion
W pełni skomentowaliśmy wersję 1 naszego studium przypadku, która wykorzystuje symulowaną warstwę [metier]:
![]() |
Pozostaje nam tylko „podłączyć” rzeczywistą warstwę biznesową do warstwy [web].






























