Skip to content

19. Fallstudie – Version 1

19.1. Die simulierte Schicht [metier]

Kommen wir noch einmal auf die Architektur der Anwendung zurück, die wir entwickeln:

Wir verfügen über das Archiv der Schichten [metier, dao, jpa] und haben die Elemente dieser Schichten vorgestellt, die die Schicht [web] kennen muss. Wir sind bereit, diese mithilfe des Struts-Frameworks zu schreiben.

Um das Testen unserer in der Entwicklung befindlichen Anwendung zu vereinfachen, werden wir eine simulierte Geschäftsschicht erstellen, die die Schnittstelle der Schicht [metier] erfüllt. Die Architektur wird wie folgt aussehen:

Wir werden die Schicht [web] zusammen mit der simulierten Schicht [métier] entwickeln. Die Tests lassen sich einfacher durchführen, da in der Architektur keine Datenbank mehr vorhanden ist. Dank Spring und der Verwendung von Schnittstellen wird das spätere Ersetzen der simulierten Schicht [metier] durch die echte Architektur [metier, dao, jpa] keinerlei Auswirkungen auf den Code der Schicht [web / struts2] haben. Die Schicht [web / struts2], die wir nun entwickeln werden, kann unverändert verwendet werden.

Die simulierte Schicht [metier], die wir verwenden werden, lautet wie folgt:


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 {

  // Mitarbeiterliste
  private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
  private List<Employe> listEmployes;

  // Lohnabrechnung abrufen
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
    // Abruf des Mitarbeiters mit der Nummer SS
    Employe e = hashEmployes.get(SS);
    // Erstellung einer fiktiven Gehaltsabrechnung
    return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
  }

  // Mitarbeiterliste
  public List<Employe> findAllEmployes() {
    if (listEmployes == null) {
      // Es wird eine Liste mit zwei Mitarbeitern erstellt
      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)));
      // Mitarbeiterverzeichnis
      for (Employe e : listEmployes) {
        hashEmployes.put(e.getSS(), e);
      }
    }
    // Die Mitarbeiterliste wird ausgegeben
    return listEmployes;
  }
}
  • Zeile 11: Die Klasse [MetierSimule] implementiert die Schnittstelle [IMetier], die die eigentliche Schicht [metier] implementiert.
  • Zeile 14: Ein Verzeichnis der Mitarbeiter, indiziert nach ihrer Nummer INSEE
  • Zeile 15: Die Liste der Mitarbeiter
  • Zeilen 27–39: Implementierung der Methode findAllEmployes der Schnittstelle [IMetier].
  • Zeilen 30–33: Erstellung einer Liste mit zwei Mitarbeitern
  • Zeilen 34–36: Erstellung des nach der Nummer INSEE indizierten Mitarbeiterverzeichnisses
  • Zeilen 18–24: Implementierung der Methode calculerSalaire der Schnittstelle [IMetier]. Hier wird eine fiktive Gehaltsabrechnung zurückgegeben.

19.2. Das NetBeans-Projekt

Das NetBeans-Projekt sieht wie folgt aus:

  • in [1]:
  • [applicationContext.xml] ist die Spring-Konfigurationsdatei
  • [tiles.xml] ist die Konfigurationsdatei eines Frameworks namens Tiles.
  • [web.xml] ist die Konfigurationsdatei der Webanwendung
  • in [2]: die verschiedenen Ansichten der Anwendung
  • in [3]:
  • [messages.properties]: die Meldungsdatei
  • [struts.xml]: die Konfigurationsdatei von Struts
  • in [4]: die Quellcodes der Anwendung. Die Struts-Aktionen befinden sich im Paket [web.actions].
  • in [5]: die simulierte Schicht [metier]
  • in [6]: die verwendeten Archive. Hier befinden sich die Archive der verschiedenen verwendeten Tools: Spring, Tiles, Struts 2, das Struts-2-/Spring-Integrations-Plugin, das Struts-2-/Tiles-Integrations-Plugin.
  • in [7]: das Archiv der tatsächlichen Schicht [metier, dao, jpa]. Es ermöglicht uns den Zugriff auf die JPA-Entitäten, die Schnittstelle [IMetier] sowie die Klassen [FeuilleSalaire] und [ElementsSalaire]. All diese Elemente werden nämlich von unserer Klasse [MetierSimule] verwendet.

19.3. Projektkonfiguration

Das Projekt wird durch verschiedene Dateien konfiguriert:

  • [web.xml], die die Webanwendung konfiguriert
  • [struts.xml], das das Struts-Framework konfiguriert
  • [applicationContext.xml], das das Spring-Framework konfiguriert
  • [tiles.xml] zur Konfiguration des Tiles-Frameworks

19.3.1. Konfiguration der Webanwendung

Die Datei [web.xml] lautet wie folgt:


<?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>
    <!-- Kacheln -->
  <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>
  • Zeilen 13–20: Konfigurieren den Struts-2-Filter – bereits bekannt
  • Zeilen 22–24: Konfigurieren den Spring-Listener – bereits bekannt
  • Zeilen 9–11: Konfigurieren den Tiles-Listener. Die Klasse „[org.apache.struts2.tiles.StrutsTilesListener]“ wird beim Start der Webanwendung instanziiert. Sie nutzt dann ihre Konfigurationsdatei. Diese wird durch die Zeilen 5–8 definiert. Die Tiles-Konfigurationsdatei ist also die Datei „[WEB-INF/tiles.xml]“.

Letztendlich werden beim Start der Struts-Anwendung drei Klassen instanziiert:

  • eine für den Struts-2-Filter. Diese sorgt für das „C“ in MVC.
  • eine weitere für den Spring-Listener. Spring nutzt die Datei „[applicationContext.xml]“, um die Schichten „[métier, dao, jpa]“ der Anwendung zu instanziieren. Spring instanziiert außerdem, wie in einem vorherigen Beispiel, eine Klasse „[Config]“, die die Daten des Anwendungsbereichs enthält. Schließlich injiziert Spring in jede Struts-Aktion, die diese benötigt, eine Referenz auf diese einzige Instanz [Config].
  • Eine weitere Instanz ist für den Tiles-Listener vorgesehen. Dieses Framework übernimmt die Verwaltung der Ansichten. Wir werden bald darauf zurückkommen.

19.3.2. Konfiguration des Struts-Frameworks

Das Struts-Framework wird über die folgende Datei „[struts.xml]“ konfiguriert:


<?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>
  <!-- Internationalisierung -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- Spring-Integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- Struts-/Tiles-Aktionen -->
  <package name="default" namespace="/" extends="tiles-default">
    <!-- Standard-Aktion -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- Formular-Aktion -->
    <action name="Formulaire" class="web.actions.Formulaire" method="input">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
    </action>
    <!-- Aktion 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>
    <!-- Aktion 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>
   <!-- Aktion RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
   <!-- Aktion VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
    </action>
   <!-- Aktie 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>
   <!-- Aktion 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>

Wir werden die verschiedenen Struts-Aktionen im Laufe unserer Untersuchung kommentieren. Vorerst sind folgende Punkte zu beachten:

  • Zeile 8: Definiert die Meldungsdatei
  • Zeile 10: Legt fest, wie Spring-Beans in Struts-Aktionen injiziert werden. Die Injektion erfolgt anhand des Namens des Beans. Das Feld der Struts-Aktion, das von Spring initialisiert werden soll, muss denselben Namen wie das zu injizierende Bean tragen.
  • Zeile 25: Legt die Ansicht fest, die für den Navigationsschlüssel success der Aktion [Formulaire] angezeigt werden soll. Wir sehen, dass das Ergebnis <result> ein Attribut type='tiles' hat, das uns unbekannt ist. Wir kannten bereits den Typ redirect, mit dem der Client zu einer Ansicht weitergeleitet werden kann. Hier wird die Ansicht vom Typ tiles vom Framework Tiles verwaltet. Der Typ tiles ist in der Datei [struts-plugin.xml] des Archivs [struts2-tiles-plugin-2.2.3.1.jar] definiert:

<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>
  • Zeilen 3–5: Definition des Ergebnistyps tiles.
  • Zeile 2: Dieser Typ ist im Paket [tiles-default] definiert, das das Paket [struts-default] erweitert.
  • Zeile 14: Definiert das Paket [default], in dem sich alle Aktionen der Anwendung befinden werden. Um die Definition des Ansichtstyps tiles nutzen zu können, erweitert das Paket [tiles-default].

19.3.3. Konfiguration des Spring-Frameworks

Das Spring-Framework wird über die folgende Datei „[WEB-INF/applicationContext.xml]“ konfiguriert:


<?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">
  
  <!-- Anwendungsschichten -->
 
<!-- Web -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- Geschäftslogik -->
  <bean id="metier" class="metier.MetierSimule"/>

</beans>
  • Zeile 13: Die simulierte Schicht [metier], die von der Klasse [metier.MetierSimule] instanziiert wird
  • Zeilen 9–11: Konfigurieren einen Bean namens config. Wie in einem zuvor behandelten Beispiel dient dieser Bean dazu, die Informationen des Bereichs Application zu kapseln. Die diesem Bean zugeordnete Klasse ist die folgende Klasse [Config]:

package web;

import java.util.List;
import jpa.Employe;
import metier.IMetier;

public class Config {

  // von Spring initialisierte Fachschicht
  private IMetier metier;
  // Mitarbeiterliste
  private List<Employe> employes;
  // Fehler
  private Exception initException;

  // Konstruktor
  public Config() {
  }

  // Spring-Methode zur Initialisierung des Objekts
  public void init() {
    // Die Mitarbeiterliste wird angefordert
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

  // Getter und Setter
  ...
}

Kehren wir nun zur Konfiguration der Bean config zurück:


  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- Geschäftslogik -->
  <bean id="metier" class="metier.Metier">
    ...  
</bean>

In Zeile 2 ist zu sehen, dass die Bean metier aus Zeile 5 in das Feld mit dem Namen metier (name) des Objekts [Config] injiziert (ref) wird. Die Bean metier ist eine Referenz auf der Ebene [metier]:

Um mit der Schicht [metier] zu kommunizieren, benötigen alle Struts-Aktionen der Schicht [web] eine Referenz auf diese. Man kann sagen, dass die Referenz auf die Schicht [metier] eine Variable mit dem Geltungsbereich Application ist. Alle Anfragen aller Benutzer benötigen sie. Deshalb fügen wir diese Referenz in das Objekt [Config] ein. Außerdem weist die Konfiguration des Beans config in Zeile 1 ein Attribut init-method auf. Dieses Attribut bezeichnet die Methode des Beans, die nach der Instanziierung des Beans ausgeführt werden soll. Hier wird festgelegt, dass nach der Instanziierung der Klasse [web.Config] deren Methode init ausgeführt werden soll. Diese lautet wie folgt:


// Von Spring initialisierte Geschäftslogik-Schicht
  private IMetier metier;
  // Liste der Mitarbeiter
  private List<Employe> employes;
  // Fehler
  private Exception initException;

  // Konstruktor
  public Config() {
  }

  // Spring-Methode zur Initialisierung des Objekts
  public void init() {
    // Abfrage der Mitarbeiterliste
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

Wenn die Methode init ausgeführt wird, wurde das Feld metier der Klasse von Spring instanziiert. Die Methode init hat somit Zugriff auf die Geschäftslogik-Schnittstelle [IMetier] (Zeile 2):


package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
  // Lohnabrechnung abrufen
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // Liste der Mitarbeiter
  List<Employe> findAllEmployes();
}
  • Zeile 8: Mit dieser Methode lässt sich das Gehalt eines Mitarbeiters berechnen
  • Zeile 10: Die Methode dient dazu, die Liste der Mitarbeiter abzurufen

Es ist ersichtlich, dass die Methode init die Liste der Mitarbeiter von der Schicht [métier] anfordert. Diese Liste wird im Feld in Zeile 4 gespeichert. Tritt eine Ausnahme auf, wird diese im Feld in Zeile 6 gespeichert.

Zusammenfassend enthält das einzelne Objekt [Config]:

  • einen Verweis auf die Ebene [métier]
  • die Liste der Mitarbeiter

19.4. Generierung der Tiles-Ansichten

Wie in der Struts-Konfigurationsdatei zu sehen war, werden die Ansichten vom Framework Tiles generiert. Wir werden nur das unbedingt Notwendige erläutern, das für die Erstellung unserer Anwendung nützlich ist.

Mit Tiles lassen sich Ansichten ausgehend von einer Master-Seite generieren. Diese Seite, hier [MasterPage.jsp] genannt, setzt sich aus den folgenden JSP-Fragmenten zusammen:

Entete.jsp
 
Saisie.jsp
 
Simulation.jsp
 
Simulations.jsp
 
Exception.jsp
 
Erreur.jsp
 

Diese JSP-Fragmente sind im NetBeans-Projekt definiert:

Image

Das Framework Tiles ermöglicht es uns, festzulegen, welche Fragmente in die Master-Seite eingefügt werden sollen.

Die Master-Seite [MasterPage.jsp] sieht wie folgt aus:


<%@ 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>

Die Master-Seite ist ein Container für JSP-Fragmente. Hier setzt sie sich aus sechs Fragmenten zusammen, nämlich denen in den Zeilen 17 bis 23. Bei der Generierung können 0 bis 6 Fragmente in der Master-Seite zusammengefügt werden. Diese Generierung wird durch die Datei [WEB-INF/tiles.xml] gesteuert, die die Ansichten Tiles definiert:


<?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>
 
<!-- Die Master-Seite -->
  <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>
 
<!-- die Erfassungsansicht -->
  <definition name="saisie" extends="masterPage">
    <put-attribute name="saisie"   value="/Saisie.jsp"/>     
  </definition>

  <!-- die Simulationsansicht -->
 <definition name="simulation" extends="saisie">
    <put-attribute name="simulation"   value="/Simulation.jsp"/>     
  </definition>

<!-- Simulationsansicht -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"   value="/Simulations.jsp"/>     
  </definition>
  
<!-- Ausnahmeansicht -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"   value="/Exception.jsp"/>     
  </definition>
 
<!-- die Fehleransicht -->
 <definition name="erreur" extends="masterPage">
    <put-attribute name="erreur"   value="/Erreur.jsp"/>     
  </definition>
</tiles-definitions>
  • Die oben genannte Datei definiert sechs Ansichten Tiles mit den Namen: masterPage (Zeile 9), Eingabe (Zeile 20), Simulation (Zeile 25), Simulationen (Zeile 30), Ausnahme (Zeile 35), Fehler (Zeile 40).
  • Zeilen 9–17: Definieren eine Ansicht namens masterPage (name), die der Master-Seite [MasterPage.jsp] (template) zugeordnet ist. Wir haben gesehen, dass diese JSP-Seite sechs Unteransichten definiert. Eine der Master-Seite zugeordnete Ansicht Tiles muss das JSP-Fragment angeben, das jeder der sechs Unteransichten zugeordnet ist. Es ist zu erkennen, dass bestimmte Unteransichten den Wert (value) „leere Zeichenkette“ erhalten. Diese Unteransichten werden nicht in die Master-Seite [MasterPage.jsp] aufgenommen. Die Ansicht Tiles mit dem Namen masterPage besteht daher ausschließlich aus dem Unterfragment [Entete.jsp].
  • Zeilen 20–22: Definieren eine Ansicht namens saisie (name), die die zuvor genannte Ansicht masterPage erweitert (extends). Das bedeutet, dass sie alle Definitionen der Ansicht masterPage übernimmt. Ihre Definition entspricht der folgenden:

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

Man sieht, dass sie der JSP-Seite [MasterPage.jsp] zugeordnet ist und daher die sechs Unteransichten dieser Seite definieren muss. Es ist ersichtlich, dass die Definition in Zeile 9 diejenige in Zeile 4 aufhebt. Die Ansicht Tiles mit dem Namen saisie besteht somit aus den JSP-Fragmenten [Entete.jsp, Saisie.jsp]

Wenn wir diese Überlegung fortsetzen, erhalten wir die folgende Tabelle:

Tiles-Ansicht
JSP-Seiten
masterPage
Entete.jsp
saisie
Entete.jsp, Saisie.jsp
simulation
Entete.jsp, Saisie.jsp, Simulation.jsp
simulations
Entete.jsp, Simulations.jsp
exception
Entete.jsp, Exception.jsp
erreur
Entete.jsp, Erreur.jsp

19.5. Die Meldungsdateien

Die Anwendung wurde internationalisiert. Die Meldungen befinden sich in den Dateien [messages.properties] und [Formulaire.properties].

Die Datei [messages.properties] sieht wie folgt aus:


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
# Formate
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# Liste der Simulationen
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
# Menü
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
# Fehlermeldung
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# Konvertierungsfehler
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

Die Datei [Formulaire.properties] lautet wie folgt:


# damit die Doppelwerte im lokalen Format vorliegen
double.format={0,number,#0.00##}
# Fehlermeldung
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. Das Stylesheet

Die Kachelansichten verwenden das folgende Stylesheet „[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. Die Startansicht

Um die Anwendung zu untersuchen, werden wir sie anhand der verschiedenen Benutzeraktionen vorstellen. Wir werden jeweils die Aktion Struts betrachten, die diese Aktion ausführt, sowie die Ansicht Tiles, die als Antwort zurückgegeben wird.

In [struts.xml] finden sich folgende Aktionen:


  <!-- Standardaktion -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- Formular-Aktion -->
    <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>

  • Zeilen 2–8: Die Standardaktion der Anwendung ist [Formulaire!input].

Die Klasse [Formulaire] sieht wie folgt aus:


package web.actions;

...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {

  // von Spring initialisierte Konfiguration
  private Config config;
  // Mitarbeiterliste
  private List<Employe> employes;
  // Fehlerliste
  private List<Erreur> erreurs;
  // Lohnabrechnung
  private FeuilleSalaire feuilleSalaire;
  // Eingaben
  private String comboEmployesValue;
  private Double heuresTravaillees;
  private Integer joursTravailles;
  // Sitzung
  private Map<String, Object> session;
  // Menü
  private Menu menu;

  @Override
  public void prepare() throws Exception {
    ...
  }

  @Override
  public String input() {
  ....
  }

  // Lohnberechnung
  public String calculSalaire() {
 ...
    }
  }

  @Override
  public void validate() {
  ...
  }

  @Override
  public void setSession(Map<String, Object> map) {
    session = map;
  }

  // Getter und Setter
  ...
}
  • Zeile 4: Die Aktion [Formulaire] implementiert die Schnittstelle Preparable. Diese verfügt nur über eine Methode, nämlich die Methode prepare in Zeile 24. Diese Methode wird einmal vor allen anderen Methoden der Aktion ausgeführt. Sie dient in der Regel dazu, das Modell der Aktion zu initialisieren.

Das Aktionsmodell besteht aus den Zeilen 6–21:

  • Zeile 7: Das Feld config wird, wie bereits erläutert, von Spring initialisiert. Es ermöglicht den Zugriff auf Daten im Anwendungsbereich:
  • eine Referenz auf die Ebene [métier]
  • eine Referenz auf die Liste der Mitarbeiter.
  • eine Referenz auf die Ausnahme, die möglicherweise bei der Instanziierung des Objekts [Config] aufgetreten ist
  • Zeile 9: eine Liste der Mitarbeiter. Diese wird in das Kombinationsfeld für Mitarbeiter im Fragment [Saisie.jsp] eingespielt.
  • Zeile 11: eine Fehlerliste. Diese wird in das Fragment [Erreur.jsp] übernommen.
  • Zeile 21: die Liste der Menüoptionen des Fragments [Entete.jsp]

In [1] werden die Links des angezeigten Menüs durch das Feld menu der Aktion [Formulaire] gesteuert.

Die Methode prepare wird vor der Methode input ausgeführt. Sie lautet wie folgt:


  @Override
  public void prepare() throws Exception {
    // Konfigurationsfehler?
    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();
    }
}
  • Zeile 4: Die Ausnahme wird in dem von Spring instanziierten Objekt [Config] abgefangen
  • Zeile 5: Wenn bei der Instanziierung des Objekts [Config] eine Ausnahme aufgetreten ist, wird die Fehlerliste aus Zeile 11 initialisiert. Die Klasse [Erreur] sieht wie folgt aus:

package web.entities;

import java.io.Serializable;

public class Erreur implements Serializable{
  
  public Erreur() {
  }
  
  // Felder
  private String classe;
  private String message;

  // Konstruktor
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // Getter und Setter
...
}

Die Klasse dient dazu, den Ausnahmestapel zu speichern:

  • Zeile 11: die Ausnahmeklasse
  • Zeile 12: die Ausnahmemeldung

Kehren wir zur Methode prepare zurück:

  • Zeile 13: Die Liste der Mitarbeiter des Objekts [Config] wird im Feld employes der Aktion gespeichert.

Sobald die Methode prepare ausgeführt wurde, wird als Nächstes die Methode input ausgeführt. Diese lautet wie folgt:


  @Override
  public String input() {
    if (erreurs == null) {
      // Menü
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // Menü
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}

Die Methode input dient lediglich dazu, die Liste der anzuzeigenden Menüoptionen festzulegen. Die Klasse [Menu] lautet wie folgt:


package web.entities;

import java.io.Serializable;

public class Menu implements Serializable {
  // Menüelemente

  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;
  }
  
  // Getter und Setter
...  
}
  • Zeilen 8–13: Es gibt 6 mögliche Links im Menü
  • Zeilen 18–25: Mit dem Konstruktor der Klasse lassen sich festlegen, welche Links angezeigt werden sollen und welche nicht.

Die Menü-Links werden in [Entete.jsp] angezeigt, einem JSP-Fragment, das in allen Ansichten von Tiles vorhanden ist. Jede Aktion verfügt über ein Feld menu, um die Anzeige des Menüs von [Entete.jsp] zu steuern.

Kommen wir zurück zur Methode input:


  @Override
  public String input() {
    if (erreurs == null) {
      // Menü
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // Menü
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}
  • Zeilen 3–6: Ist die Fehlerliste leer, wird das Menü [Faire la simulation, Voir les simulations, Terminer la session] angezeigt und der Schlüssel input zurückgegeben.
  • Zeilen 9–10: Ist die Fehlerliste nicht leer, ist das Menü leer und der Schlüssel exception wird zurückgegeben.

Kehren wir zur Konfiguration der Aktion [Formulaire] in [struts.xml] zurück:


    <!-- Formularaktion -->
    <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>
  • Zeile 5: Der Schlüssel input zeigt die Kachelansicht mit dem Namen saisie an
  • Zeile 4: Der Schlüssel exception zeigt die Tiles-Ansicht mit dem Namen exception an

Beginnen wir mit der Tiles-Ansicht mit dem Namen saisie. Sie besteht aus den JSP-Fragmenten [Entete.jsp] und [Saisie.jsp].

Das Fragment [Entete.jsp] lautet wie folgt:

Image

Sein Code lautet wie folgt:


<%@ 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>
  • Zeilen 8–25: Anzeige der sechs Menülinks [Simulation durchführen (Zeilen 8–10), Simulation löschen (Zeilen 11–13), Simulationen anzeigen (Zeilen 14–16), Zurück zum Formular (Zeilen 17–19), Simulation speichern (Zeilen 20–22), Sitzung beenden (Zeilen 23–25).
  • Zeilen 8, 11, 14, 17, 20, 23: Die Anzeige der Links wird durch das Feld menu der aktuellen Aktion gesteuert.

Es ist zu beachten, dass das Fragment [Entete.jsp] eine HTML-Tabelle anzeigt (Zeilen 4–28), jedoch keine vollständige HTML-Seite darstellt. Dabei darf nicht vergessen werden, dass alle Ansichten der Anwendung in die folgende Master-Seite [MasterPage.jsp] eingebettet werden:


<%@ 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>

Das Fragment [Entete.jsp] wird in Zeile 17 innerhalb einer regulären HTML-Seite eingefügt.

Das Fragment [Saisie.jsp] wird in Zeile 19 eingefügt. Es handelt sich um die folgende Ansicht:

Image

Der Code des Fragments [Saisie.jsp] lautet wie folgt:


<%@ 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(){
    // Formular absenden
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }  
</script>

<!-- Eingabe der Informationen -->
<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>
  • Zeile 17: Das Formular hat kein Attribut action. Standardmäßig lautet es action='Formulaire'.
  • Zeile 18: Anzeige der Mitarbeiter-Combo-Box. Der Inhalt der Combo-Box (Attribut list) wird durch das Feld employes der aktuellen Aktion bereitgestellt. Das Attribut value der Optionen entspricht der Nummer SS der Mitarbeiter (Attribut listKey). Die für jede Option angezeigte Bezeichnung ist der Vorname und der Nachname des Mitarbeiters (Attribut listValue). Die Nummer SS des in der Auswahlliste ausgewählten Mitarbeiters wird in das Feld [Formulaire].comboEmployesvalue (Attribut name) übertragen.
  • Zeile 19: Eingabefeld für die geleisteten Arbeitsstunden. Der angezeigte Wert (Attribut value) entspricht dem Wert des Feldes heuresTravaillees der Aktion [Formulaire] im folgenden Format (Formulaire.properties):

double.format={0,number,#0.00##}

Der Wert wird in das Feld [Formulaire].heuresTravaillees (Attribut name) geschrieben.

  • Zeile 20: Eingabefeld für die gearbeiteten Tage. Der angezeigte Wert (Attribut value) entspricht dem Wert des Feldes joursTravailles der Aktion [Formulaire].

Der Wert wird in das Feld [Formulaire].joursTravailles (Attribut name) geschrieben.

Letztendlich sieht die Ansicht „Tiles saisie“, die beim Start angezeigt wird, wenn keine Fehler vorliegen, wie folgt aus:

Image

Kehren wir zur Konfiguration der Aktion [Formulaire] zurück:


    <!-- Formularaktion -->
    <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>

Wir haben gesehen, dass die Aktion [Formulaire].input auch den Schlüssel exception aus Zeile 4 zurückgeben kann. In diesem Fall wird die Tiles-Ansicht mit dem Namen exception angezeigt. Diese setzt sich aus den Fragmenten [Entete.jsp] und [Exception.jsp] zusammen. Das Fragment [Entete.jsp] haben wir bereits vorgestellt. Das Fragment [Exception.jsp] lautet wie folgt:

Image

Dies ist die Startseite der Version 2 der Anwendung, wenn das DBMS nicht gestartet wurde. Der JSP-Code des Fragments [Erreur.jsp] lautet wie folgt:


<%@ 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>
  • Zeilen 10–14: Ein Iterator über die Sammlung `List<Erreur>` „erreurs“ der Aktion `[Formulaire]`. Wir erinnern uns, dass dort im Fehlerfall ein Ausnahmestapel gespeichert wurde.

19.8. Simulation durchführen

Sobald die Startansicht angezeigt wird, kann über den Link [Faire une simulation] eine Gehaltsberechnung durchgeführt werden.

19.8.1. Validierung der Eingaben

Betrachten wir die folgende Abfolge:

  • zu [1], eine fehlerhafte Eingabe
  • zu [2], die gesendete Antwort.

Betrachten wir die Konfiguration der Aktion [Formulaire] in [struts.xml]:


    <!-- Formularaktion -->
    <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>

Es ist bekannt, dass der Validierungs-Interceptor im Falle eines Validierungsfehlers den Schlüssel input zurückgibt. Daher wird die Ansicht „Tiles“ saisie zurückgegeben. Der Validierungsprozess sorgt dafür, dass die fehlerhaften Felder mit Fehlermeldungen versehen werden.

Die Validierung der Aktion [Formulaire] wird durch die folgende Datei [Formulaire-validation.xml] gewährleistet:


<!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>
  • Die Zeilen 6–20 prüfen, ob das Feld heuresTravaillees eine reelle Zahl im Intervall [0,300] ist.
  • Die Zeilen 22–36 prüfen, ob das Feld joursTravailles eine ganze Zahl im Intervall [0,31] ist.

Kehren wir zur Konfiguration der Aktion [Formulaire] in [struts.xml] zurück:


    <!-- Formularaktion -->
    <action name="Formulaire" class="web.actions.Formulaire">
      ...
      <result name="input" type="tiles">saisie</result>
</action>

Es ist bekannt, dass der Validierungs-Interceptor im Falle eines Validierungsfehlers den Schlüssel input zurückgibt. Daher wird die Tiles-Ansicht saisie zurückgegeben.

Zur Erinnerung: Diese besteht aus den Fragmenten [Entete.jsp] und [Saisie.jsp], wobei [Entete.jsp] eine Überschrift und eine Auswahl an Optionen enthält und [Saisie.jsp] das Eingabeformular. Bei Eingabefehlern sorgt der Validierungsprozess dafür, dass die fehlerhaften Felder mit Fehlermeldungen versehen werden und zudem ihren fehlerhaften Wert anzeigen. Das Fragment [Entete.jsp] spielt im Validierungsprozess keine Rolle. Sehen wir uns dessen Code an:


<%@ 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>

Die sechs Links werden über das Feld menu des Modells konfiguriert (Zeilen 8, 11, 14, 17, 20, 23). Wenn ein Fehler auftritt, wird dieses Modell durch die Aktion nicht aktualisiert, sodass eine Seite ohne Menü angezeigt wird. Um dieses Problem zu beheben, verfügt die Klasse [Formulaire] über die folgende Methode validate:


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {

  ...
  // Menü
  private Menu menu;

  @Override
  public void prepare() throws Exception {
  ...
  }

  @Override
  public String input() {
  ...
  }

  // Lohnberechnung
  public String calculSalaire() {
  ...
  }

  @Override
  public void validate() {
    // Fehler?
    if (!getFieldErrors().isEmpty()) {
      // Menü
      menu = new Menu(true, false, false, true, false, true);
    }
  }

  // Getter und Setter
  ...
}
  • Zeile 27: Es ist bekannt, dass die Methode validate vom Validierungsprozess ausgeführt wird, wenn sie vorhanden ist. Dies wird genutzt, um das Menü in Zeile 4 zu aktualisieren, das Teil der Vorlage des Fragments [Entete.jsp] ist.
  • Zeilen 29–32: Wenn Validierungsfehler aufgetreten sind, wird das Menü so eingestellt, dass die Ansicht „Tiles saisie“ erneut angezeigt wird. Wenn keine Validierungsfehler aufgetreten sind, wird nichts unternommen. Die Methode calculSalaire ist dann dafür zuständig, das Modell der anzuzeigenden Ansicht zu erstellen.

19.8.2. Die Gehaltsberechnung

Kehren wir zum JSP-Code des Kopfbereichs zurück:


<%@ 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>
  • Zeile 9: Wenn der Benutzer auf den Link [Faire la simulation] klickt, wird die JavaScript-Funktion doSimulation ausgeführt. Diese ist im Fragment [Saisie.jsp] definiert:

<script language="javascript" type="text/javascript">
  function doSimulation(){
    // Das Formular wird hochgeladen
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }
</script>

<!-- Eingabe der Daten -->
<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>
  • Zeile 14: Ein verstecktes Feld mit dem Namen action wird an die Aktion [Formulaire] übermittelt. Mit diesem Feld können wir die Aktion und die Methode angeben, die im Formular unter POST ausgeführt werden sollen. Vielleicht erinnern wir uns noch an die ersten Beispiele, in denen diese Angaben in einem Parameter namens „action:Action!Methode“ festgelegt werden können. Der Wert dieses Parameters spielt keine Rolle. Es reicht aus, dass er vorhanden ist.
  • Zeilen 2–6: Die JavaScript-Funktion, die ausgeführt wird, wenn der Benutzer auf den Link [Faire la simulation] im Fragment [Entete.jsp] klickt.
  • Zeile 4: Das Attribut name des versteckten Feldes action wird geändert. Es wird so gestaltet, dass es die von Struts erwartete Form „action:Action!Methode“ aufweist.
  • Zeile 5: Das Formular mit dem Namen Saisie aus Zeile 5 wird gesendet. Dadurch wird die folgende Parameterkette gesendet:
comboEmployesValue=SS1&heuresTravaillees=xx&joursTravailles=yy&action:Formulaire!calculSalaire

SS1: Nummer INSEE des im Dropdown-Menü ausgewählten Mitarbeiters

heuresTravaillees: Anzahl der gearbeiteten Stunden

joursTravailles: Anzahl der gearbeiteten Tage

Aktion:Formular!calculSalaire: Die oben genannten Elemente werden an die Aktion [Formulaire] übermittelt, anschließend wird die Methode calculSalaire dieser Aktion ausgeführt.

Die Methode [Formulaire].calculSalaire lautet wie folgt:


// Lohnberechnung
  public String calculSalaire() {
    try {
      // Lohnberechnung
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // Die Simulation wird in die Sitzung übernommen
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // Menü
      menu = new Menu(true, true, true, true, false, true);
      // Fertig
      return "simulation";
    } catch (Throwable th) {
      ...
    }
  }
  • Zeile 5: Die Berechnung der Gehaltsabrechnung wird an die Ebene [métier] angefordert
  • Zeile 7: Ein Objekt vom Typ Simulation wird in die Sitzung des Benutzers aufgenommen. Dies kann bei einer späteren Abfrage erforderlich sein. Die Klasse [Simulation] lautet wie folgt:

package web.entities;

import java.io.Serializable;
import metier.FeuilleSalaire;

public class Simulation implements Serializable{
  
  public Simulation() {
  }

  // Felder einer Simulation
  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();
  }

  // Getter und Setter
...
}
  • Zeile 12: Die Nummer der Simulation. Wird bei jeder neu gespeicherten Simulation erhöht.
  • Zeile 13: die Gehaltsabrechnung des Mitarbeiters
  • Zeile 14: die Anzahl der gearbeiteten Stunden
  • Zeile 15: die Anzahl der gearbeiteten Tage
  • Zeile 25: Die Methode getIndemnites gibt die Summe der Zulagen des Mitarbeiters zurück

Wir werden sehen, dass die Klasse [Simulation] die Vorlage für das Fragment [Simulations.jsp] ist, das alle durchgeführten Simulationen enthält.

Zurück zur Methode [Formulaire].calculSalaire:


// Lohnberechnung
  public String calculSalaire() {
    try {
      // Gehaltsberechnung
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // Die Simulation wird in die Sitzung geladen
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // Menü
      menu = new Menu(true, true, true, true, false, true);
      // Fertig
      return "simulation";
    } catch (Throwable th) {
 ...
  }
  • Zeile 9: Aktualisierung des Menüs
  • Zeile 11: Rückgabe des Navigationsschlüssels simulation.

Zurück zur Konfiguration der Aktion [Formulaire]:


    <!-- Formularaktion -->
    <action name="Formulaire" class="web.actions.Formulaire">
         ...
      <result name="simulation" type="tiles">simulation</result>
</action>

Zeile 4 zeigt, dass der Navigationsschlüssel simulation die Kachelansicht mit dem Namen simulation anzeigt. Diese besteht aus den folgenden JSP-Fragmenten: [Entete, Saisie, Simulation].

Die gerenderte Ansicht sieht wie folgt aus:

  • in [1], das Fragment [Entete.jsp]
  • in [2], das Fragment [Saisie.jsp]
  • in [3], das Fragment [Simulation.jsp]. Zur Erinnerung: Bei der dargestellten Gehaltsabrechnung handelt es sich um die fiktive Gehaltsabrechnung, die von der Ebene [metier] zurückgegeben wurde.

Die ersten beiden Fragmente wurden bereits vorgestellt. Das Fragment [Simulation.jsp] lautet wie folgt:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<hr/>
<!-- Mitarbeiterinformationen -->
<span class="titreInfos">
  <s:text  name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- Zeile 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>
  <!-- Zeile 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>
  <!-- Zeile 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>
  <!-- Zeile 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>
<!-- Informationen zu Beiträgen -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.cotisations"/>
</span>

<br/><br/>
<table>
  <!-- Zeile 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>
  <!-- Zeile 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>
<!-- Informationen zu Entschädigungen -->
<br/>
<span class="titreInfos">
  <s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
  <!-- Zeile 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>
  <!-- Zeile 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>
<!-- Informationen zum Gehalt -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
  <!-- Zeile 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>

  <!-- Zeile 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>
<!-- Nettogehalt-->
<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>

Das ist lang … aber funktional einfach. Dieses Fragment zeigt die verschiedenen Eigenschaften des Feldes [Formulaire].feuilleSalaire an, das die Gehaltsabrechnung des Mitarbeiters darstellt.

Zurück zur Methode [Formulaire].calculSalaire:


// Lohnberechnung
  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();
      }
      // Menü
      menu = new Menu(false, false, false, false, true, true);
      return "exception";
    }
  }

Die Gehaltsabrechnung kann fehlschlagen. Dies wäre insbesondere dann der Fall, wenn die Verbindung zum DBMS unterbrochen würde. In diesem Fall wird die auftretende Ausnahme behandelt. Wir sind diesem Fall bereits bei der Untersuchung der Methode [Formulaire].input begegnet.

  • Zeilen 7–11: Es wird eine Liste von Objekten Erreur aus dem Ausnahmestapel erstellt
  • Zeile 13: Das Menü wird festgelegt
  • Zeile 14: Der Schlüssel exception wird zurückgegeben.

Der Schlüssel exception bewirkt, dass die Kachelansicht exception angezeigt wird:


    <!-- Aktion „Formular“ -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

Diese „Tiles“-Ansicht wurde bereits vorgestellt. Sie sieht wie folgt aus:

Image

19.9. Eine Simulation speichern

Nach Durchführung einer Simulation möchte der Benutzer diese möglicherweise in der Sitzung speichern.

  • In [1] wird die Simulation gespeichert
  • In [2] wird die Antwort angezeigt, die die Liste der bereits durchgeführten Simulationen enthält, zu der die neue Simulation hinzugefügt wird

Der Link [Enregistrer la simulation] befindet sich im Fragment [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>

Man sieht, dass ein Klick auf den Link die Ausführung der Aktion [EnregistrerSimulation] auslöst. Diese ist in der Datei [struts.xml] wie folgt konfiguriert:


   <!-- Aktion 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>
  • Zeile 1: Die Aktion [EnregistrerSimulation] ist der Klasse [Enregistrer] und deren Methode execute zugeordnet.

Die Klasse [Enregistrer] lautet wie folgt:


package web.actions;

...
public class Enregistrer extends ActionSupport implements SessionAware {

  // Sitzung
  private Map<String, Object> session;
  // Menü
  private Menu menu;

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // Ausführung der Aktion
  public String execute() {
    // Die letzte Simulation in der Sitzung wird abgerufen
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }

  // Getter und Setter
  ...
}
  • Zeile 4: Da die Aktion Zugriff auf die Sitzung benötigt, implementiert sie die Schnittstelle SessionAware.
  • Zeile 7: Die Sitzung
  • Zeile 9: Das Menü

Wenn die Aktion [Enregistrer] instanziiert wurde, wird ihre Methode execute ausgeführt. Zur Erinnerung: Ihre Aufgabe besteht darin, die letzte Simulation in die Sitzung zu übernehmen. Diese wird der Liste der bereits durchgeführten Simulationen hinzugefügt, die ebenfalls in der Sitzung gespeichert wird.

  • Zeile 19: Die zuletzt in die Sitzung eingefügte Simulation wird abgerufen.
  • Zeilen 20–22: Wird sie nicht gefunden, ist die Sitzung wahrscheinlich abgelaufen. Diese hat nämlich nur eine bestimmte Laufzeit, die in der Datei [web.xml], die die Anwendung konfiguriert, festgelegt werden kann.
  • Zeile 21: Der Schlüssel error wird zurückgegeben.

Zurück zur Konfiguration der Aktion [EnregistrerSimulation]:


   <!-- Aktion 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>

Man sieht, dass der Schlüssel error (Zeile 3) die Anzeige der Ansicht „ -Kacheln“ mit dem Namen erreur bewirkt. Diese setzt sich aus den Fragmenten [Entete.jsp] und [Erreur.jsp] zusammen und sieht wie folgt aus:

Image

Das Fragment [Erreur.jsp] lautet wie folgt:


<%@ 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>

Zurück zur Methode [Enregistrer].execute:


// Ausführung der Aktion
  public String execute() {
    // Die letzte Simulation in der Sitzung wird abgerufen
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // die Nummer der letzten Simulation wird abgerufen
    Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
    if (numDerniereSimulation == null) {
      numDerniereSimulation = 0;
    }
    // Inkrementierung
    numDerniereSimulation++;
    // Die neue Nummer wird in die Sitzung zurückgeschrieben
    session.put("numDerniereSimulation", numDerniereSimulation);
    // Die Liste der Simulationen wird abgerufen
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // die aktuelle Simulation wird hinzugefügt
    simulation.setNum(numDerniereSimulation);
    simulations.add(simulation);
    // die Liste der Simulationen wird angezeigt
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
  • Zeilen 9–16: Die verschiedenen Simulationen werden beginnend mit 1 nummeriert. Die zuletzt vergebene Nummer wird in der Sitzung unter dem Schlüssel numDerniereSimulation gespeichert. Der Code in den Zeilen 9–16 dient dazu, diesen Schlüssel abzurufen und den ihm zugeordneten Wert zu erhöhen.
  • Zeilen 18–22: Die Liste der Simulationen wird in der Sitzung gespeichert, die dem Schlüssel simulations zugeordnet ist. In den Zeilen 18–22 wird diese Liste abgerufen, sofern sie vorhanden ist, oder erstellt, falls sie nicht vorhanden ist.
  • Zeilen 24–25: Sobald die Liste der Simulationen vorliegt, wird die aktuelle Simulation hinzugefügt (Zeile 25). Zuvor wurde der aktuellen Simulation eine Nummer zugewiesen (Zeile 24).
  • Zeile 27: Das anzuzeigende Menü wird festgelegt
  • Zeile 28: Der Navigationsschlüssel wird auf simulations gesetzt.

Zurück zur Konfiguration der Aktion [EnregistrerSimulation] in [struts.xml]:


   <!-- Aktion 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>

In Zeile 4 bewirkt der Schlüssel simulations die Anzeige der Ansicht „ Tiles“ mit dem Namen simulations. Diese Ansicht setzt sich aus den Fragmenten [Entete.jsp] und [Simulations.jsp] zusammen. Die angezeigte Ansicht sieht wie folgt aus:

  • in [1], das Fragment [Entete.jsp], das wir inzwischen gut kennen.
  • in [2] das Fragment [Simulations.jsp]

Das Fragment [Simulations.jsp] lautet wie folgt:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- leere Simulationsliste -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- Liste mit Simulationen nicht leer -->
<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"/>">Entfernen</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • Zeilen 5–7: Wenn in der Sitzung keine Simulationen vorhanden sind, wird die folgende Ansicht angezeigt:

Image

  • Zeilen 13–21: Anzeige der Spaltenüberschriften der Tabelle

Image

  • Zeilen 23–55: Iteration über die Liste der in der Sitzung gefundenen Simulationen
  • Zeilen 24–26: Erstellung einer URL mit dem Namen url (Attribut id). Der durch diese URL generierte HTML-Link lautet wie folgt:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>

Man sieht, dass der Link auf die Aktion [SupprimerSimulation] verweist, mit dem Parameter id, der die Nummer der Simulation angibt, die aus der Liste entfernt werden soll.

  • Zeilen 28–54: Bei jeder Iteration über die Liste der Simulationen werden die Eigenschaften der aktuellen Simulation angezeigt.

Image

19.10. Eine Simulation entfernen

Der Benutzer möchte möglicherweise eine Simulation aus der Simulationsliste entfernen:

  • In [1] wird die Simulation Nr. 1 gelöscht
  • In [2] wurde die Simulation Nr. 1 gelöscht

Der Link [Retirer] befindet sich in dem Fragment [Simulations.jsp], das wir bereits betrachtet haben:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!-- leere Simulationsliste -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- nicht leere Simulationsliste -->
<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"/>">Entfernen</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • Zeilen 16–18: erzeugen den HTML-Link
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

wobei num die Nummer der zu entfernenden Simulation ist.

Die Aktion [SupprimerSimulation] ist in der Datei [struts.xml] wie folgt definiert:


<?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>
  <!-- Internationalisierung -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- Spring-Integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- Struts-/Tiles-Aktionen -->
  <package name="default" namespace="/" extends="tiles-default">
    ...
   <!-- Aktion RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   ...
  </package>

    <!-- Pakete hier hinzufügen -->

</struts>
  • Zeile 16: Die Aktion [SupprimerSimulation] ist der Klasse [Supprimer] zugeordnet. Da keine Methode angegeben ist, wird deren Methode execute ausgeführt. Die Klasse [Supprimer] lautet wie folgt:

package web.actions;

...
public class Supprimer extends ActionSupport implements SessionAware {

  // Sitzung
  private Map<String, Object> session;
  // ID der zu löschenden Simulation
  private String id;
  // Menü
  private Menu menu;

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // Ausführung der Aktion
  public String execute() {
    // Die Simulationen werden aus der Sitzung abgerufen
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // Ausnahmefall – die Sitzung ist abgelaufen
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // ID-Prüfung
    int num = 0;
    boolean erreur = false;
    try {
      num = Integer.parseInt(id);
      erreur = num <= 0;
    } catch (NumberFormatException ex) {
      // Fehler
      erreur = true;
    }
    // Fehler?
    if (erreur) {
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // Die zu löschende Simulation wird gesucht
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // Liste der Simulationen wird angezeigt
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }

  // Getter und Setter
...
}
  • Zeile 4: Die Aktion [Supprimer] implementiert die Schnittstelle [SessionAware], um Zugriff auf die Sitzung zu erhalten.
  • Zeile 7: Die Sitzung
  • Zeile 9: Die Nummer der zu löschenden Simulation. Wir erinnern uns nämlich daran, dass wir über die HTML-URL zur Instanziierung der Klasse [Supprimer] gelangen:
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

wobei num die Nummer der zu löschenden Simulation ist. Diese Nummer wird im Feld id in Zeile 9 gespeichert.

  • Zeile 11: Das Menü für die Ansicht, die als Antwort auf die Anfrage angezeigt wird
  • Zeile 19: Die Methode execute, die die Antwort auf die Anfrage generiert.
  • Zeile 21: Die Liste der in der Sitzung bereits durchgeführten Simulationen wird abgerufen
  • Zeilen 22–26: Wird diese Liste nicht in der Sitzung abgerufen, bedeutet dies wahrscheinlich, dass die Sitzung abgelaufen ist. Dieser Fall ist bereits aufgetreten. Wir geben den Schlüssel erreur zurück, der die Ansicht „Tiles“ erreur anzeigt:

<!-- Aktion RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      ...
</action>

Die Tiles-Ansicht erreur wurde in Abschnitt 19.9 vorgestellt.

  • Zeilen 28–36: Es wird überprüft, ob die Zeichenfolge id in Zeile 9 tatsächlich eine ganze Zahl > 0 darstellt.
  • Zeilen 38–40: Ist dies nicht der Fall, wird erneut der Schlüssel erreur zurückgegeben, wodurch die Ansicht „Tiles“ erreur angezeigt wird.
  • Zeilen 43–48: Die zu entfernende Simulation wird in der Liste der Simulationen gesucht. Wird sie gefunden, wird sie gelöscht.
  • Zeile 50: Das Menü wird für die Ansicht „Tiles simulations“ aktualisiert.
  • Zeile 51: Der Schlüssel simulations wird zurückgegeben. Dieser bewirkt die Anzeige der Ansicht „Tiles“ simulations:

<!-- Aktion RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

Die Kachelansicht simulations wurde in Abschnitt 19.9 vorgestellt.

19.11. Zurück zum Formular

Von der Kachelansicht simulations aus kann der Benutzer zum Formular zurückkehren:

  • In [1] klickt man auf den Link „Zurück zum Formular“
  • In [2] wird ein leeres Formular angezeigt

Der Link [Retour au formulaire de simulation] ist im Fragment [Entete.jsp] definiert:


<%@ 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>
  • Zeile 10: Der Link verweist auf die Aktion [RetourFormulaire]. Diese ist in der Datei [struts.xml] wie folgt definiert:

   <!-- Aktion RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>

Man sieht, dass diese Aktion keiner Klasse zugeordnet ist. Sie leitet den Client-Browser lediglich an die Aktion [/Formulaire!input] weiter. Wir befinden uns also in derselben Situation wie bei der Anzeige der in Abschnitt 19.7 erläuterten Startansicht. Wir finden daher diese Startansicht [2] wieder.

19.12. Siehe Liste der Simulationen

Ausgehend von den Ansichten „Tiles“ simulation oder saisie kann der Benutzer die Simulationen anzeigen lassen:

  • In [1] klickt man auf den Link [Voir les simulations]
  • In [2] findet man die Liste der Simulationen

Der Link [Voir les simulations] ist im Fragment [Entete.jsp] definiert:


<%@ 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>
  • Zeile 10: Der Link [Voir les simulations] ruft die Aktion [VoirSimulations] auf. Diese ist in der Datei [struts.xml] wie folgt definiert:

   <!-- Aktion VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

Die Aktion [VoirSimulations] ist der Klasse [Voir] ohne Angabe einer Methode zugeordnet. Daher wird die Methode [Voir].execute ausgeführt. Die Klasse [Voir] lautet wie folgt:


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;

public class Voir extends ActionSupport{
  // Menü
  private Menu menu=new Menu(false,false,false,false,true,true);
  // Getter und Setter

  public Menu getMenu() {
    return menu;
  }

  public void setMenu(Menu menu) {
    this.menu = menu;
  }
  
}

Die Aktion [Voir] führt nur eine einzige Aufgabe aus: Sie positioniert das Menü für die Ansicht „Tiles simulations“ (Zeile 8). Es gibt keine Methode execute. Daher wird die Methode der übergeordneten Klasse [ActionSupport] ausgeführt. Es ist bekannt, dass diese nichts anderes tut, als den Schlüssel success zurückzugeben.

Zurück zur Aktion in [struts.xml]:


   <!-- Aktion VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

In Zeile 3 sieht man, dass der Schlüssel success zur Anzeige der Ansicht „Tiles“ simulations führt. Diese wurde auf Seite 156 beschrieben.

19.13. Aktuelle Simulation löschen

Ausgehend von der Tiles-Ansicht simulation kann der Benutzer das Löschen der aktuellen Simulation anfordern:

  • In [1] wird die aktuelle Simulation gelöscht
  • In [2] wird das leere Eingabeformular angezeigt

Der Link [Effacer la simulation] ist im Fragment [Entete.jsp] definiert:


<%@ 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>

In Zeile 10 ist zu sehen, dass die Verknüpfung [Effacer la simulation] die Aktion [Formulaire!input] auslöst. Wir wissen, dass diese Aktion zur Startansicht [2] führt.

19.14. Aktuelle Sitzung beenden

Von allen Kachelansichten aus kann der Benutzer das Beenden der Sitzung anfordern:

  • In [1] startet man von der Simulationsansicht aus und beendet die Sitzung
  • in [2] wird das leere Eingabeformular angezeigt. Man fordert die Anzeige der Simulationen an.
  • In [3] ist die Liste der Simulationen nun leer.

Der Link [Terminer la session] ist im Fragment [Entete.jsp] definiert:


<%@ 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>

In Zeile 10 ist zu sehen, dass der Link [Terminer la session] die Aktion [TerminerSession] auslöst. Diese ist in der Datei [struts.xml] wie folgt definiert:


    <action name="TerminerSession" class="web.actions.Terminer">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>
  • Zeile 1: Hier ist zu sehen, dass die Klasse [Terminer] instanziiert und ihre Methode execute ausgeführt wird.
  • Zeilen 2–5: Nach Ausführung der Methode [Terminer].execute erfolgt eine Weiterleitung zur ursprünglichen Eingabemaske. Dies erklärt Bildschirm Nr. 2.

Die Klasse [Terminer] lautet wie folgt:


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 {

  // Sitzung
  private Map<String, Object> session;
  
  @Override
  public String execute() {
    // Beenden der aktuellen Sitzung
    session.clear();
    return SUCCESS;
  }

  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
}

Die Aktion [Terminer] dient dazu, die Attribute der aktuellen Sitzung zu löschen.

  • Zeile 7: Die Aktion [Terminer] implementiert die Schnittstelle [SessionAware], um Zugriff auf die Sitzung zu erhalten.
  • Zeile 10: Das Session-Dictionary
  • Zeile 13: Die Methode execute wird ausgeführt
  • Zeile 15: Sie leert das Sitzungswörterbuch. Dadurch verschwindet die Liste der Simulationen, die sich in der Sitzung befinden. Dies erklärt den Bildschirm Nr. 3.
  • Zeile 16: Sie gibt den Schlüssel success zurück, der, wie wir gesehen haben, die Ansicht „Tiles“ saisie [2] anzeigt.

19.15. Conclusion

Wir haben Version 1 unserer Fallstudie, die mit einer simulierten Ebene [metier] arbeitet, vollständig kommentiert:

Jetzt müssen wir nur noch die echte Fachschicht an die Schicht [web] „anbinden“.