Skip to content

19. Case Study – Version 1

19.1. The simulated [business] layer

Let’s review the architecture of the application we are building:

We have the [business, DAO, JPA] layer archives, and we have presented the elements of these layers that the [web] layer needs to know. We are ready to write the [web] layer using the Struts framework.

To simplify testing of our application during development, we will create a simulated business layer that will implement the [business] layer interface. The architecture will become as follows:

We will develop the [web] layer using the simulated [business] layer. Testing will be simpler because there is no longer a database in the architecture. Thanks to Spring and the use of interfaces, replacing the simulated [business] layer with the actual [business, DAO, JPA] architecture at a later stage will have no impact on the code of the [web / Struts2] layer. The [web / Struts2] layer we are about to develop can be used as-is.

The simulated [business] layer we will use is as follows:


package business;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jpa.MembershipFee;
import jpa.Employee;
import jpa.Allowance;

public class SimulatedJob implements IJob {

  // list of employees
  private Map<String, Employee> hashEmployees = new HashMap<String, Employee>();
  private List<Employee> listOfEmployees;

  // get the pay stub
  public Payroll calculatePayroll(String SS,
          double hoursWorked, int daysWorked) {
    // retrieve the employee with SS number
    Employee e = hashEmployees.get(SS);
    // return a dummy pay stub
    return new PayStub(e, new Contribution(3.49, 6.15, 9.39, 7.88), new PayElements(100, 100, 100, 100, 100));
  }

  // list of employees
  public List<Employee> findAllEmployees() {
    if (employeesList == null) {
      // create a list of two employees
      employeeList = new ArrayList<Employee>();
      employeeList.add(new Employee("254104940426058", "Jouveinal", "Marie", "5 rue des oiseaux", "St Corentin", "49203", new Compensation(2, 2.1, 2.1, 3.1, 15)));
      listEmployees.add(new Employee("260124402111742", "Laverti", "Justine", "La Br�lerie", "St Marcel", "49014", new Allowance(1, 1.93, 2, 3, 12)));
      // employee dictionary
      for (Employee e : employees) {
        hashEmployees.put(e.getSS(), e);
      }
    }
    // Return the list of employees
    return listEmployees;
  }
}
  • line 11: the [MetierSimule] class implements the [IMetier] interface that the actual [business] layer implements.
  • line 14: a dictionary of employees indexed by their INSEE number
  • line 15: the list of employees
  • lines 27–39: implementation of the findAllEmployees method of the [IMetier] interface.
  • lines 30–33: creation of a list of two employees
  • Lines 34–36: creation of the employee dictionary indexed by INSEE number
  • Lines 18–24: Implementation of the `calculerSalaire` method from the [IMetier] interface. Here, we return a fictitious pay stub.

19.2. The NetBeans project

The NetBeans project is as follows:

  • in [1]:
  • [applicationContext.xml] is the Spring configuration file
  • [tiles.xml] is the configuration file for a framework called Tiles.
  • [web.xml] is the web application configuration file
  • in [2]: the different views of the application
  • in [3]:
  • [messages.properties]: the message file
  • [struts.xml]: the Struts configuration file
  • in [4]: the application's source code. The Struts actions are in the [web.actions] package.
  • in [5]: the simulated [business] layer
  • in [6]: the archives used. Here are the archives for the various tools used: Spring, Tiles, Struts 2, the Struts 2/Spring integration plugin, and the Struts 2/Tiles integration plugin.
  • in [7]: the archive of the actual [business, DAO, JPA] layer. It gives us access to the JPA entities, the [IMetier] interface, and the [PayrollSheet] and [PayrollItems] classes. All of these elements are indeed used by our [SimulatedBusiness] class.

19.3. Project Configuration

The project is configured by various files:

  • [web.xml], which configures the web application
  • [struts.xml], which configures the Struts framework
  • [applicationContext.xml], which configures the Spring framework
  • [tiles.xml], which configures the Tiles framework

19.3.1. Web Application Configuration

The [web.xml] file is as follows:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="pam_struts_01" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Pam</display-name>
    <!-- Tiles -->
  <context-param>
    <param-name> org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG </param-name>
    <param-value>/WEB-INF/tiles.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
  </listener>
    <!-- Struts 2 -->
  <filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
     <!-- Spring -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>
  • lines 13–20: configure the Struts 2 filter – already seen
  • lines 22–24: configure the Spring listener – seen before
  • lines 9–11: configure the Tiles listener. The [org.apache.struts2.tiles.StrutsTilesListener] class will be instantiated when the web application starts. It will then use its configuration file. This is defined by lines 5–8. The Tiles configuration file is therefore the [WEB-INF/tiles.xml] file.

Ultimately, when the Struts application starts, three classes are instantiated:

  • one for the Struts 2 filter. This is the component that handles the "C" in MVC.
  • another one for the Spring listener. Spring will use the [applicationContext.xml] file to instantiate the application’s [business, DAO, JPA] layers. Spring will also instantiate, as in a previous example, a [Config] class that will contain application-scoped data. Finally, Spring will inject a reference to this single [Config] instance into every Struts action that needs it.
  • another one for the Tiles listener. This framework will handle view management. We’ll come back to this shortly.

19.3.2. Struts Framework Configuration

The Struts framework is configured by the following [struts.xml] file:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- Spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- Struts/Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    <!-- default action -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Form</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- Form action -->
    <action name="Form" class="web.actions.Form" method="input">
      <result name="success" type="tiles">submitted</result>
      <result name="exception" type="tiles">exception</result>
    </action>
    <!-- action RunSimulation -->
    <action name="RunSimulation" class="web.actions.Form" method="calculateSalary">
      <result name="success" type="tiles">simulation</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">input</result>
    </action>
    <!-- action SaveSimulation -->
    <action name="SaveSimulation" class="web.actions.Save" method="execute">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- action ReturnForm -->
    <action name="ReturnForm">
      <result type="redirectAction">
        <param name="actionName">Form</param>
        <param name="namespace">/</param>
      </result>
    </action>
   <!-- action ViewSimulations -->
    <action name="ViewSimulations" class="web.actions.View">
      <result name="success" type="tiles">simulations</result>
    </action>
   <!-- action RemoveSimulation -->
    <action name="DeleteSimulation" class="web.actions.Delete" method="execute">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- action EndSession -->
    <action name="EndSession" class="web.actions.End" method="execute">
      <result name="success" type="redirectAction">
        <param name="actionName">Form</param>
        <param name="namespace">/</param>
      </result>
    </action>
  </package>

</struts>

We will discuss the various Struts actions as we go through them. For now, note the following points:

  • line 8: defines the message file
  • line 10: defines how Spring beans are injected into Struts actions. Injection is based on the bean’s name. The Struts action field that needs to be initialized by Spring must have the same name as the bean to be injected.
  • line 25: defines the view to display for the "success" navigation key of the [Form] action. We see that the <result> element has a type='tiles' attribute that we are not familiar with. We were familiar with the redirect type, which allows the client to be redirected to a view. Here, the tiles-type view is managed by the Tiles framework. The tiles type is defined in the [struts-plugin.xml] file within the [struts2-tiles-plugin-2.2.3.1.jar] archive:

<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>
  • Lines 3–5: The definition of the tiles result type.
  • line 2: this type is defined in the [tiles-default] package, which extends the [struts-default] package.
  • line 14: defines the [default] package, which will contain all the application's actions. To take advantage of the tiles view type definition, the package extends [tiles-default].

19.3.3. Spring Framework Configuration

The Spring framework is configured by the following [WEB-INF/applicationContext.xml] file:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
  
  <!-- application layers -->
 
 <!-- web -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="business" ref="business"/>
  </bean>
  <!-- business -->
  <bean id="business" class="business.SimulatedBusiness"/>

</beans>
  • line 13: the simulated [business] layer instantiated by the [business.SimulatedBusiness] class
  • lines 9–11: configure a bean named config. As in a previously studied example, this bean will be used to encapsulate Application-scope information. The class associated with this bean is the following [Config] class:

package web;

import java.util.List;
import jpa.Employee;
import business.IMetier;

public class Config {

  // Business layer initialized by Spring
  private IBusinessModel businessModel;
  // list of employees
  private List<Employee> employees;
  // errors
  private Exception initException;

  // constructor
  public Config() {
  }

  // Spring method for initializing the object
  public void init() {
    // request the list of employees
    try {
      employees = business.findAllEmployees();
    } catch (Exception ex) {
      initException = ex;
    }
  }

  // getters and setters
  ...
}

Let's go back to the config bean configuration:


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

In line 2, we can see that the business bean from line 5 is injected (ref) into the field named business (name) of the [Config] object. The business bean is a reference to the [business] layer:

To interact with the [business] layer, all Struts actions in the [web] layer will need a reference to it. We can say that the reference to the [business] layer is Application-scoped data. All requests from all users will need it. That is why we place this reference in the [Config] object. Additionally, on line 1, the config bean’s configuration includes an init-method attribute. This attribute specifies the bean method to be executed after the bean is instantiated. Here, we specify that after the [web.Config] class is instantiated, its init method must be executed. This method is as follows:


// business layer initialized by Spring
  private IBusinessModel businessModel;
  // list of employees
  private List<Employee> employees;
  // errors
  private Exception initException;

  // constructor
  public Config() {
  }

  // Spring method for initializing the object
  public void init() {
    // Retrieve the list of employees
    try {
      employees = business.findAllEmployees();
    } catch (Exception ex) {
      initException = ex;
    }
  }

When the init method is executed, the business field of the class has been instantiated by Spring. The init method therefore has access to the business layer interface [IMetier] (line 2):


package business;

import java.util.List;
import jpa.Employee;

public interface IBusiness {
  // get the pay stub
  PayStub calculatePayStub(String SS, double hoursWorked, int daysWorked);
  // list of employees
  List<Employee> findAllEmployees();
}
  • Line 8: The method calculates an employee's salary
  • line 10: the method retrieves the list of employees

We can see that the init method requests the list of employees from the [business] layer. This list is stored in the field on line 4. If an exception occurs, it is stored in the field on line 6.

In conclusion, the single [Config] object contains:

  • a reference to the [business] layer
  • the list of employees

19.4. Generating Tiles Views

As we saw in the Struts configuration file, views will be generated by the Tiles framework. We will only explain what is strictly necessary for writing our application.

Tiles allows you to generate views from a master page. This page, called [MasterPage.jsp] here, will be an assembly of the following JSP fragments:

Header.jsp
 
Input.jsp
 
Simulation.jsp
 
Simulations.jsp
 
Exception.jsp
 
Error.jsp
 

These JSP fragments are defined in the NetBeans project:

Image

The Tiles framework allows us to define which fragments will be inserted into the master page.

The master page [MasterPage.jsp] is as follows:


<%@ 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="title" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/resources/standard.jpg"/>">
    <tiles:insertAttribute name="header" />
    <hr/>
    <tiles:insertAttribute name="input" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="error" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

The master page is a container for JSP fragments. Here, it is an assembly of six fragments, those in lines 17 through 23. Upon generation, there may be between 0 and 6 fragments assembled in the master page. This generation is governed by the [WEB-INF/tiles.xml] file, which defines the Tiles views:


<?xml version="1.0" encoding="UTF-8" ?>
 
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
 "http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
 
<tiles-definitions>
 
 <!-- the master page -->
  <definition name="masterPage" template="/MasterPage.jsp">
    <put-attribute name="header" value="/Header.jsp"/>
    <put-attribute name="title"   value="Pam"/>
    <put-attribute name="input" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="error" value=""/>     
  </definition>
 
 <!-- the input view -->
  <definition name="entry" extends="masterPage">
    <put-attribute name="input"    value="/Input.jsp"/>     
  </definition>

  <!-- the simulation view -->
 <definition name="simulation" extends="entry">
    <put-attribute name="simulation"    value="/Simulation.jsp"/>     
  </definition>

 <!-- the simulations view -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"    value="/Simulations.jsp"/>     
  </definition>
  
 <!-- the exception view -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"    value="/Exception.jsp"/>     
  </definition>
 
 <!-- the error view -->
 <definition name="error" extends="masterPage">
    <put-attribute name="error"    value="/Error.jsp"/>     
  </definition>
</tiles-definitions>
  • The file above defines six Tiles views named: masterPage (line 9), input (line 20), simulation (line 25), simulations (line 30), exception (line 35), error (line 40).
  • Lines 9–17: define a view named masterPage (name) and associated with the master page [MasterPage.jsp] (template). We saw that this JSP page defined six subviews. A Tiles view associated with the master page must specify the JSP fragment associated with each of the six subviews. We see that some subviews are assigned the empty string as their value. These subviews will not be included in the master page [MasterPage.jsp]. The Tiles view named masterPage therefore consists solely of the subfragment [Entete.jsp].
  • Lines 20–22: define a view named `saisie` that extends the `masterPage` view defined earlier. This means it inherits all the definitions from the `masterPage` view. Its definition is equivalent to the following:

<definition name="saisie" template="/MasterPage.jsp">
    <put-attribute name="entete" value="/Entete.jsp"/>
    <put-attribute name="title"   value="Pam"/>
    <put-attribute name="entry" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="error" value=""/>
    <put-attribute name="input" value="/Input.jsp"/>     
  </definition>

We can see that it is associated with the JSP page [MasterPage.jsp] and that, as such, it must define the six subviews of this page. We can see that the definition on line 9 overrides the one on line 4. The Tiles view named "saisie" is therefore composed of the JSP fragments [Entete.jsp, Saisie.jsp]

If we continue this line of reasoning, we obtain the following table:

Tiles view
JSP pages
masterPage
Header.jsp
Input
Header.jsp, Input.jsp
simulation
Header.jsp, Entry.jsp, Simulation.jsp
simulations
Header.jsp, Simulations.jsp
exception
Header.jsp, Exception.jsp
error
Header.jsp, Error.jsp

19.5. Message files

The application has been internationalized. The messages are located in the [messages.properties] and [Formulaire.properties] files.

The [messages.properties] file is as follows:


Pam.title=Calculation of child care providers' wages
Pam.Errors.title=The following errors occurred:
Pam.Errors.class=Exception
Pam.Errors.message=Message
Pam.Error.label=The following error occurred
Pam.Input.Hours.label=Hours worked
Pam.Entry.Days.label=Days worked
Pam.Entry.employee=Employee
Pam.BtnSalary.label = Salary
Pam.BtnDelete.label=Delete
Simulation.EmployeeInfo=Employee Information
Simulation.Employee.lastName=Last Name
Simulation.Employee.first_name=First_Name
Simulation.Employee.address=Address
Simulation.Employee.index=Index
Simulation.Employee.city=City
Simulation.Employee.zipCode=Zip Code
Simulation.Info.Contributions=Social Security Contributions
Simulation.Contributions.csgrds=CsgRds
Simulation.Contributions.csgrds=Csgd
Simulation.Contributions.pension=Pension
Simulation.Contributions.secu=Social Security
Form.BenefitsInfo=Benefits
Simulation.Benefits.HourlyWage=Hourly Wage
Simulation.Allowances.MaintenancePerDay=Maintenance/Day
Simulation.Allowances.Meal/Day=Meal/Day
Simulation.Allowances.Paid\u00e9Leave=Paid\u00e9Leave
Simulation.Info.Salary=Salary
Simulation.Salary.BaseSalary=Base Salary
Simulation.Salary.SocialSecurityContributions=Social Security Contributions
Simulation.Salary.maintenance=Maintenance allowances
Simulation.Salary.meal=Meal allowance
Simulation.NetSalary = Net Salary
# formats
Format.time = {0,time}
Format.number = {0,number,#0.0##}
Format.percent = {0,number,##0.00' %'}
Format.currency = {0,number,##0.00' \u20ac'}
# list of simulations
Pam.Simulations.title = List of simulations
Pam.Simulations.num = Number
Pam.Simulations.lastName = Last Name
Pam.Simulations.first_name=First_name
Pam.Simulations.hours=Hours
Pam.Simulations.days=Days
Pam.Simulations.baseSalary=Base Salary
Pam.Simulations.allowances=Allowances
Pam.Simulations.socialcontributions=Social contributions
Pam.Simulations.netSalary=Salary
Pam.SimulationsEmpty.title=The list of simulations is empty
# menu
Menu.RunSimulation=Run simulation
Menu.ClearSimulation=Clear simulation
Menu.ViewSimulations=View simulations
Menu.BackToForm=Back to the navigation form
Menu.SaveSimulation=Save simulation
Menu.EndSession=End session
# error message
Error.sessionExpired=Session has expired
Error.simulationNumber=Invalid simulation number
# conversion error
xwork.default.invalid.fieldvalue=Invalid value for the "{0}" field.

The [Form.properties] file is as follows:


# so that doubles are in the local format
double.format={0,number,#0.00##}
# error message
daysWorked.error=Enter an integer between 1 and 31
hoursWorked.error=Enter a real number between 0 and 300

19.6. The stylesheet

Tiles views use the following stylesheet [styles.css]:


.label{
  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;
}

.titleInfo{
  background-color: #ffcc00
}

19.7. The Initial View

To explore the application, we will examine it based on the user’s various actions. For each action, we will look at the Struts action that executes it and the Tiles view that is returned in response.

In [struts.xml] we have the following actions:


  <!-- default action -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Form!input</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- Form action -->
    <action name="Form" class="web.actions.Form">
      <result name="success" type="tiles">submitted</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">input</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

  • lines 2-8: the application's default action is [Form!input].

The [Form] class is as follows:


package web.actions;

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

  // configuration initialized by Spring
  private Config config;
  // list of employees
  private List<Employee> employees;
  // list of errors
  private List<Error> errors;
  // Payroll
  private PayrollPayroll;
  // entries
  private String employeesValue;
  private Double hoursWorked;
  private Integer daysWorked;
  // session
  private Map<String, Object> session;
  // menu
  private Menu menu;

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

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

  // Calculate salary
  public String calculateSalary() {
 ...
    }
  }

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

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

  // getters and setters
  ...
}
  • Line 4: The [Form] action implements the Preparable interface. This interface has only one method, the prepare method on line 24. This method is executed once before any other method in the action. It is generally used to initialize the action's model.

The action model is defined in lines 6–21:

  • line 7: the config field is initialized by Spring as explained. It provides access to application-scope data:
  • a reference to the [business] layer
  • a reference to the list of employees.
  • a reference to the exception that may have occurred during the instantiation of the [Config] object
  • line 9: a list of employees. This will populate the employee dropdown in the [Saisie.jsp] fragment.
  • line 11: a list of errors. This will populate the [Error.jsp] fragment.
  • Line 21: the list of menu options for the [Entete.jsp] fragment

In [1], the links in the displayed menu are controlled by the menu field of the [Form] action.

The prepare method is executed before the input method. It is as follows:


  @Override
  public void prepare() throws Exception {
    // configuration error?
    Exception initException = config.getInitException();
    if (initException != null) {
      errors = new ArrayList<Error>();
      Throwable th = initException;
      while (th != null) {
        errors.add(new Error(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
    } else {
      employees = config.getEmployees();
    }
}
  • line 4: we retrieve the exception from the [Config] object instantiated by Spring
  • line 5: if an exception occurred during the instantiation of the [Config] object, then we initialize the error list on line 11. The [Error] class is as follows:

package web.entities;

import java.io.Serializable;

public class Error implements Serializable{
  
  public Error() {
  }
  
  // fields
  private String class;
  private String message;

  // constructor
  public Error(String class, String message) {
    this.setClass(class);
    this.message = message;
  }
  
  // getters and setters
...
}

The class is used to store the exception stack:

  • line 11: the exception class
  • line 12: the exception message

Let’s go back to the prepare method:

  • line 13: the list of employees from the [Config] object is stored in the action's employees field.

Once the prepare method has been executed, the input method will be executed next. It is as follows:


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

The input method simply sets the list of menu options to display. The [Menu] class is as follows:


package web.entities;

import java.io.Serializable;

public class Menu implements Serializable {
  // menu items

  private boolean runSimulation;
  private boolean clearSimulation;
  private boolean saveSimulation;
  private boolean viewSimulations;
  private boolean returnToForm;
  private boolean endSession;

  public Menu() {
  }

  public Menu(boolean runSimulation, boolean clearSimulation, boolean saveSimulation, boolean viewSimulations, boolean returnToForm, boolean endSession) {
    this.runSimulation = runSimulation;
    this.clearSimulation = clearSimulation;
    this.saveSimulation = saveSimulation;
    this.viewSimulations = viewSimulations;
    this.returnToForm = returnToForm;
    this.endSession = endSession;
  }
  
  // getters and setters
...  
}
  • lines 8–13: there are 6 possible links in the menu
  • lines 18-25: the class constructor allows you to specify which links should be displayed and which should not.

The menu links are displayed in [Entete.jsp], a JSP fragment present in all Tiles views. Each action will have a menu field to control the display of the menu in [Entete.jsp].

Let’s return to the input method:


  @Override
  public String input() {
    if (errors == null) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // menu
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}
  • lines 3-6: if the error list is empty, the menu [Run Simulation, View Simulations, End Session] will be displayed and the input key returned.
  • lines 9-10: if the error list is not empty, the menu will be empty and the exception key will be returned.

Let's return to the configuration of the [Form] action in [struts.xml]:


    <!-- Form action -->
    <action name="Form" class="web.actions.Form">
      <result name="success" type="tiles">submit</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">input</result>
      <result name="simulation" type="tiles">simulation</result>
</action>
  • Line 5: The input key displays the Tiles view named input
  • line 4: the exception key displays the Tiles view named exception

Let's start with the Tiles view named "input". It consists of the JSP fragments [Entete.jsp] and [Saisie.jsp].

The [Entete.jsp] fragment is as follows:

Image

Its code is as follows:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      <s:if test="menu.runSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.RunSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.clearSimulation">
        |<a href="<s:url action="Form!input"/>"><s:text name="Menu.ClearSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.viewSimulations">
        |<a href="<s:url action="ViewSimulations"/>"><s:text name="Menu.ViewSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.returnToForm">
        |<a href="<s:url action="ReturnToForm"/>"><s:text name="Menu.ReturnToForm"/></a><br/>
      </s:if>
      <s:if test="menu.saveSimulation">
        |<a href="<s:url action="SaveSimulation"/>"><s:text name="Menu.SaveSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.endSession">
        |<a href="<s:url action="EndSession"/>"><s:text name="Menu.EndSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>
  • lines 8-25: display of the six menu links [Run Simulation (lines 8-10), Clear Simulation (lines 11-13), View Simulations (lines 14–16), Return to Form (lines 17–19), Save Simulation (lines 20–22), End Session (lines 23–25).
  • lines 8, 11, 14, 17, 20, 23: the display of the links is controlled by the menu field of the current action.

Note that the [Entete.jsp] fragment displays an HTML table (lines 4–28) but is not a complete HTML page. Keep in mind that all views of the application are embedded in the following master page [MasterPage.jsp]:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css"/>
    <title>
      <tiles:insertAttribute name="title" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/resources/standard.jpg"/>">
    <tiles:insertAttribute name="header" />
    <hr/>
    <tiles:insertAttribute name="input" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="error" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

The [Entete.jsp] fragment is inserted on line 17, within a regular HTML page.

The [Saisie.jsp] fragment is inserted on line 19. This is the resulting view:

Image

The code for the [Saisie.jsp] fragment is as follows:


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

<!-- Enter information -->
<s:form name="Entry" id="Entry">
  <s:select name="comboEmployeesValue" list="employees" listKey="SS" listValue="firstName+' ' +lastName" key="Pam.Entry.employee"/>
  <s:textfield name="hoursWorked" key="Pam.Entry.Hours.label" value="%{#parameters['hoursWorked']!=null ? #parameters['hoursWorked'] : hoursWorked==null ? '' : getText('double.format',{hoursWorked})}"/>
  <s:textfield name="daysWorked" key="Pam.Entry.Days.label" value="%{#parameters['daysWorked']!=null ? #parameters['daysWorked'] : daysWorked==null ? '' : daysWorked}"/>
  <input type="hidden" name="action"/>
</s:form>
  • Line 17: The form has no action attribute. By default, action='Form'.
  • Line 18: Display of the employee dropdown. The dropdown's content (list attribute) is provided by the employees field of the current action. The value attribute of the options will be the employee's SSN (listKey attribute). The label displayed for each option will be the employee's first and last name (listValue attribute). The social security number of the employee selected in the combo box will be posted to the [Form].comboEmployeesValue field (name attribute).
  • Line 19: input field for hours worked. The displayed value (value attribute) is that of the heuresTravaillees field in the [Form] action in the following format (Form.properties):

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

The value will be posted to the [Form].hoursWorked field (name attribute).

  • Line 20: field for entering days worked. The displayed value (value attribute) is that of the daysWorked field in the [Form] action.

The value will be posted to the [Form].daysWorked field (name attribute).

Ultimately, the Tiles view displayed at startup when there are no errors is as follows:

Image

Let’s return to the configuration of the [Form] action:


    <!-- Form action -->
    <action name="Form" class="web.actions.Form">
      <result name="success" type="tiles">entered</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">input</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

We have seen that the [Form].input action can also trigger the exception key on line 4. In this case, the Tiles view named exception is displayed. This view consists of the [Header.jsp] and [Exception.jsp] fragments. We have already presented the [Header.jsp] fragment. The [Exception.jsp] fragment is as follows:

Image

This is the startup page of version 2 of the application when the DBMS has not been launched. The JSP code for the [Error.jsp] fragment is as follows:


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

<h2><s:text name="Pam.Errors.title"/></h2>
<table>
  <tr class="titreInfos">
    <th><s:text name="Pam.Errors.class"/></th>
    <th><s:text name="Pam.Errors.message"/></th>
  </tr>
  <s:iterator value="errors">
    <tr>
      <td class="label"><s:property value="class"/></td>
      <td class="info"><s:property value="message"/></td>
    </tr>
  </s:iterator>
</table>
  • Lines 10–14: An iterator over the List<Error> collection of errors from the [Form] action. Recall that in the event of an error, a stack of exceptions was stored there.

19.8. Run a simulation

Once the initial view is displayed, you can calculate a salary via the [Run a simulation] link.

19.8.1. Validating the entries

Consider the following sequence:

  • in [1], an incorrect entry
  • in [2], the response sent.

Let's consider the configuration of the [Form] action in [struts.xml]:


    <!-- Form action -->
    <action name="Form" class="web.actions.Form">
      <result name="success" type="tiles">submission</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">input</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

We know that in the event of a validation error, the validation interceptor returns the input key. Therefore, the Tiles input view is returned. The validation process ensures that fields with errors are accompanied by error messages.

Validation of the [Form] action is handled by the following [Form-validation.xml] file:


<!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="hoursWorked.error"/>
    </field-validator>
    
    <field-validator type="double" short-circuit="true">
      <param name="minInclusive">0</param>
      <param name="maxInclusive">300</param>
      <message key="hoursWorked.error"/>
    </field-validator>
  </field>
  
  <field name="daysWorked">
    <field-validator type="required" short-circuit="true">
      <message key="daysWorked.error"/>
    </field-validator>
    
    <field-validator type="conversion" short-circuit="true">
      <message key="daysWorked.error"/>
    </field-validator>
    
    <field-validator type="int" short-circuit="true">
      <param name="min">0</param>
      <param name="max">31</param>
      <message key="daysWorked.error"/>
    </field-validator>
  </field>
  
</validators>
  • Lines 6–20 verify that the hoursWorked field is a real number in the range [0,300].
  • Lines 22–36 verify that the `joursTravaillés` field is an integer in the range [0,31].

Let's return to the configuration of the [Form] action in [struts.xml]:


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

We know that in the event of a validation error, the validation interceptor returns the "input" key. Therefore, the "Input" Tiles view is returned.

Recall that this view consists of the fragments [Header.jsp] and [Input.jsp], where [Header.jsp] contains a title and a list of options, and [Input.jsp] contains the input form. In the event of input errors, the validation process ensures that the erroneous fields are accompanied by error messages and also display their incorrect values. The [Entete.jsp] fragment plays no role in the validation process. Let’s look at its code:


<%@ 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.runSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.RunSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.clearSimulation">
        |<a href="<s:url action="Form!input"/>"><s:text name="Menu.ClearSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.viewSimulations">
        |<a href="<s:url action="ViewSimulations"/>"><s:text name="Menu.ViewSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.returnToForm">
        |<a href="<s:url action="ReturnToForm"/>"><s:text name="Menu.ReturnToForm"/></a><br/>
      </s:if>
      <s:if test="menu.saveSimulation">
        |<a href="<s:url action="SaveSimulation"/>"><s:text name="Menu.SaveSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.endSession">
        |<a href="<s:url action="EndSession"/>"><s:text name="Menu.EndSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

The six links are configured by the template's menu field (lines 8, 11, 14, 17, 20, 23). When an error occurs, this template is not updated by the action, resulting in a page without a menu. To resolve this issue, the [Form] class has the following validate method:


package web.actions;

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

  ...
  // menu
  private Menu menu;

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

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

  // Calculate salary
  public String calculateSalary() {
  ...
  }

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

  // getters and setters
  ...
}
  • line 27: we know that when this is present, the validate method is executed by the validation process. We take this opportunity to update the menu on line 4, which is part of the [Entete.jsp] fragment template.
  • lines 29–32: if there were validation errors, then we set the menu to redisplay the Tiles input view. If there were no validation errors, then we do nothing. The `calculSalaire` method is then responsible for creating the view model to be displayed.

19.8.2. Salary Calculation

Let’s return to the JSP code in the header:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      <s:if test="menu.runSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.RunSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • Line 9: When the user clicks the [Run Simulation] link, the JavaScript function doSimulation is executed. This function is defined in the [Saisie.jsp] fragment:

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

<!-- Enter information -->
<s:form name="Entry" id="Entry">
  <s:select name="comboEmployeesValue" list="employees" listKey="SS" listValue="firstName+' ' +lastName" key="Pam.Entry.employee"/>
  <s:textfield name="hoursWorked" key="Pam.Entry.Hours.label" value="%{#parameters['hoursWorked']!=null ? #parameters['hoursWorked'] : hoursWorked==null ? '' : getText('double.format',{hoursWorked})}"/>
  <s:textfield name="daysWorked" key="Pam.Entry.Days.label" value="%{#parameters['daysWorked']!=null ? #parameters['daysWorked'] : daysWorked==null ? '' : daysWorked}"/>
  <input type="hidden" name="action"/>
</s:form>
  • line 14: a hidden field named action will be posted to the [Form] action. This field allows us to specify the action and method to be executed when the form is posted. As we may recall from the first examples, these can be specified in a parameter named action:Action!method. The value of this parameter does not matter; it just needs to be present.
  • Lines 2–6: The JavaScript function that is executed when the user clicks the [Run Simulation] link in the [Header.jsp] fragment.
  • Line 4: We change the name attribute of the hidden action field. We ensure it is in the form action:Action!method expected by Struts.
  • Line 5: The form named "Saisie de la ligne 5" is submitted. As a result, the following parameter string is submitted:
comboEmployeesValue=SS1&hoursWorked=xx&daysWorked=yy&action:Form!calculateSalary

SS1: INSEE number of the employee selected in the combo box

heuresTravaillees: number of hours worked

daysWorked: number of days worked

action:Form!calculateSalary: the above elements will be posted to the [Form] action, and then the calculateSalary method of that action will be executed.

The [Form].calculateSalary method is as follows:


// salary calculation
  public String calculateSalary() {
    try {
      // calculate salary
      payroll = config.getJob().calculatePayroll(employeeValue, hoursWorked, daysWorked);
      // add the simulation to the session
      session.put("simulation", new Simulation(0, "" + hoursWorked, "" + daysWorked, paySheet));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // done
      return "simulation";
    } catch (Throwable th) {
      ...
    }
  }
  • Line 5: The payroll calculation is requested from the [business] layer
  • line 7: a Simulation object is added to the user's session. This is because it may be needed for a subsequent request. The [Simulation] class is as follows:

package web.entities;

import java.io.Serializable;
import business.Payroll;

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

  // fields of a simulation
  private Integer num;
  private PayrollPayroll;
  private String hoursWorked;
  private String daysWorked;
  
  // constructor
  public Simulation(Integer num, String hoursWorked, String daysWorked, PayrollSheet payrollSheet) {
    this.setNum(num);
    this.setPayStub(payStub);
    this.setHoursWorked(hoursWorked);
    this.setDaysWorked(daysWorked);
  }
  
  public double getAllowances(){
    return paySheet.getPayElements().getMaintenanceAllowance() + paySheet.getPayElements().getMealAllowance();
  }

  // getters and setters
...
}
  • line 12: the simulation number. It is incremented with each new simulation saved.
  • line 13: the employee's pay stub
  • line 14: the number of hours worked
  • line 15: the number of days worked
  • line 25: the getIndemnites method returns the total compensation for the employee

We will see that the [Simulation] class is the model for the [Simulations.jsp] fragment, which displays all the simulations performed.

Back to the [Form].calculateSalary method:


// salary calculation
  public String calculateSalary() {
    try {
      // salary calculation
      payroll = config.getJob().calculatePayroll(employeeValue, hoursWorked, daysWorked);
      // add the simulation to the session
      session.put("simulation", new Simulation(0, "" + hoursWorked, "" + daysWorked, paySheet));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // done
      return "simulation";
    } catch (Throwable th) {
 ...
  }
  • line 9: update the menu
  • line 11: return the "simulation" navigation key.

Back to the [Form] action configuration:


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

Line 4 shows that the "simulation" navigation key displays the Tiles view named "simulation." This view consists of the following JSP fragments: [Header, Input, Simulation].

The rendered view is as follows:

  • in [1], the fragment [Header.jsp]
  • in [2], the fragment [Input.jsp]
  • at [3], the fragment [Simulation.jsp]. Note that the pay stub displayed is the fictitious pay stub rendered by the [business] layer.

The first two fragments have already been presented. The [Simulation.jsp] fragment is as follows:


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

<hr/>
<!-- Employee information -->
<span class="titreInfos">
  <s:text   name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="label">
      <s:text name="Simulation.Employee.name"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Employee.last_name"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Employee.address"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">   
      <s:property value="payroll.employee.lastName"/>      
    </td>
    <td class="info">
      <s:property value="payroll.employee.firstName"/>
    </td>
    <td class="info">
      <s:property value="payroll.employee.address"/>
    </td>
</table>
<table>
  <!-- line 1 -->
  <tr>
    <th class="label"><s:text name="Simulation.Employee.City"/></th>
    <th class="label">
      <s:text name="Simulation.Employee.zipCode"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Employee.index"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:property value="payroll.employee.city"/>
    </td>
    <td class="info">
      <s:property value="payroll.employee.zipCode"/>
    </td>
    <td class="info">
      <s:property value="payroll.employee.allowance.index"/>
    </td>
</table>
<!-- Contribution Information -->
<br/>
<span class="titleInfo">
  <s:text name="Simulation.ContributionInfo"/>
</span>

<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="label">
      <s:text name="Simulation.Contributions.csgrds"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Contributions.csgrds"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Contributions.retirement"/>
    </th>
    <th class="label">
      <s:text name="Simulation.SocialSecurityContributions"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.percent">
        <s:param value="payroll.contribution.csgrds"/>
      </s:text>
    </td>
    <td   class="info">
      <s:text name="Format.percent">
        <s:param value="payroll.contribution.csgd"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.percent">
        <s:param value="payroll.contribution.pension"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.percent">
        <s:param value="payroll.social.security"/>
      </s:text>
    </td>
</table>
<!-- Compensation Information -->
<br/>
<span class="titreInfos">
  <s:text name="Form.CompensationInfo"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="label">
      <s:text name="Simulation.Compensation.HourlyWage"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Allowances.DailyMaintenance"/>
    </th>
    <th class="label">
      <s:text name="Simulation.MealAllowances.Day"/>
    </th>
    <th class="label">
      <s:text name="Simulation.PaidLeaveAllowances"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.employee.allowance.baseHour"/>
      </s:text>
    </td>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.employee.dailyMaintenanceAllowance"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.employee.mealAllowance.Daily"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.employee.allowance.sick_leave_allowance"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- Salary information -->
<br/>
<span class="titleInfo">
  <s:text name="Simulation.SalaryInfo"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="label">
      <s:text name="Simulation.Salary.baseSalary"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Salary.socialSecurityContributions"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Salary.maintenance"/>
    </th>
    <th class="label">
      <s:text name="Simulation.Salary.Meals"/>
    </th>
  </tr>

  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payrollSheet.payrollElements.basePay"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payrollSheet.payrollItems.socialSecurityContributions"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.payrollItems.maintenanceAllowances"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="CurrencyFormat">
        <s:param value="payroll.payrollItems.mealAllowances"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- Net Pay-->
<br/>
<table>
  <tr>
    <td class="label">
      <s:text name="Simulation.salaireNet"/>
    <td></td>
    <td class="info">
      <s:text name="Currency.format">
        <s:param value="payrollSheet.payrollItems.netPay"/>
      </s:text>
    </td>
  </tr>
</table>

It's long... but functionally simple. This snippet displays the various properties of the [Form].paystub field, which represents the employee's pay stub.

Back to the [Form].calculatePay method:


// salary calculation
  public String calculateSalary() {
    try {
      ...
      return "simulation";
    } catch (Throwable th) {
      errors = new ArrayList<Error>();
      while (th != null) {
        errors.add(new Error(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
      // menu
      menu = new Menu(false, false, false, false, true, true);
      return "exception";
    }
  }

Salary calculation can go wrong. This would be the case, for example, if the connection to the DBMS were to fail. In this case, we handle the exception that occurs. We have already encountered this scenario when studying the [Form] method.input.

  • Lines 7–11: We create a list of Error objects from the exception stack
  • line 13: we set the menu
  • line 14: we set the exception key.

The exception key will display the Tiles exception view:


    <!-- Form action -->
    <action name="Form" class="web.actions.Form">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

This Tiles view has already been presented. It looks like this:

Image

19.9. Save a simulation

After running a simulation, the user may want to save it to the session.

  • In [1], the simulation is saved
  • In [2], the response displays the list of simulations already performed, to which the new simulation is added

The [Save Simulation] link is located in the [Entete.jsp] fragment:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      ...
      <s:if test="menu.saveSimulation">
        |<a href="<s:url action="SaveSimulation"/>"><s:text name="Menu.SaveSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

We can see that clicking on the link triggers the [SaveSimulation] action. This action is configured in the [struts.xml] file as follows:


   <!-- action SaveSimulation -->
    <action name="SaveSimulation" class="web.actions.Save" method="execute">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
</action>
  • Line 1: The [SaveSimulation] action is associated with the [Save] class and its execute method.

The [Save] class is as follows:


package web.actions;

...
public class Save extends ActionSupport implements SessionAware {

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

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

  // execute the action
  public String execute() {
    // retrieve the latest simulation from the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }

  // getters and setters
  ...
}
  • line 4: because the action needs access to the session, it implements the SessionAware interface.
  • line 7: the session
  • line 9: the menu

When the [Save] action is instantiated, its execute method is called. Recall that its job is to place the latest simulation into the session. This simulation will be added to the list of completed simulations, which is also stored in the session.

  • line 19: we retrieve the last simulation placed in the session.
  • lines 20–22: if it is not found, then the session has likely expired. In fact, the session lasts only a certain amount of time, which can be set in the [web.xml] file that configures the application.
  • Line 21: We return the error key.

Back to the configuration of the [SaveSimulation] action:


   <!-- action SaveSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

We can see that the "error" key (line 3) triggers the display of the Tiles view named "error" . This view is composed of the fragments [Entete.jsp] and [Erreur.jsp] and looks like this:

Image

The [Erreur.jsp] fragment is as follows:


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

<h2><s:text name="Pam.Error.label"/></h2>
<h4><s:text name="Error.sessionexpired"/></h4>

Back to the [Save].execute method:


// execute the action
  public String execute() {
    // retrieve the latest simulation from the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // Retrieve the number of the last simulation
    Integer lastSimulationNumber = (Integer) session.get("lastSimulationNumber");
    if (lastSimulationNumber == null) {
      lastSimulationNumber = 0;
    }
    // increment it
    lastSimulationNumber++;
    // put the new number back into the session
    session.put("lastSimulationNumber", lastSimulationNumber);
    // retrieve the list of simulations
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // Add the current simulation
    simulation.setNum(lastSimulationNumber);
    simulations.add(simulation);
    // display the list of simulations
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
  • lines 9–16: The various simulations are numbered starting from 1. The last assigned number is stored in the session under the key numDerniereSimulation. The code in lines 9–16 retrieves this key and increments the value associated with it.
  • lines 18–22: The list of simulations is stored in the session under the key simulations. Lines 18–22 retrieve this list if it exists or create it if it does not.
  • Lines 24–25: Once the list of simulations is obtained, the current simulation is added to it (line 25). Previously, the current simulation was assigned a number (line 24).
  • Line 27: The menu to be displayed is set
  • Line 28: We return the "simulations" navigation key.

Back to the configuration of the [EnregistrerSimulation] action in [struts.xml]:


   <!-- action SaveSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

Line 4: The "simulations" key triggers the display of the Tiles view named "simulations" . This view consists of the fragments [Entete.jsp] and [Simulations.jsp]. The displayed view is as follows:

  • in [1], the [Entete.jsp] fragment, which we are now familiar with.
  • at [2], the fragment [Simulations.jsp]

The [Simulations.jsp] fragment is as follows:


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

<!-- empty list of simulations -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.title"/></h2>
</s:if>
<!-- non-empty list of simulations -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.title"/></h2>
  <table>
    <tr class="titleInfo">
      <th><s:text name="Pam.Simulations.num"/></th>
      <th><s:text name="Pam.Simulations.lastName"/></th>
      <th><s:text name="Pam.Simulations.firstName"/></th>
      <th><s:text name="Pam.Simulations.hours"/></th>
      <th><s:text name="Pam.Simulations.days"/></th>
      <th><s:text name="Pam.Simulations.baseSalary"/></th>
      <th><s:text name="Pam.Simulations.allowances"/></th>
      <th><s:text name="Pam.Simulations.social-security-contributions"/></th>
      <th><s:text name="Pam.Simulations.netpay"/></th>
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="DeleteSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        <td class="label"><s:property value="num"/></td>
        <td class="info"><s:property value="payroll.employee.lastName"/></td>
        <td class="info"><s:property value="payroll.employee.firstName"/></td>
        <td class="info"><s:property value="hoursWorked"/></td>
        <td class="info"><s:property value="daysWorked"/></td>
        <td class="info">
          <s:text name="CurrencyFormat">
            <s:param value="payroll.payrollItems.basePay"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="CurrencyFormat">
            <s:param value="allowances"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Currency.format">
            <s:param value="paystub.payrollItems.socialSecurityContributions"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="CurrencyFormat">
            <s:param value="payroll.payrollItems.netPay"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">Withdraw</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • lines 5-7: if there are no simulations in the session, then the following view is displayed:

Image

  • lines 13-21: display the table column headers

Image

  • lines 23-55: iterate over the list of simulations found in the session
  • lines 24-26: creation of a URL named url (id attribute). The HTML link generated by this URL is as follows:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Remove</a>

We can see that the link targets the [DeleteSimulation] action with the id parameter, which represents the number of the simulation to be removed from the list.

  • Lines 28–54: For each iteration through the list of simulations, the properties of the current simulation are displayed.

Image

19.10. Removing a simulation

The user may want to remove a simulation from the list of simulations:

  • in [1], simulation #1 is deleted
  • in [2], simulation #1 has been removed

The [Remove] link is located in the [Simulations.jsp] fragment that we have already examined:


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

<!-- empty list of simulations -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.title"/></h2>
</s:if>
<!-- non-empty list of simulations -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.title"/></h2>
  <table>
    <tr class="titleInfo">
      ...
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="DeleteSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        ...
        <td class="info">
          <s:text name="CurrencyFormat">
            <s:param value="payrollSheet.payrollItems.netPay"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">Withdraw</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • Lines 16-18: generate the HTML link
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?</a>id=num">Remove</a>

where num is the number of the simulation to be removed.

The [DeleteSimulation] action is defined as follows in the [struts.xml] file:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- Spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />

  
  <!-- Struts/Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    ...
   <!-- RemoveSimulation action -->
    <action name="DeleteSimulation" class="web.actions.Delete">
      <result name="error" type="tiles">error</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   ...
  </package>

    <!-- Add packages here -->

</struts>
  • Line 16: The [SupprimerSimulation] action is associated with the [Supprimer] class. Since no method is specified, its execute method will be executed. The [Supprimer] class is as follows:

package web.actions;

...
public class Delete extends ActionSupport implements SessionAware {

  // session
  private Map<String, Object> session;
  // ID of the simulation to be deleted
  private String id;
  // menu
  private Menu menu;

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

  // execute the action
  public String execute() {
    // retrieve the simulations from the session
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // abnormal case - the session must have expired
      menu = new Menu(false, false, false, false, true, true);
      return "error";
    }
    // test id
    int num = 0;
    boolean error = false;
    try {
      num = Integer.parseInt(id);
      error = num <= 0;
    } catch (NumberFormatException ex) {
      // invalid
      error = true;
    }
    // error?
    if (error) {
      menu = new Menu(false, false, false, false, true, true);
      return "error";
    }
    // search for the simulation to delete
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // display the list of simulations
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }

  // getters and setters
...
}
  • Line 4: The [Delete] action implements the [SessionAware] interface to access the session.
  • line 7: the session
  • line 9: the number of the simulation to be deleted. Recall that we instantiate the [Delete] class via the HTML URL:
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?</a>id=num">Remove</a>

where num is the number of the simulation to be removed. This number will be stored in the id field on line 9.

  • Line 11: the menu for the view that will be displayed in response to the request
  • Line 19: The `execute` method that will generate the response to the request.
  • Line 21: We retrieve the list of simulations already performed in the session
  • lines 22-26: failing to retrieve this list from the session likely means the session has expired. We have encountered this scenario before. We return the error key, which displays the Tiles error view:

<!-- action RemoveSimulation -->
    <action name="RemoveSimulation" class="web.actions.Remove">
      <result name="error" type="tiles">error</result>
      ...
</action>

The Tiles error view was introduced in Section 19.9.

  • lines 28–36: we verify that the id string on line 9 is indeed an integer greater than 0.
  • lines 38-40: if this is not the case, the error key is returned again, which will display the error Tiles view
  • lines 43–48: The simulation to be removed is searched for in the list of simulations. If found, it is deleted.
  • Line 50: Update the menu for the Tiles simulations view.
  • Line 51: The `simulations` key is returned. This will display the Tiles simulations view:

<!-- action RemoveSimulation -->
    <action name="DeleteSimulation" class="web.actions.Delete">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

The Simulations Tiles view was introduced in Section 19.9.

19.11. Return to the form

From the Tiles simulations view, the user can return to the form:

  • in [1], click the link to return to the form
  • in [2], an empty form appears

The [Return to simulation form] link is defined in the [Entete.jsp] fragment:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      ...
      <s:if test="menu.returnForm">
        |<a href="<s:url action="ReturnForm"/>"><s:text name="Menu.ReturnForm"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • Line 10: The link points to the [ReturnForm] action. This is defined as follows in the [struts.xml] file:

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

We can see that this action is not associated with any class. It simply redirects the client browser to the [/Form!input] action. We are therefore in the same situation as when displaying the initial view explained in section 19.7. We thus return to this initial view [2].

19.12. View the list of simulations

From the Simulation Tiles or Input views, the user can request to view the simulations:

  • in [1], click the [View Simulations] link
  • in [2], the list of simulations is displayed

The [View simulations] link is defined in the [Entete.jsp] fragment:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      ...
      <s:if test="menu.viewSimulations">
        |<a href="<s:url action="ViewSimulations"/>"><s:text name="Menu.ViewSimulations"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • Line 10: The link [View Simulations] calls the action [ViewSimulations]. This is defined as follows in the [struts.xml] file:

   <!-- action ViewSimulations -->
    <action name="ViewSimulations" class="web.actions.View">
      <result name="success" type="tiles">simulations</result>
</action>

The [ViewSimulations] action is associated with the [View] class without specifying a method. Therefore, the [View].execute method will be executed. The [View] class is as follows:


package web.actions;

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

public class View extends ActionSupport{
  // menu
  private Menu menu = new Menu(false, false, false, false, true, true);
  // getters and setters

  public Menu getMenu() {
    return menu;
  }

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

The [View] action does only one thing: sets the menu for the Tiles simulations view (line 8). There is no execute method. Therefore, the one from the parent class [ActionSupport] will be executed. We know that it does nothing except return the success flag.

Back to the action in [struts.xml]:


   <!-- ViewSimulations action -->
    <action name="ViewSimulations" class="web.actions.View">
      <result name="success" type="tiles">simulations</result>
</action>

On line 3, we see that the "success" key triggers the display of the Tiles simulations view. This was described on page157.

19.13. Clear the current simulation

From the Tiles simulation view, the user can request to clear the current simulation:

  • in [1], the current simulation is cleared
  • in [2], the input form is cleared

The [Clear Simulation] link is defined in the [Entete.jsp] fragment:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      ...
      <s:if test="menu.clearSimulation">
        |<a href="<s:url action="Form!input"/>"><s:text name="Menu.ClearSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

On line 10, we see that the [Clear Simulation] link triggers the [Form!input] action. We know that this action leads to the initial view [2].

19.14. End the current session

From any Tiles view, the user can request to end the session:

  • In [1], we start from the simulations view and end the session
  • In [2], the input form is empty. We request to view the simulations.
  • In [3], the list of simulations is now empty.

The [End Session] link is defined in the [Entete.jsp] fragment:


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

<table>
  <tr>
    <td><h1><s:text name="Pam.title"/></h1></td>
    <td>
      ...
      <s:if test="menu.logout">
        |<a href="<s:url action="EndSession"/>"><s:text name="Menu.EndSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

On line 10, we see that the [End Session] link triggers the [EndSession] action. This is defined as follows in the [struts.xml] file:


    <action name="TerminerSession" class="web.actions.Terminer">
      <result name="success" type="redirectAction">
        <param name="actionName">Form!input</param>
        <param name="namespace">/</param>
      </result>
</action>
  • Line 1: We can see that the [Terminer] class will be instantiated and its execute method will be called.
  • Lines 2–5: After the [Terminer].execute method is executed, the user will be redirected to the initial input view. This explains Screen 2.

The [Terminer] class is as follows:


package web.actions;

import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;

public class Finish extends ActionSupport implements SessionAware {

  // session
  private Map<String, Object> session;
  
  @Override
  public String execute() {
    // Abandon the current session
    session.clear();
    return SUCCESS;
  }

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

The role of the [Finish] action is to clear the current session of its attributes.

  • Line 7: The [Terminate] action implements the [SessionAware] interface to access the session.
  • line 10: the session dictionary
  • line 13: the execute method is called
  • line 15: it clears the session dictionary. As a result, the list of simulations currently in the session will disappear. This explains screen #3.
  • Line 16: It returns the key "success," which, as we saw, will display the Tiles view with the value [2].

19.15. Conclusion

We have fully commented on version 1 of our case study, which works with a simulated [business] layer:

All that remains is to "connect" the real business layer to the [web] layer.