19. Caso de estudio – versión 1
19.1. La capa [metier] simulada
Volvamos a la arquitectura de la aplicación que estamos desarrollando:
![]() |
Contamos con el archivo de las capas [metier, dao, jpa] y hemos presentado los elementos de estas capas que la capa [web] debía conocer. Estamos listos para escribirla utilizando el marco de trabajo Struts.
Para simplificar las pruebas de nuestra aplicación en desarrollo, crearemos una capa de negocio simulada que respetará la interfaz de la capa [metier]. La arquitectura quedará así:
![]() |
Desarrollaremos la capa [web] junto con la capa simulada [métier]. Las pruebas serán más sencillas de realizar, ya que ya no hay una base de datos en la arquitectura. Gracias a Spring y al uso de interfaces, reemplazar posteriormente la capa simulada [metier] por la arquitectura real [metier, dao, jpa] no tendrá ningún impacto en el código de la capa [web / struts2]. La capa [web / struts2] que vamos a desarrollar ahora se podrá utilizar tal cual.
La capa simulada [metier] que vamos a utilizar es la siguiente:
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 de empleados
private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
private List<Employe> listEmployes;
// obtener la nómina
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// se recupera al empleado con el n.º SS
Employe e = hashEmployes.get(SS);
// se genera una nómina ficticia
return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
}
// lista de empleados
public List<Employe> findAllEmployes() {
if (listEmployes == null) {
// se crea una lista de dos empleados
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)));
// diccionario de empleados
for (Employe e : listEmployes) {
hashEmployes.put(e.getSS(), e);
}
}
// se muestra la lista de empleados
return listEmployes;
}
}
- línea 11: la clase [MetierSimule] implementa la interfaz [IMetier] que implementa la capa real [metier].
- línea 14: un diccionario de empleados indexados por su n.º INSEE
- línea 15: la lista de empleados
- líneas 27-39: implementación del método findAllEmployes de la interfaz [IMetier].
- líneas 30-33: creación de una lista de dos empleados
- líneas 34-36: creación del diccionario de empleados indexado por el n.º INSEE
- líneas 18-24: implementación del método calculerSalaire de la interfaz [IMetier]. Aquí se devuelve una nómina ficticia.
19.2. El proyecto de NetBeans
El proyecto de NetBeans es el siguiente:
![]() |
- en [1]:
- [applicationContext.xml] es el archivo de configuración de Spring
- [tiles.xml] es el archivo de configuración de un marco de trabajo llamado Tiles.
- [web.xml] es el archivo de configuración de la aplicación web
- en [2]: las diferentes vistas de la aplicación
- en [3]:
- [messages.properties]: el archivo de mensajes
- [struts.xml]: el archivo de configuración de Struts
![]() |
- en [4]: los códigos fuente de la aplicación. Las acciones de Struts se encuentran en el paquete [web.actions].
- en [5]: la capa [metier] simulada
- en [6]: los archivos utilizados. Aquí se encuentran los archivos de las diferentes herramientas utilizadas: Spring, Tiles, Struts 2, el complemento de integración Struts 2 / Spring y el complemento de integración Struts 2 / Tiles.
- en [7]: el archivo de la capa [metier, dao, jpa] real. Nos permite acceder a las entidades JPA, a la interfaz [IMetier] y a las clases [FeuilleSalaire] y [ElementsSalaire]. De hecho, nuestra clase [MetierSimule] utiliza todos estos elementos.
19.3. Configuración del proyecto
El proyecto se configura mediante diferentes archivos:
- [web.xml], que configura la aplicación web
- [struts.xml], que configura el marco Struts
- [applicationContext.xml], que configura el marco Spring
- [tiles.xml], que configura el marco Tiles
19.3.1. Configuración de la aplicación web
El archivo [web.xml] es el siguiente:
<?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>
<!-- Mosaicos -->
<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>
- líneas 13-20: configuran el filtro de Struts 2 – ya visto
- líneas 22-24: configuran el listener de Spring – ya visto
- líneas 9-11: configuran el listener de Tiles. La clase [org.apache.struts2.tiles.StrutsTilesListener] se instanciará al iniciar la aplicación web. A continuación, utilizará su archivo de configuración, el cual se define en las líneas 5 a 8. Por lo tanto, el archivo de configuración de Tiles es el archivo [WEB-INF/tiles.xml].
Al final, al iniciar la aplicación Struts, se instancian tres clases:
- una para el filtro de Struts 2. Esta es la que representa la «C» de MVC.
- otra para el listener de Spring. Spring utilizará el archivo [applicationContext.xml] para instanciar las capas [métier, dao, jpa] de la aplicación. Spring también instanciará, como en un ejemplo anterior, una clase [Config] que contendrá los datos de ámbito Application. Por último, Spring inyectará en cada acción de Struts que lo necesite una referencia a esta instancia única [Config].
- Otra para el listener de Tiles. Este marco se encargará de la gestión de las vistas. Volveremos a ello pronto.
19.3.2. Configuración del marco de trabajo Struts
El marco Struts se configura mediante el siguiente archivo [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>
<!-- internacionalización -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- integración de Spring -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- acciones de Struts / Tiles -->
<package name="default" namespace="/" extends="tiles-default">
<!-- acción predeterminada -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- acción de formulario -->
<action name="Formulaire" class="web.actions.Formulaire" method="input">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
</action>
<!-- acción 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>
<!-- acción 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>
<!-- acción RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- acción VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
<!-- acción 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>
<!-- acción 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>
Comentaremos las diferentes acciones de Struts a medida que las vayamos analizando. Por ahora, cabe destacar los siguientes puntos:
- línea 8: define el archivo de mensajes
- línea 10: define el modo de inyección de los beans de Spring en las acciones de Struts. La inyección se realiza según el nombre del bean. El campo de la acción de Struts que debe ser inicializado por Spring debe llevar el nombre del bean que se va a inyectar.
- línea 25: define la vista que se mostrará para la clave de navegación success de la acción [Formulaire]. Vemos que el resultado <result> tiene un atributo type='tiles' que no conocemos. Ya conocíamos el tipo redirect, que permite redirigir al cliente a una vista. Aquí, la vista de tipo tiles es administrada por el marco Tiles. El tipo tiles está definido en el archivo [struts-plugin.xml] del archivo [struts2-tiles-plugin-2.2.3.1.jar]:
- líneas 3-5: la definición del tipo de resultado tiles.
- línea 2: este tipo se define en el paquete [tiles-default], que extiende el paquete [struts-default].
- línea 14: define el paquete [default], en el que se encontrarán todas las acciones de la aplicación. Para aprovechar la definición del tipo de vista tiles, el paquete extiende [tiles-default].
19.3.3. Configuración del marco Spring
El marco Spring se configura mediante el siguiente archivo [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">
<!-- capas de aplicación -->
<!-- web -->
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- negocio -->
<bean id="metier" class="metier.MetierSimule"/>
</beans>
- línea 13: la capa [metier] simulada, instanciada por la clase [metier.MetierSimule]
- líneas 9-11: configuran un bean llamado config. Al igual que en un ejemplo analizado anteriormente, este bean servirá para encapsular la información del ámbito Application. La clase asociada a este bean es la siguiente clase [Config]:
package web;
import java.util.List;
import jpa.Employe;
import metier.IMetier;
public class Config {
// capa de negocio inicializada por Spring
private IMetier metier;
// lista de empleados
private List<Employe> employes;
// errores
private Exception initException;
// constructor
public Config() {
}
// método de Spring para inicializar el objeto
public void init() {
// se solicita la lista de empleados
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
// getters y setters
...
}
Volvamos a la configuración del bean config:
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- negocio -->
<bean id="metier" class="metier.Metier">
...
</bean>
En la línea 2, se puede observar que el bean metier de la línea 5 se inyecta (ref) en el campo denominado metier (name) del objeto [Config]. El bean metier es una referencia en la capa [metier]:
![]() |
Para interactuar con la capa [metier], todas las acciones de Struts de la capa [web] necesitarán una referencia a esta. Se puede decir que la referencia a la capa [metier] es un dato de alcance Application. Todas las solicitudes de todos los usuarios la necesitarán. Por eso, colocamos esta referencia en el objeto [Config]. Por otra parte, en la línea 1, la configuración del bean config tiene un atributo init-method. Este atributo designa el método del bean que se debe ejecutar tras la instanciación del bean. Aquí se indica que, tras la instanciación de la clase [web.Config], se debe ejecutar su método init. Este es el siguiente:
// Capa de lógica de negocio inicializada por Spring
private IMetier metier;
// lista de empleados
private List<Employe> employes;
// errores
private Exception initException;
// constructor
public Config() {
}
// método de Spring para inicializar el objeto
public void init() {
// se solicita la lista de empleados
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
Cuando se ejecuta el método init, Spring ya ha instanciado el campo metier de la clase. Por lo tanto, el método init tiene acceso a la capa de negocio de la interfaz [IMetier] (línea 2):
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// obtener la nómina
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// lista de empleados
List<Employe> findAllEmployes();
}
- línea 8: el método permite calcular el salario de un empleado
- línea 10: el método permite obtener la lista de empleados
Se observa que el método init solicita la lista de empleados a la capa [métier]. Esta lista se guarda en el campo de la línea 4. Si se produce una excepción, esta se almacena en el campo de la línea 6.
En resumen, el objeto único [Config] contiene:
- una referencia a la capa [métier]
- la lista de empleados
19.4. Generación de las vistas Tiles
Como vimos en el archivo de configuración de Struts, las vistas serán generadas por el marco Tiles. Solo explicaremos lo estrictamente necesario para desarrollar nuestra aplicación.
Tiles permite generar vistas a partir de una página maestra. Esta, llamada aquí [MasterPage.JSP], será el ensamblaje de los siguientes fragmentos JSP:
Estos fragmentos JSP están definidos en el proyecto NetBeans:
![]() |
El marco Tiles nos permite definir qué fragmentos se insertarán en la página maestra.
La página maestra [MasterPage.JSP] es la siguiente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="styles.css" rel="stylesheet" type="text/css"/>
<title>
<tiles:insertAttribute name="titre" ignore="true" />
</title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<tiles:insertAttribute name="entete" />
<hr/>
<tiles:insertAttribute name="saisie" />
<tiles:insertAttribute name="simulation" />
<tiles:insertAttribute name="exception" />
<tiles:insertAttribute name="erreur" />
<tiles:insertAttribute name="simulations" />
</body>
</html>
La página maestra es un contenedor de fragmentos JSP. En este caso, está compuesta por seis fragmentos, los de las líneas 17 a 23. Al generarse, la página maestra puede contener de 0 a 6 fragmentos ensamblados. Esta generación se rige por el archivo [WEB-INF/tiles.xml], que define las vistas 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>
<!-- la página maestra -->
<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>
<!-- la vista de entrada -->
<definition name="saisie" extends="masterPage">
<put-attribute name="saisie" value="/Saisie.JSP"/>
</definition>
<!-- la vista de simulación -->
<definition name="simulation" extends="saisie">
<put-attribute name="simulation" value="/Simulation.JSP"/>
</definition>
<!-- la vista de simulaciones -->
<definition name="simulations" extends="masterPage">
<put-attribute name="simulations" value="/Simulations.JSP"/>
</definition>
<!-- vista de excepción -->
<definition name="exception" extends="masterPage">
<put-attribute name="exception" value="/Exception.JSP"/>
</definition>
<!-- la vista de error -->
<definition name="erreur" extends="masterPage">
<put-attribute name="erreur" value="/Erreur.JSP"/>
</definition>
</tiles-definitions>
- El archivo anterior define seis vistas Tiles denominadas: masterPage (línea 9), entrada (línea 20), simulación (línea 25), simulaciones (línea 30), excepción (línea 35) y error (línea 40).
- líneas 9-17: definen una vista llamada masterPage (name) y asociada a la página maestra [MasterPage.JSP] (template). Hemos visto que esta página JSP define seis subvistas. Una vista Tiles asociada a la página maestra debe indicar el fragmento JSP asociado a cada una de las seis subvistas. Se observa que algunas subvistas reciben como valor (value) la cadena vacía. Estas subvistas no se incluirán en la página maestra [MasterPage.JSP]. La vista Tiles, denominada masterPage, está compuesta, por lo tanto, únicamente por el subfragmento [Entete.JSP].
- líneas 20-22: definen una vista llamada saisie (name) y que extiende (extends) la vista llamada masterPage vista anteriormente. Esto significa que incorpora todas las definiciones de la vista masterPage. Su definición es equivalente a la siguiente:
<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>
Se observa que está asociada a la página JSP [MasterPage.JSP] y que, por lo tanto, debe definir las seis subvistas de esta página. Se observa que la definición de la línea 9 anula la de la línea 4. La vista Tiles, denominada saisie, está compuesta, por lo tanto, por los fragmentos JSP y [Entete.JSP, Saisie.JSP]
Si seguimos con este razonamiento, obtenemos la siguiente tabla:
vista Tiles | páginas JSP |
19.5. Los archivos de mensajes
La aplicación ha sido internacionalizada. Los mensajes se encuentran en los archivos [messages.properties] y [Formulaire.properties].
El archivo [messages.properties] es el siguiente:
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
# formatos
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# lista de simulaciones
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
# mensaje de error
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# error de conversión
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
El archivo [Formulaire.properties] es el siguiente:
# para que los valores dobles estén en formato local
double.format={0,number,#0.00##}
# mensaje de error
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. La hoja de estilo
Las vistas Tiles utilizan la siguiente hoja de estilo [styles.css]:
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold;;
padding-right: 5px;
padding-left: 5px;
padding-bottom: 5px;
padding-top: 5px;
}
.info{
background-color: #99cc00;;
padding-right: 5px;
padding-left: 5px;
padding-bottom: 5px;
padding-top: 5px;
}
.titreInfos{
background-color: #ffcc00
}
19.7. La vista inicial
Para analizar la aplicación, la presentaremos a partir de las diferentes acciones del usuario. En cada caso, analizaremos la acción Struts que ejecuta dicha acción y la vista Tiles que se envía como respuesta.
En [struts.xml] tenemos las siguientes acciones:
<!-- acción predeterminada -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
<!-- acción Formulario -->
<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>
- líneas 2-8: la acción predeterminada de la aplicación es [Formulaire!input].
La clase [Formulaire] es la siguiente:
package web.actions;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
// configuración inicializada por Spring
private Config config;
// lista de empleados
private List<Employe> employes;
// lista de errores
private List<Erreur> erreurs;
// nómina
private FeuilleSalaire feuilleSalaire;
// entradas
private String comboEmployesValue;
private Double heuresTravaillees;
private Integer joursTravailles;
// sesión
private Map<String, Object> session;
// menú
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
....
}
// cálculo del salario
public String calculSalaire() {
...
}
}
@Override
public void validate() {
...
}
@Override
public void setSession(Map<String, Object> map) {
session = map;
}
// getters y setters
...
}
- línea 4: la acción [Formulaire] implementa la interfaz Preparable. Esta tiene un solo método, el método prepare de la línea 24. Este método se ejecuta una vez antes de cualquier otro método de la acción. Por lo general, sirve para inicializar el modelo de la acción.
El modelo de la acción está formado por las líneas 6 a 21:
- línea 7: el campo config es inicializado por Spring tal como se ha explicado. Da acceso a los datos de ámbito de aplicación:
- una referencia en la capa [métier]
- una referencia a la lista de empleados.
- una referencia a la excepción que pudo haber ocurrido durante la instanciación del objeto [Config]
- línea 9: una lista de empleados. Esta alimentará el menú desplegable de empleados en el fragmento [Saisie.JSP].
- línea 11: una lista de errores. Esta alimentará el fragmento [Erreur.JSP].
- línea 21: la lista de opciones del menú del fragmento [Entete.JSP]
![]() |
En [1], los enlaces del menú que se muestra se controlan mediante el campo menu de la acción [Formulaire].
El método prepare se ejecuta antes que el método input. Es el siguiente:
@Override
public void prepare() throws Exception {
// ¿Error de configuración?
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();
}
}
- línea 4: se recupera la excepción en el objeto [Config] instanciado por Spring
- línea 5: si se produjo una excepción al instanciar el objeto [Config], entonces se inicializa la lista de errores de la línea 11. La clase [Erreur] es la siguiente:
package web.entities;
import java.io.Serializable;
public class Erreur implements Serializable{
public Erreur() {
}
// campos
private String classe;
private String message;
// constructor
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// getters y setters
...
}
La clase sirve para almacenar la pila de excepciones:
- línea 11: la clase de la excepción
- línea 12: el mensaje de la excepción
Volvamos al método prepare:
- línea 13: la lista de empleados del objeto [Config] se almacena en el campo employes de la acción.
Una vez ejecutado el método prepare, se ejecutará a su vez el método input. Este es el siguiente:
@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";
}
}
El método input simplemente configura la lista de opciones del menú que se mostrarán. La clase [Menu] es la siguiente:
package web.entities;
import java.io.Serializable;
public class Menu implements Serializable {
// elementos del menú
private boolean faireSimulation;
private boolean effacerSimulation;
private boolean enregistrerSimulation;
private boolean voirSimulations;
private boolean retourFormulaire;
private boolean terminerSession;
public Menu() {
}
public Menu(boolean faireSimulation, boolean effacerSimulation, boolean enregistrerSimulation, boolean voirSimulations, boolean retourFormulaire, boolean terminerSession) {
this.faireSimulation = faireSimulation;
this.effacerSimulation = effacerSimulation;
this.enregistrerSimulation = enregistrerSimulation;
this.voirSimulations = voirSimulations;
this.retourFormulaire = retourFormulaire;
this.terminerSession = terminerSession;
}
// getters y setters
...
}
- líneas 8-13: hay 6 enlaces posibles en el menú
- líneas 18-25: el constructor de la clase permite establecer qué enlaces deben mostrarse y cuáles no.
Los enlaces del menú se muestran en [Entete.JSP], fragmento JSP presente en todas las vistas Tiles. Cada acción contará con un campo menu para controlar la visualización del menú de [Entete.JSP].
Volvamos al método 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";
}
}
- líneas 3-6: si la lista de errores está vacía, se mostrará el menú [Faire la simulation, Voir les simulations, Terminer la session] y se devolverá la clave input.
- líneas 9-10: si la lista de errores no está vacía, el menú estará vacío y se devolverá la clave exception.
Volvamos a la configuración de la acción [Formulaire] en [struts.xml]:
<!-- acción Formulario -->
<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>
- línea 5: la clave input muestra la vista Tiles denominada saisie
- línea 4: la clave exception muestra la vista Tiles denominada exception
Comencemos con la vista Tiles llamada saisie. Está compuesta por los fragmentos JSP, [Entete.JSP] y [Saisie.JSP].
El fragmento [Entete.JSP] es el siguiente:
![]() |
Su código es el siguiente:
<%@ 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>
- líneas 8-25: visualización de los seis enlaces del menú [Realizar la simulación (líneas 8-10), Borrar la simulación (líneas 11-13), Ver las simulaciones (líneas 14-16), Regresar al formulario (líneas 17-19), Guardar la simulación (líneas 20-22), Finalizar la sesión (líneas 23-25).
- líneas 8, 11, 14, 17, 20, 23: la visualización de los enlaces está controlada por el campo menu de la acción actual.
Cabe señalar que el fragmento [Entete.JSP] muestra una tabla HTML (líneas 4-28), pero no es una página HTML completa. No hay que olvidar que todas las vistas de la aplicación se insertan en la página maestra [MasterPage.JSP] siguiente:
<%@ 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>
El fragmento [Entete.JSP] se inserta en la línea 17, dentro de una página HTML normal.
El fragmento [Saisie.JSP] se inserta en la línea 19. Esta es la siguiente vista:
![]() |
El código del fragmento [Saisie.JSP] es el siguiente:
<%@ 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(){
// al enviar el formulario
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- ingreso de información -->
<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>
- línea 17: el formulario no tiene el atributo action. Por defecto, se utiliza action='Formulaire'.
- línea 18: visualización del menú desplegable de empleados. El contenido del menú (atributo list) lo proporciona el campo employes de la acción actual. El atributo value de las opciones será el n.º SS de los empleados (atributo listKey). El texto que se mostrará para cada opción será el nombre y apellido del empleado (atributo listValue). El n.º SS del empleado seleccionado en el menú desplegable se registrará en el campo [Formulaire].comboEmployesvalue (atributo name).
- línea 19: campo para ingresar las horas trabajadas. El valor que se muestra (atributo value) es el del campo heuresTravaillees de la acción [Formulaire] en el siguiente formato (Formulaire.properties):
double.format={0,number,#0.00##}
El valor se registrará en el campo [Formulaire].heuresTravaillees (atributo name).
- línea 20: campo de entrada de días trabajados. El valor que se muestra (atributo value) es el del campo joursTravailles de la acción [Formulaire].
El valor se registrará en el campo [Formulaire].joursTravailles (atributo name).
Al final, la vista Tiles saisie que se muestra al inicio cuando no hay errores es la siguiente:
![]() |
Volvamos a la configuración de la acción [Formulaire]:
<!-- acción Formulario -->
<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>
Hemos visto que la acción [Formulaire].input también puede generar la clave exception de la línea 4. En este caso, se muestra la vista Tiles denominada exception. Esta está compuesta por los fragmentos [Entete.JSP] y [Exception.JSP]. Ya hemos presentado el fragmento [Entete.JSP]. El fragmento [Exception.JSP] es el siguiente:
![]() |
Esta es la página de inicio de la versión 2 de la aplicación cuando el Sgbd no se ha iniciado. El código JSP del fragmento [Erreur.JSP] es el siguiente:
<%@ 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>
- líneas 10-14: un iterador sobre la colección List<Error> errores de la acción [Formulaire]. Recordemos que, en caso de error, allí se había almacenado una pila de excepciones.
19.8. Realizar una simulación
Una vez obtenida la vista inicial, se puede realizar un cálculo de salario a través del enlace [Faire une simulation].
19.8.1. Validación de los datos ingresados
Consideremos la siguiente secuencia:
![]() |
- en [1], una entrada errónea
- en [2], la respuesta enviada.
Consideremos la configuración de la acción [Formulaire] en [struts.xml]:
<!-- acción del formulario -->
<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>
Sabemos que, en caso de error de validación, el interceptor de validación devuelve la clave input. Por lo tanto, es la vista Tiles saisie la que se devuelve. El proceso de validación hace que los campos con errores vayan acompañados de mensajes de error.
La validación de la acción [Formulaire] se lleva a cabo mediante el siguiente archivo [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>
- Las líneas 6 a 20 verifican que el campo heuresTravaillees sea un número real dentro del intervalo [0,300].
- Las líneas 22 a 36 verifican que el campo joursTravailles sea un número entero dentro del intervalo [0,31].
Volvamos a la configuración de la acción [Formulaire] en [struts.xml]:
<!-- Acción de formulario -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="input" type="tiles">saisie</result>
</action>
Sabemos que, en caso de error de validación, el interceptor de validación devuelve la clave input. Por lo tanto, es la vista Tiles saisie la que se devuelve.
Recordemos que esta se compone de los fragmentos [Entete.JSP] y [Saisie.JSP], donde [Entete.JSP] contiene un título y una selección de opciones, y [Saisie.JSP], el formulario de entrada. En caso de errores de entrada, el proceso de validación hace que los campos con errores se acompañen de mensajes de error y, además, muestren su valor incorrecto. El fragmento [Entete.JSP] no interviene en absoluto en el proceso de validación. Veamos su código:
<%@ 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>
Los seis enlaces se configuran mediante el campo menu del modelo (líneas 8, 11, 14, 17, 20, 23). Cuando hay un error, la acción no actualiza este modelo, por lo que aparece una página sin menú. Para solucionar este problema, la clase [Formulaire] cuenta con el siguiente método 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() {
...
}
// cálculo del salario
public String calculSalaire() {
...
}
@Override
public void validate() {
// ¿Hay errores?
if (!getFieldErrors().isEmpty()) {
// menú
menu = new Menu(true, false, false, true, false, true);
}
}
// getters y setters
...
}
- línea 27: sabemos que, cuando está presente, el proceso de validación ejecuta el método validate. Aprovechamos esto para actualizar el menú de la línea 4, que forma parte de la plantilla del fragmento [Entete.JSP].
- líneas 29-32: si se han producido errores de validación, se configura el menú para volver a mostrar la vista Tiles saisie. Si no se han producido errores de validación, no se realiza ninguna acción. El método calculSalaire se encarga entonces de crear la plantilla de la vista que se mostrará.
19.8.2. El cálculo del salario
Volvamos al código JSP de la cabecera:
<%@ 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>
- línea 9: cuando el usuario hace clic en el enlace [Faire la simulation], se ejecuta la función de JavaScript doSimulation. Esta función está definida en el fragmento [Saisie.JSP]:
<script language="javascript" type="text/javascript">
function doSimulation(){
// enviamos el formulario
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- ingreso de información -->
<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>
- línea 14: se enviará un campo oculto llamado action a la acción [Formulaire]. Este campo nos permitirá especificar la acción y el método que deben ejecutarse en el campo POST del formulario. Quizás recordemos de los primeros ejemplos que estos se pueden especificar en un parámetro llamado acción:Acción!método. No importa el valor de este parámetro; basta con que esté presente.
- líneas 2-6: la función de JavaScript que se ejecuta cuando el usuario hace clic en el enlace [Faire la simulation] del fragmento [Entete.JSP].
- línea 4: se modifica el atributo name del campo oculto action. Se asegura que tenga el formato action:Action!método que espera Struts.
- línea 5: se envía el formulario denominado Saisie de la línea 5. De este modo, se envía la siguiente cadena de parámetros:
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.
El método [Formulaire].calculSalaire es el siguiente:
// cálculo del salario
public String calculSalaire() {
try {
// cálculo del salario
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// se guarda la simulación en la sesión
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menú
menu = new Menu(true, true, true, true, false, true);
// fin
return "simulation";
} catch (Throwable th) {
...
}
}
- línea 5: se solicita el cálculo de la nómina a la capa [métier]
- línea 7: se agrega un objeto de tipo Simulation a la sesión del usuario. De hecho, podría ser necesario para una consulta posterior. La clase [Simulation] es la siguiente:
package web.entities;
import java.io.Serializable;
import metier.FeuilleSalaire;
public class Simulation implements Serializable{
public Simulation() {
}
// campos de una simulación
private Integer num;
private FeuilleSalaire feuilleSalaire;
private String heuresTravaillées;
private String joursTravaillés;
// generador
public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire feuilleSalaire){
this.setNum(num);
this.setFeuilleSalaire(feuilleSalaire);
this.setHeuresTravaillées(heuresTravaillées);
this.setJoursTravaillés(joursTravaillés);
}
public double getIndemnites(){
return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
}
// getters y setters
...
}
- línea 12: el número de la simulación. Se incrementa con cada nueva simulación registrada.
- línea 13: la nómina del empleado
- línea 14: su número de horas trabajadas
- línea 15: su número de días trabajados
- línea 25: el método getIndemnites devuelve el total de las indemnizaciones del empleado
Veremos que la clase [Simulation] es la plantilla del fragmento [Simulations.JSP], que presenta todas las simulaciones realizadas.
Volvamos al método [Formulaire].calculSalaire:
// cálculo del salario
public String calculSalaire() {
try {
// cálculo del salario
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// se incluye la simulación en la sesión
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// menú
menu = new Menu(true, true, true, true, false, true);
// fin
return "simulation";
} catch (Throwable th) {
...
}
- línea 9: actualización del menú
- línea 11: devolución de la tecla de navegación simulation.
Regreso a la configuración de la acción [Formulaire]:
<!-- Acción del formulario -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="simulation" type="tiles">simulation</result>
</action>
La línea 4 muestra que la clave de navegación simulation hace que se muestre la vista Tiles denominada simulation. Esta está compuesta por los siguientes fragmentos JSP: [Entete, Saisie, Simulation].
La vista generada es la siguiente:
![]() |
- en [1], el fragmento [Entete.JSP]
- en [2], el fragmento [Saisie.JSP]
- en [3], el fragmento [Simulation.JSP]. Cabe recordar que la nómina presentada es la nómina ficticia generada por la capa [metier].
Los dos primeros fragmentos ya se han presentado. El fragmento [Simulation.JSP] es el siguiente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<hr/>
<!-- información del empleado -->
<span class="titreInfos">
<s:text name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
<!-- línea 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>
<!-- línea 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>
<!-- línea 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>
<!-- línea 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>
<!-- información sobre cotizaciones -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.cotisations"/>
</span>
<br/><br/>
<table>
<!-- línea 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>
<!-- línea 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>
<!-- información sobre indemnizaciones -->
<br/>
<span class="titreInfos">
<s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
<!-- línea 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>
<!-- línea 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>
<!-- información sobre el salario -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
<!-- línea 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>
<!-- línea 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>
<!-- Salario neto-->
<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>
Es largo... pero funcionalmente sencillo. Este fragmento muestra las diferentes propiedades del campo [Formulaire].feuilleSalaire, que representa la nómina del empleado.
Volvamos al método [Formulaire].calculSalaire:
// cálculo del salario
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";
}
}
El cálculo del salario puede fallar. Esto ocurriría, por ejemplo, si se interrumpiera la conexión con el SGBD. En ese caso, se maneja la excepción que se produce. Ya nos hemos encontrado con este caso al analizar el método [Formulaire].input.
- líneas 7-11: se crea una lista de objetos Erreur a partir de la pila de excepciones
- línea 13: se establece el menú
- línea 14: se devuelve la clave exception.
La clave exception mostrará la vista Tiles exception:
<!-- Acción Formulario -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="exception" type="tiles">exception</result>
...
</action>
Esta vista Tiles ya se ha presentado. Tiene el siguiente aspecto:
![]() |
19.9. Guardar una simulación
Después de realizar una simulación, es posible que el usuario desee guardarla en la sesión.
![]() |
![]() |
- En [1], se guarda la simulación
- en [2], la respuesta que presenta la lista de simulaciones ya realizadas, a la que se agrega la nueva simulación
El enlace [Enregistrer la simulation] se encuentra en el fragmento [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>
Se observa que al hacer clic en el enlace se ejecuta la acción [EnregistrerSimulation]. Esta está configurada en el archivo [struts.xml] de la siguiente manera:
<!-- Acción 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>
- línea 1: la acción [EnregistrerSimulation] está asociada a la clase [Enregistrer] y a su método execute.
La clase [Enregistrer] es la siguiente:
package web.actions;
...
public class Enregistrer extends ActionSupport implements SessionAware {
// sesión
private Map<String, Object> session;
// menú
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// ejecución de la acción
public String execute() {
// se recupera la última simulación de la sesión
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
...
}
// getters y setters
...
}
- línea 4: dado que la acción debe tener acceso a la sesión, implementa la interfaz SessionAware.
- línea 7: la sesión
- línea 9: el menú
Cuando se instanció la acción [Enregistrer], se ejecutó su método execute. Recordemos que su función es colocar en la sesión la última simulación. Esta se agregará a la lista de simulaciones ya realizadas, que también se conserva en la sesión.
- línea 19: se recupera de la sesión la última simulación que se haya colocado allí.
- líneas 20-22: si no se encuentra, es probable que la sesión haya caducado. De hecho, esta solo dura un tiempo determinado que se puede establecer en el archivo [web.xml], el cual configura la aplicación.
- línea 21: se devuelve la clave error.
Regreso a la configuración de la acción [EnregistrerSimulation]:
<!-- acción 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>
Se observa que la clave error (línea 3) muestra la vista de mosaicos de denominada erreur. Esta se compone de los fragmentos [Entete.JSP] y [Erreur.JSP] y tiene el siguiente aspecto:
![]() |
El fragmento [Erreur.JSP] es el siguiente:
<%@ 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>
Regreso al método [Enregistrer].execute:
// ejecución de la acción
public String execute() {
// se recupera la última simulación de la sesión
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
// se recupera el número de la última simulación
Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
if (numDerniereSimulation == null) {
numDerniereSimulation = 0;
}
// se incrementa
numDerniereSimulation++;
// se vuelve a ingresar el nuevo número en la sesión
session.put("numDerniereSimulation", numDerniereSimulation);
// se recupera la lista de simulaciones
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
simulations = new ArrayList<Simulation>();
session.put("simulations", simulations);
}
// se le agrega la simulación actual
simulation.setNum(numDerniereSimulation);
simulations.add(simulation);
// se muestra la lista de simulaciones
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
- líneas 9-16: las diferentes simulaciones se numeran a partir del 1. El último número asignado se guarda en la sesión bajo la clave numDerniereSimulation. El código de las líneas 9-16 consiste en recuperar esta clave e incrementar el valor asociado a ella.
- líneas 18-22: la lista de simulaciones se guarda en la sesión asociada a la clave simulations. Las líneas 18-22 consisten en recuperar esta lista si existe o crearla si no existe.
- líneas 24-25: una vez obtenida la lista de simulaciones, se le agrega la simulación actual (línea 25). Previamente, a la simulación actual se le ha asignado un número (línea 24).
- línea 27: se establece el menú que se mostrará
- línea 28: se devuelve la clave de navegación simulations.
Regreso a la configuración de la acción [EnregistrerSimulation] en [struts.xml]:
<!-- acción 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>
En la línea 4, la clave simulations provoca que se muestre la vista de mosaicos de denominada simulations. Esta vista está compuesta por los fragmentos [Entete.JSP] y [Simulations.JSP]. La vista que se muestra es la siguiente:
![]() |
- en [1], el fragmento [Entete.JSP] que ya conocemos bien.
- en [2], el fragmento [Simulations.JSP]
El fragmento [Simulations.JSP] es el siguiente:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- lista de simulaciones vacía -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- lista de simulaciones no vacía -->
<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"/>">Eliminar</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- líneas 5-7: si no hay simulaciones en la sesión, entonces se muestra la siguiente vista:
![]() |
- líneas 13-21: visualización de los encabezados de las columnas de la tabla
- líneas 23-55: iteración sobre la lista de simulaciones encontradas en la sesión
- líneas 24-26: creación de un URL denominado URL (atributo id). El enlace HTML generado por este URL es el siguiente:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>
Se observa que el enlace apunta a la acción [SupprimerSimulation] con el parámetro id, que representa el número de la simulación que se debe eliminar de la lista.
- líneas 28-54: en cada iteración sobre la lista de simulaciones, se muestran las propiedades de la simulación actual.
![]() |
19.10. Eliminar una simulación
Es posible que el usuario desee eliminar una simulación de la lista de simulaciones:
![]() |
![]() |
- En [1], se elimina la simulación n.º 1
- en [2], se ha eliminado la simulación n.º 1
El enlace [Retirer] se encuentra en el fragmento [Simulations.JSP] que ya hemos analizado:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- lista de simulaciones vacía -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- lista de simulaciones no vacía -->
<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"/>">Eliminar</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- líneas 16-18: generan el enlace 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>
donde num es el número de la simulación que se va a eliminar.
La acción [SupprimerSimulation] se define de la siguiente manera en el archivo [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>
<!-- internacionalización -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- integración de Spring -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- acciones de Struts /Tiles -->
<package name="default" namespace="/" extends="tiles-default">
...
<!-- acción RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
...
</package>
<!-- Agregar paquetes aquí -->
</struts>
- línea 16: la acción [SupprimerSimulation] está asociada a la clase [Supprimer]. Como no se especifica ningún método, se ejecutará su método execute. La clase [Supprimer] es la siguiente:
package web.actions;
...
public class Supprimer extends ActionSupport implements SessionAware {
// sesión
private Map<String, Object> session;
// ID de la simulación que se va a eliminar
private String id;
// menú
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// ejecución de la acción
public String execute() {
// se recuperan las simulaciones de la sesión
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
// caso anormal: la sesión debe haber caducado
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// prueba de identificación
int num = 0;
boolean erreur = false;
try {
num = Integer.parseInt(id);
erreur = num <= 0;
} catch (NumberFormatException ex) {
// anormal
erreur = true;
}
// ¿Error?
if (erreur) {
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// se busca la simulación que se va a eliminar
for (int i = 0; i < simulations.size(); i++) {
if (num == simulations.get(i).getNum()) {
simulations.remove(i);
break;
}
}
// se muestra la lista de simulaciones
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
// getters y setters
...
}
- línea 4: la acción [Supprimer] implementa la interfaz [SessionAware] para tener acceso a la sesión.
- línea 7: la sesión
- línea 9: el número de la simulación que se va a eliminar. Recordemos que llegamos a la instanciación de la clase [Supprimer] a través de 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>
donde num es el número de la simulación que se va a eliminar. Este número se almacenará en el campo id de la línea 9.
- línea 11: el menú de la vista que se mostrará en respuesta a la solicitud
- línea 19: el método execute que generará la respuesta a la consulta.
- línea 21: se recupera la lista de simulaciones ya realizadas en la sesión
- líneas 22-26: si no se recupera esta lista en la sesión, probablemente significa que la sesión ha caducado. Ya nos hemos encontrado con este caso. Se devuelve la clave erreur, que muestra la vista Tiles erreur:
<!-- acción RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
...
</action>
La vista Tiles erreur se presentó en el párrafo 19.9.
- líneas 28-36: se verifica que la cadena id de la línea 9 represente efectivamente un número entero >0.
- líneas 38-40: si no es así, se devuelve nuevamente la clave erreur, lo que hará que se muestre la vista Tiles erreur
- líneas 43-48: se busca la simulación que se va a eliminar en la lista de simulaciones. Si se encuentra, se elimina.
- línea 50: se actualiza el menú para la vista Tiles simulations.
- línea 51: se devuelve la clave simulations. Esta hará que se muestre la vista Tiles simulations:
<!-- acción RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
...
<result name="simulations" type="tiles">simulations</result>
</action>
La vista Tiles simulations se presentó en el párrafo 19.9.
19.11. Regresar al formulario
Desde la vista Tiles simulations, el usuario puede regresar al formulario:
![]() |
![]() |
- en [1], se hace clic en el enlace para regresar al formulario
- en [2], aparece un formulario en blanco
El enlace [Retour au formulaire de simulation] se define en el fragmento [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>
- línea 10: el enlace apunta a la acción [RetourFormulaire]. Esta se define de la siguiente manera en el archivo [struts.xml]:
<!-- acción RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
Se observa que esta acción no está asociada a ninguna clase. Simplemente redirige el navegador del cliente a la acción [/Formulaire!input]. Nos encontramos entonces en el mismo caso que al mostrar la vista inicial explicada en el párrafo 19.7. Por lo tanto, volvemos a encontrar esta vista inicial [2].
19.12. Ver la lista de simulaciones
Desde las vistas Tiles simulation o saisie, el usuario puede solicitar ver las simulaciones:
![]() |
![]() |
- en [1], se hace clic en el enlace [Voir les simulations]
- en [2], se encuentra la lista de simulaciones
El enlace [Voir les simulations] se define en el fragmento [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>
- línea 10: el enlace [Voir les simulations] llama a la acción [VoirSimulations]. Esta se define de la siguiente manera en el archivo [struts.xml]:
<!-- acción VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
La acción [VoirSimulations] está asociada a la clase [Voir] sin especificar un método. Por lo tanto, se ejecutará el método [Voir].execute. La clase [Voir] es la siguiente:
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);
// getters y setters
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
}
}
La acción [Voir] solo hace una cosa: configurar el menú para la vista «Tiles simulations» (línea 8). No existe el método execute. Por lo tanto, se ejecutará el de la clase padre [ActionSupport]. Sabemos que no hace nada más que devolver la clave success.
Volvamos a la acción en [struts.xml]:
<!-- acción VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
En la línea 3, vemos que la clave success lleva a la visualización de la vista Tiles simulations. Esta se describió en la página 161.
19.13. Borrar la simulación actual
Desde la vista de mosaicos simulation, el usuario puede solicitar que se borre la simulación actual:
![]() |
![]() |
- en [1], se borra la simulación actual
- en [2], se muestra el formulario de entrada en blanco
El enlace [Effacer la simulation] se define en el fragmento [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>
En la línea 10, vemos que el enlace [Effacer la simulation] activa la acción [Formulaire!input]. Sabemos que esta acción lleva a la vista inicial [2].
19.14. Finalizar la sesión actual
Desde todas las vistas de mosaicos, el usuario puede solicitar que se termine la sesión:
![]() |
![]() |
![]() |
- En [1], se inicia desde la vista de simulaciones y se cierra la sesión
- en [2], se muestra el formulario de entrada en blanco. Se solicita ver las simulaciones.
- En [3], la lista de simulaciones ahora está vacía.
El enlace [Terminer la session] se define en el fragmento [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>
En la línea 10, se observa que el enlace [Terminer la session] activa la acción [TerminerSession]. Esta se define de la siguiente manera en el archivo [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>
- línea 1: se observa que se instanciará la clase [Terminer] y se ejecutará su método execute.
- líneas 2-5: tras la ejecución del método [Terminer].execute, se redirigirá a la vista inicial de entrada de datos. Esto explica la pantalla n.º 2.
La clase [Terminer] es la siguiente:
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 {
// sesión
private Map<String, Object> session;
@Override
public String execute() {
// cancelación de la sesión actual
session.clear();
return SUCCESS;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
La función de la acción [Terminer] es borrar los atributos de la sesión actual.
- línea 7: la acción [Terminer] implementa la interfaz [SessionAware] para tener acceso a la sesión.
- línea 10: el diccionario de la sesión
- línea 13: el método execute que se ejecuta
- línea 15: vacía el diccionario de la sesión. Por lo tanto, la lista de simulaciones que se encuentra en la sesión desaparecerá. Esto explica la pantalla n.º 3.
- línea 16: devuelve la clave success, que, como hemos visto, mostrará la vista Tiles saisie [2].
19.15. Conclusion
Hemos comentado en detalle la versión 1 de nuestro caso de estudio, que trabaja con una capa simulada [metier]:
![]() |
Ahora solo nos queda «conectar» la capa de negocio real a la capa [web].






























