19. Caso práctico – versión 1
19.1. La capa [metier] simulada
Volvamos a la arquitectura de la aplicación que estamos construyendo:
![]() |
Disponemos del 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 framework Struts.
Para simplificar las pruebas de nuestra aplicación en desarrollo, vamos a crear una capa de negocio simulada que respetará la interfaz de la capa [metier]. La arquitectura quedará de la siguiente manera:
![]() |
Vamos a desarrollar la capa [web] con la capa [métier] simulada. Las pruebas serán más sencillas de realizar, ya que ya no hay ninguna base de datos en la arquitectura. Gracias a Spring y al uso de interfaces, sustituir 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 el 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 NetBeans
El proyecto 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 framework 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 Struts se encuentran en el paquete [web.actions].
- en [5]: la capa [metier] simulada
- en [6]: los archivos utilizados. Se encuentran los archivos de las diferentes herramientas utilizadas: Spring, Tiles, Struts 2, el plugin de integración Struts 2 / Spring, el plugin 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, todos estos elementos son utilizados por nuestra clase [MetierSimule].
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 framework Struts
- [applicationContext.xml], que configura el framework Spring
- [tiles.xml], que configura el framework 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 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. Este se define en las líneas 5-8. El archivo de configuración de Tiles es, por tanto, el archivo [WEB-INF/tiles.xml].
Al final, al iniciar la aplicación Struts, se instancian tres clases:
- una para el filtro Struts 2. Es esta la que se encarga de 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 Struts que lo necesite una referencia a esta única instancia [Config].
- Otra para el listener Tiles. Este framework se encargará de la gestión de las vistas. Volveremos sobre ello en breve.
19.3.2. Configuración del marco Struts
El framework 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 Struts /Tiles -->
<package name="default" namespace="/" extends="tiles-default">
<!-- acción por defecto -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- acción 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 estudiemos. 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 en función del nombre del bean. El campo de la acción de Struts que debe inicializar Spring debe tener el mismo nombre que el 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 desconocemos. Conocíamos el tipo redirect, que permite redirigir al cliente a una vista. Aquí, la vista de tipo tiles es gestionada por el marco Tiles. El tipo tiles se define en el archivo [struts-plugin.xml] del archivo [struts2-tiles-plugin-2.2.3.1.jar]:
<struts>
<package name="tiles-default" extends="struts-default">
<result-types>
<result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult"/>
</result-types>
</package>
</struts>
- 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 amplía 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 amplía [tiles-default].
19.3.3. Configuración del framework 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 denominado config. Al igual que en un ejemplo estudiado anteriormente, este bean servirá para encapsular la información de á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 Spring de inicialización del 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 ver 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 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 ámbito 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 negocio inicializada por Spring
private IMetier metier;
// lista de empleados
private List<Employe> employes;
// errores
private Exception initException;
// constructor
public Config() {
}
// método Spring de inicialización del 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 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 almacena 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 vistas Tiles
Como hemos visto en el archivo de configuración de Struts, las vistas serán generadas por el framework Tiles. Solo explicaremos lo estrictamente necesario para escribir nuestra aplicación.
Tiles permite generar vistas a partir de una página maestra. Esta, denominada aquí [MasterPage.jsp], será el ensamblaje de los siguientes fragmentos JSP:
Estos fragmentos JSP se definen 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 entre 0 y 6 fragmentos. 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>
<!-- vista de entrada -->
<definition name="saisie" extends="masterPage">
<put-attribute name="saisie" value="/Saisie.jsp"/>
</definition>
<!-- vista de simulación -->
<definition name="simulation" extends="saisie">
<put-attribute name="simulation" value="/Simulation.jsp"/>
</definition>
<!-- 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), 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á constituida, por tanto, únicamente por el subfragmento [Entete.jsp].
- líneas 20-22: definen una vista llamada saisie (name) y que amplía (extends) la vista denominada masterPage vista anteriormente. Esto significa que retoma 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 dicha 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 tanto, por los fragmentos JSP [Entete.jsp, Saisie.jsp]
Si continuamos con este razonamiento, obtenemos la siguiente tabla:
vista Tiles | páginas JSP |
19.5. Los archivos de mensajes
La aplicación se ha internacionalizado. 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 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 hoja de estilo [styles.css] siguiente:
.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 por defecto -->
<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 solo tiene un método, el método prepare de la línea 24. Este método se ejecuta una vez antes de cualquier 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-21:
- línea 7: Spring inicializa el campo config tal y como se ha explicado. Este campo permite acceder a los datos del ámbito de la aplicación:
- una referencia en la capa [métier]
- una referencia a la lista de empleados
- una referencia a la excepción que pudo producirse durante la instanciación del objeto [Config]
- línea 9: una lista de empleados. Esta alimentará el cuadro combinado 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ú mostrado 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 ha producido una excepción en la instanciación del objeto [Config], 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 se limita a establecer la lista de opciones de menú que se deben mostrar. 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 los enlaces que deben mostrarse y los que no.
Los enlaces del menú se muestran en [Entete.jsp], fragmento JSP presente en todas las vistas Tiles. Cada acción tendrá 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 por la vista Tiles denominada 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 simulaciones (líneas 14-16), Volver 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 aquí 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. Se trata de 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(){
// enviar el formulario
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- introducción de datos -->
<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, tenemos action='Formulaire'.
- línea 18: visualización del menú desplegable de empleados. El contenido del menú (atributo list) se obtiene del 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 muestra para cada opción será el nombre y los apellidos del empleado (atributo listValue). El n.º SS del empleado seleccionado en el menú desplegable se introducirá en el campo [Formulaire].comboEmployesvalue (atributo name).
- línea 19: campo de introducción de las horas trabajadas. El valor mostrado (atributo value) es el del campo heuresTravaillees de la acción [Formulaire] con 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 introducción de días trabajados. El valor mostrado (atributo value) es el del campo joursTravailles de la acción [Formulaire].
El valor se registrará en el campo [Formulaire].joursTravailles (atributo name).
Finalmente, 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 podía generar la clave exception de la línea 4. En este caso, se muestra la vista Tiles denominada exception. Esta se compone de 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<Erreur> de errores de la acción [Formulaire]. Recordemos que, en caso de error, se había almacenado allí una pila de excepciones.
19.8. Realizar una simulación
Una vez obtenida la vista de inicio, se puede realizar un cálculo de salario a través del enlace [Faire une simulation].
19.8.1. Validación de los datos introducidos
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 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 erróneos 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-20 comprueban que el campo heuresTravaillees es un número real dentro del intervalo [0,300].
- Las líneas 22-36 comprueban 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 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 introducción de datos. En caso de errores de introducción de datos, el proceso de validación hace que los campos erróneos vayan acompañados de mensajes de error y muestren además su valor erróneo. 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 se produce un error, la acción no actualiza este modelo, por lo que se muestra una página sin menú. Para solucionar este problema, la clase [Formulaire] tiene 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 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 el modelo de la vista que se va a mostrar.
19.8.2. El cálculo del salario
Volvamos al código JSP del encabezado:
<%@ 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 JavaScript doSimulation. Esta se define en el fragmento [Saisie.jsp]:
<script language="javascript" type="text/javascript">
function doSimulation(){
// se envía el formulario
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- introducción de datos -->
<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 denominado action a la acción [Formulaire]. Este campo nos permitirá especificar la acción y el método que deben ejecutarse en el 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 JavaScript que se ejecuta cuando el usuario hace clic en el enlace [Faire la simulation] del fragmento [Entete.jsp].
- línea 4: se cambia el atributo name del campo oculto action. Se hace de manera que tenga la forma action:Action!método esperada por 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 del empleado seleccionado en el combo
heuresTravaillees: número de horas trabajadas
joursTravailles: número de días trabajados
acción:Formulario!calculSalaire: los elementos anteriores se enviarán a la acción [Formulaire] y, a continuación, se ejecutará el método calculSalaire de dicha acción.
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 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 5: se solicita el cálculo de la nómina a la capa [métier]
- línea 7: se coloca un objeto de tipo Simulation en la sesión del usuario. De hecho, puede 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;
// constructor
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.º 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.
Volver a la configuración de la acción [Formulaire]:
<!-- acción 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 muestra la vista Tiles denominada simulation. Esta se compone de los siguientes fragmentos JSP: [Entete, Saisie, Simulation].
La vista resultante es la siguiente:
![]() |
- en [1], el fragmento [Entete.jsp]
- en [2], el fragmento [Saisie.jsp]
- en [3], el fragmento [Simulation.jsp]. Recordemos 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, en particular, si se rompiera la conexión con el Sgbd. En ese caso, se gestiona la excepción que se produce. Ya nos hemos encontrado con este caso al estudiar 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 fija 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 muestra la lista de simulaciones ya realizadas a la que se añade 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 ha instanciado la acción [Enregistrer], se ejecuta su método execute. Recordemos que su función es introducir en la sesión la última simulación. Esta se añadirá 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 ha colocado en ella.
- 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 fijar en el archivo [web.xml] que configura la aplicación.
- línea 21: se devuelve la clave error.
Volvamos 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>
Volvamos 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 introducir 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 añade 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 añade la simulación actual (línea 25). Previamente, la simulación actual ha recibido un número (línea 24).
- línea 27: se establece el menú que se va a mostrar
- línea 28: se devuelve la clave de navegación simulations.
Volver 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>
Línea 4: la clave simulations provoca la visualización de la vista Tiles denominada simulations. Esta vista está compuesta por los fragmentos [Entete.jsp] y [Simulations.jsp]. La vista mostrada 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, se muestra la siguiente vista:

- líneas 13-21: visualización de los encabezados de las columnas de la tabla
![]()
- líneas 23-55: iterador sobre la lista de simulaciones encontradas en la sesión
- líneas 24-26: creación de una URL denominada url (atributo id). El enlace HTML generado por esta 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 va a eliminar de la lista.
- Líneas 28-54: en cada iteración de la lista de simulaciones, se muestran las propiedades de la simulación actual.

19.10. Eliminar una simulación
El usuario puede querer 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 estudiado:
<%@ 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.º 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 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>
<!-- Añadir 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 anómalo: la sesión debe haber caducado
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// comprobación de id
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 se llega a la instanciación de la clase [Supprimer] a través de la 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.º 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 solicitud.
- 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 apartado 19.9.
- líneas 28-36: se comprueba que la cadena id de la línea 9 representa efectivamente un número entero >0.
- líneas 38-40: si no es así, se devuelve de nuevo la clave erreur, que mostrará 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 apartado 19.9.
19.11. Volver al formulario
Desde la vista Tiles simulations, el usuario puede volver al formulario:
![]() |
![]() |
- en [1], se hace clic en el enlace de vuelta al formulario
- en [2], se encuentra un formulario vacío
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. Se limita a redirigir 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 apartado 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 ningún 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: posicionar 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 conduce a la visualización de la vista Tiles simulations. Esta se ha descrito en la página 156.
19.13. Borrar la simulación actual
Desde la vista Tiles 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 vacío
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 Tiles, el usuario puede solicitar que se cierre la sesión:
![]() |
![]() |
![]() |
- en [1], se parte de la vista de simulaciones y se cierra la sesión
- en [2], se muestra el formulario de entrada vacío. Se solicita ver las simulaciones.
- en [3], la lista de simulaciones está ahora 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, vemos 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 va a instanciar la clase [Terminer] y se va a ejecutar su método execute.
- líneas 2-5: tras la ejecución del método [Terminer].execute, se redirigirá a la vista de entrada inicial. 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 vaciar la sesión actual de sus atributos.
- 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 íntegramente la versión 1 de nuestro caso práctico, que trabaja con una capa [metier] simulada:
![]() |
Ahora solo nos queda «conectar» la capa de negocio real a la capa [web].






















