Skip to content

12. Version 7 - Multi-view/multi-page PAM web application

Here we return to the initial architecture where the [business] layer was simulated. We now know that this can easily be replaced by the actual [business] layer. The simulated [business] layer facilitates testing.

A JSF application is of the MVC (Model-View-Controller) type:

  • the [Faces Servlet] is the generic controller provided by JSF. This controller is extended by application-specific event handlers. The event handlers encountered so far were methods of classes serving as models for JSF pages
  • JSF pages send responses to the client browser. These are the views of the application.
  • JSF pages contain dynamic elements known as the page model. Note that for some authors, the model encompasses the entities manipulated by the application, such as the PayrollSheet or Employee classes. To distinguish between these two models, we can refer to the application model and the JSF page model.

In the JSF architecture, transitioning from a JSFi page to a JSFj page can be problematic.

  • The JSFi page has been displayed. From this page, the user triggers a POST via some event [1]
  • In JSF, this POST request is generally handled [2a,2b] by a method C of the Mi model of the JSFi page. We can say that method C is a secondary controller.
  • If, at the end of this method, the JSFj page needs to be displayed, controller C must:
  1. update [2c] the Mj model of the JSFj page
  2. return [2a] to the main controller the navigation key that will allow the JSFj page to be displayed

Step 1 requires that the Mi model of the JSFi page have a reference to the Mj model of the JSFj page. This complicates matters somewhat, making the Mi models dependent on one another. Indeed, the C manager of the Mi model that updates the Mj model must be aware of the latter. If the Mj model needs to be changed, the C manager of the Mi model will also need to be changed.

There is one scenario where model dependencies can be avoided: when there is a single M model used by all JSF pages. This architecture is only suitable for applications with just a few views, but it is very easy to use. This is the one we are currently using.

In this context, the application architecture is as follows:

12.1. The application views

The different views presented to the user will be as follows:

  • the [VueSaisies] view, which displays the simulation form

  • the [VueSimulation] view used to display the detailed simulation results:

Image

Image

  • the [SimulationsView] view, which lists the simulations performed by the customer

Image

  • the [EmptySimulationsView], which indicates that the client has no simulations or no more simulations:

Image

  • the [ErrorView] view, which indicates one or more errors:

Image

12.2. The NetBeans project for the [web] layer

The NetBeans project for this version will be the following Maven project:

  • in [1], the configuration files,
  • in [2], the JSF pages,
  • in [3], the stylesheet and background image for the views,
  • in [4], the [web] layer classes,
  • in [5], the lower layers of the application,
  • in [6], the message file for the application's internationalization,
  • in [7], the project dependencies.

Let’s review some of these elements.

12.2.1. Configuration files

The [web.xml] and [faces-config.xml] files are identical to those in the previous project, with the exception of one detail in [web.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  ...
  <welcome-file-list>
    <welcome-file>faces/saisie.xhtml</welcome-file>
  </welcome-file-list>
  ...
</web-app>
  • lines 4-6: the home page is the [saisie.xhtml] page

12.2.2. The style sheet

The [styles.css] file is as follows:


.simulationsHeader {
   text-align: center;
   font-style: italic;
   color: Snow;
   background: Teal;
}

.simuNum {
   height: 25px;
   text-align: center;
   background: MediumTurquoise;
}
.simuNom {
   text-align: left;
   background: PowderBlue;
}
.simuFirstName {
   width: 6em;
   text-align: left;
   color: Black;
   background: MediumTurquoise;
}
.simuHT {
   width: 3em;
   text-align: center;
   color: Black;
   background: PowderBlue;
}
.simuJT {
   width: 3em;
   text-align: center;
   color: Black;
   background: MediumTurquoise;
}
.simuSalaireBase {
   width: 9em;
   text-align: center;
   color: Black;
   background: PowderBlue;
}
.simuIndemnites {
   width: 3em;
   text-align: center;
   color: Black;
   background: MediumTurquoise;
}
.simuCotisationsSociales {
   width: 6em;
   text-align: center;
   background: PowderBlue;
}

.simuNetSalary {
   width: 10em;
   text-align: center;
   background: MediumTurquoise;
}

.errorHeaders {
   background: Teal;
   background-color: #ff6633;
   color: Snow;
   font-style: italic;
   text-align: center

}

.errorClass {
   background: MediumTurquoise;
   background-color: #ffcc66;
   height: 25px;
   text-align: center
}

.errorMessage {
   background: PowderBlue;
   background-color: #ffcc99;
   text-align: left
}

Here are some examples of JSF code using these styles:

Simulations View


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">

The result is as follows:

Image

Error View


          <h:dataTable value="#{form.errors}" var="error"
                                 headerClass="errorsHeaders" columnClasses="errorClass,errorMessage">

Image

12.2.3. The message file

The message file [messages_fr.properties] is as follows:


form.title=Payroll Calculator
form.comboEmployees.label=Employee
form.hoursWorked.label = Hours Worked
form.daysWorked.label=Days worked
form.hoursWorked.required=Enter the number of hours worked
form.hoursWorked.validation=Invalid data
form.daysWorked.required=Enter the number of days worked
form.daysWorked.validation=Invalid input
form.btnSalary.label=Salary
form.btnClear.label=Clear
exception.header=The following exception occurred
exception.httpCode=HTTP error code
exception.message=Exception message
exception.requestUri=URL requested when the error occurred
exception.servletName=Name of the servlet requested when the error occurred
form.employeeInfo=Employee Information
form.employee.lastName=Last Name
form.employee.last_name=Last name
form.employee.address=Address
form.employee.city=City
form.employe.zipCode=Zip Code
form.employee.index=Index
form.contributions.info=Social Security Contributions Information
form.contributions.csgrds=CSGRDS
form.contributions.csgd=CSGD
form.contributions.pension=Pension
form.contributions.social-security=Social Security
form.infos.indemnites=Information on Benefits
form.allowances.wageHourly=Hourly wage
form.allowances.maintenancePerDay=Maintenance / Day
form.allowances.mealPerDay=Meal / Day
form.allowances.paidLeave=Paid Leave
form.salary.info=Salary Information
form.salary.base = Base Salary
form.salary.socialContributions=Social Contributions
form.salary.maintenance=Maintenance Allowances
form.salary.meal=Meal Allowances
form.salary.net=Net Salary
form.menu.runSimulation=| Run simulation
form.menu.clearSimulation=| Clear simulation
form.menu.saveSimulation=| Save simulation
form.menu.returnToSimulator=| Return to simulator
form.menu.viewSimulations=| View simulations
form.menu.endSession=| End session
simulations.headers.name=Name
simulations.headers.lastName=Last Name
simulations.headers.lastName=Last Name
simulations.headers.hoursWorked=Hours Worked
simulations.headers.daysWorked=Days Worked
simulations.headers.baseSalary=Base Salary
simulations.headers.allowances=Allowances
simulations.headers.socialContributions=Social Contributions
simulations.headers.netSalary=Net Salary
simulations.headers.number=No.
error.title="An error has occurred."
error.exceptions=Exception chain
exception.type=Exception type
exception.message=Associated message

12.2.4. The [business] layer

The simulated [business] layer is modified as follows:


package business;

...
public class Business implements LocalBusiness, Serializable {
  
  // list of employees
  private Map<String, Employee> hashEmployees = new HashMap<String, Employee>();
  private List<Employee> employeeList;
  
  // get the pay stub
  public Payroll calculatePayroll(String SS,
    double hoursWorked, int daysWorked) {
    // retrieve the employee
    Employee e = hashEmployees.get(SS);
    // throw an exception if the employee does not exist
    if (e == null) {
      throw new PamException(String.format("The employee with SS number [%s] does not exist", SS), 1);
    }
    // return a dummy pay stub
    return new PayStub(e, new Contribution(3.49, 6.15, 9.39, 7.88), e.getAllowance(), new PayElements(100, 100, 100, 100, 100));
  }
  
  // list of employees
  public List<Employee> findAllEmployees() {
    if(employeesList == null){
      // create a list of three employees
      employeeList = new ArrayList<Employee>();
      employeesList.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", "The Roastery", "St Marcel", "49014", new Compensation(1, 1.93, 2, 3, 12)));
      // employee dictionary
      for (Employee e : listEmployees) {
        hashEmployees.put(e.getSS(), e);
      }
      // add an employee who does not yet exist in the dictionary
      listEmployees.add(new Employee("X", "Y", "Z", "La Brûlerie", "St Marcel", "49014", new Compensation(1, 1.93, 2, 3, 12)));
    }
    // Return the list of employees
    return listEmployees;
  }
}
  • line 8: the list of employees
  • line 7: the same list as a dictionary indexed by the employees' SS numbers
  • lines 24–39: the findAllEmployees method, which returns the list of employees. This method creates a hard-coded list and references it via the employees field from line 8.
  • lines 27–33: a list and a dictionary of two employees are created
  • line 35: an employee is added to the employees list (line 8) but not to the hashEmployees dictionary (line 7). This is so that the employee appears in the employee dropdown but is not recognized by the calculatePayroll method (line 14), causing it to throw an exception (line 17).
  • Lines 11–21: the calculatePayroll method
  • Line 14: The employee is looked up in the `hashEmployees` dictionary using their SSN. If they are not found, an exception is thrown (lines 16–18). Thus, we will get an exception for the employee with SSN X added on line 35 to the `employees` list but not to the `hashEmployees` dictionary.
  • Line 20: A fictitious pay stub is created and returned.

12.3. The application's beans

There will be three beans with three different scopes:

  

12.3.1. The ApplicationData bean

The ApplicationData bean will have application scope:


package web.beans.application;

import java.io.Serializable;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import business.IMetierLocal;
import business.Business;

@Named
@ApplicationScoped
public class ApplicationData implements Serializable {

  // business layer
  private ILocalBusinessLogic businessLogic = new BusinessLogic();
  // logger
  private boolean logsEnabled = true;
  private final Logger logger = Logger.getLogger("pam");

  public ApplicationData() {
  }

  @PostConstruct
  public void init() {
    // log
    if (isLogsEnabled()) {
      logger.info("ApplicationData");
    }
  }

  // getters and setters
...
}
  • line 11: the @Named annotation makes the class a managed bean. Note that, unlike in the previous project, we did not use the @ManagedBean annotation. The reason is that a reference to this class must be injected into another class using the @Inject annotation, and @Inject only injects classes with the @Named annotation.
  • Line 12: The @ApplicationScoped annotation makes the class an application-scoped object. Note that the annotation class is [javax.enterprise.context.ApplicationScoped] (line 6) and not [javax.faces.bean.ApplicationScoped] as in the beans of the previous project.

The ApplicationData bean serves two purposes:

  • line 16: maintaining a reference to the [business] layer,
  • lines 18–19: defining a logger that can be used by other beans to log to the GlassFish console.

12.3.2. The SessionData bean

The SessionData bean will have session scope:


package web.beans.session;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import web.beans.application.ApplicationData;
import web.entities.Simulation;

@Named
@SessionScoped
public class SessionData implements Serializable {

  // application
  @Inject
  private ApplicationData applicationData;
  // simulations
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int lastSimulationCount = 0;
  private Simulation simulation;
  // menus
  private boolean isSimulationRendered = true;
  private boolean menuClearSimulationIsRendered = true;
  private boolean saveSimulationMenuIsRendered;
  private boolean menuViewSimulationsIsRendered;
  private boolean menuReturnToSimulatorIsRendered;
  private boolean menuEndSessionIsRendered = true;
  // locale
  private String locale="fr_FR";
  
  // constructor
  public SessionData() {
  }

  @PostConstruct
  public void init() {
    // log
    if (applicationData.isLogsEnabled()) {
      applicationData.getLogger().info("SessionData");
    }
  }

  // menu management
  public void setMenu(boolean menuSimulateIsRendered, boolean menuSaveSimulationIsRendered, boolean menuClearSimulationIsRendered, boolean menuViewSimulationsIsRendered, boolean menuReturnToSimulatorIsRendered, boolean menuEndSessionIsRendered) {
    this.setMenuRunSimulationIsRendered(menuRunSimulationIsRendered);
    this.setMenuSaveSimulationIsRendered(menuSaveSimulationIsRendered);
    this.setMenuViewSimulationsIsRendered(menuViewSimulationsIsRendered);
    this.setMenuClearSimulationIsRendered(menuClearSimulationIsRendered);
    this.setMenuReturnToSimulatorIsRendered(menuReturnToSimulatorIsRendered);
    this.setMenuEndSessionIsRendered(menuEndSessionIsRendered);
  }

  // getters and setters
  ...
}
  • Line 13: The SessionData class is a managed bean (@Named) that can be injected into other managed beans,
  • line 14: it has session scope (@SessionScoped),
  • lines 18–19: a reference to the ApplicationData bean is injected into it (@Inject),
  • lines 21–32: the application data that must be maintained across sessions.
  • line 21: the list of simulations performed by the user,
  • line 22: the number of the last saved simulation,
  • line 23: the last simulation that was performed,
  • lines 25–30: the menu options,
  • line 32: the application locale.

Lines 39–44: the init method is executed after the class is instantiated (@PostConstruct). Here, it is used only to log its execution. We must be able to verify that it is executed only once per user since the class is session-scoped. Line 42: the method uses the logger defined in the ApplicationData class. This is why we needed to inject a reference to this bean (lines 18–19).

12.3.3. The Form Bean

The Form bean is request-scoped:


package web.beans.request;

import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import jpa.Employee;
import business.Payroll;
import web.beans.application.ApplicationData;
import web.beans.session.*;
import web.entities.Error;
import web.entities.Simulation;

@Named
@RequestScoped
public class Form {

  public Form() {
  }
  // other beans
  @Inject
  private ApplicationData applicationData;
  @Inject
  private SessionData sessionData;
  // the view model
  private String comboEmployeesValue = "";
  private String hoursWorked = "";
  private String daysWorked = "";
  private Integer numSimulationToDelete;
  private List<Error> errors = new ArrayList<Error>();
  private PayrollSheet payrollSheet;

  
  // list of employees
  public List<Employee> getEmployees(){
    return applicationData.getBusiness.findAllEmployees();
  }
  
  // menu action
  public String runSimulation() {
    ...
  }

  public String saveSimulation() {
  ...
  }

  public String clearSimulation() {
  ...
  }

  public String viewSimulations() {
  ...
  }

  public String returnSimulator() {
   ...
  }

  public String endSession() {
  ...
  }

  public String removeSimulation() {
 ...
  }

  private void clearForm() {
 ...
  }

// getters and setters
...
}
  • line 17, the class is a managed bean (@Named),
  • line 18, request-scoped (@RequestScoped),
  • lines 24–25: injection of a reference to the application-scoped bean ApplicationData,
  • lines 26–27: injection of a reference to the session-scoped bean SessionData.

12.4. The application pages

  

12.4.1. [layout.xhtml]

The [layout.xhtml] page handles the layout of all views:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <f:view locale="#{sessionData.locale}">
    <h:head>
      <title><h:outputText value="#{msg['form.title']}"/></title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <script type="text/javascript">
      function reset(){
        // we change the posted values
        document.forms['form'].elements['form:employeesCombo'].value="0";
        document.forms['form'].elements['form:hoursWorked'].value="0";
        document.forms['form'].elements['form:daysWorked'].value="0";
      }
    </script>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="form">
        <!-- header -->
        <ui:include src="header.xhtml" />
        <!-- content -->
        <ui:insert name="part1" >
          Childcare Provider Management
        </ui:insert>
        <ui:insert name="part2"/>
      </h:form>
    </h:body>
  </f:view>
</html>

Every view consists of the following elements:

  • a header displayed by the fragment [entete.xhtml] (line 24),
  • a body consisting of two fragments called part1 (lines 26–28) and part2 (line 29),
  • line 8: the locale attribute ensures the internationalization of the pages. Here, there will be only one: fr_FR,
  • lines 14–19: JavaScript code.

The display of the page [layout.xhtml] is as follows:

  • in [1], the header [entete.xhtml],
  • in [2], the part1 fragment.

12.4.2. The header [entete.xhtml]

The code for the [entete.xhtml] page is as follows:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition>
    <!-- header -->
    <h:panelGrid columns="2">
      <h:panelGroup>
        <h2><h:outputText value="#{msg['form.title']}"/></h2>
      </h:panelGroup>
      <h:panelGroup>
        <h:panelGrid columns="1">
          <h:commandLink id="cmdRunSimulation" value="#{msg['form.menu.runSimulation']}" action="#{form.runSimulation}" rendered="#{sessionData.menuRunSimulationIsRendered}"/>
          <h:commandLink id="cmdClearSimulation" onclick="raz()" value="#{msg['form.menu.clearSimulation']}" action="#{form.clearSimulation}" rendered="#{sessionData.menuClearSimulationIsRendered}"/>
          <h:commandLink id="cmdSaveSimulation" immediate="true" value="#{msg['form.menu.saveSimulation']}" action="#{form.saveSimulation}" rendered="#{sessionData.menuSaveSimulationIsRendered}"/>
          <h:commandLink id="cmdViewSimulations" immediate="true" value="#{msg['form.menu.viewSimulations']}" action="#{form.viewSimulations}" rendered="#{sessionData.menuViewSimulationsIsRendered}"/>
          <h:commandLink id="cmdReturnToSimulator" immediate="true" value="#{msg['form.menu.returnToSimulator']}" action="#{form.returnToSimulator}" rendered="#{sessionData.menuReturnToSimulatorIsRendered}"/>
          <h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>
        </h:panelGrid>
      </h:panelGroup>
    </h:panelGrid>
    <hr/>
  </ui:composition>
</html>
  • line 12: the application title,
  • lines 16–21: the six links corresponding to the six actions the user can perform. These links are controlled (rendered attribute) by Booleans in the SessionData bean,
  • line 17: clicking the [Clear Simulation] link triggers the execution of the JavaScript function raz. This function was defined in the [layout.xhtml] template,
  • line 18: the immediate=true attribute ensures that data validity is not checked before the [Form].enregistrerSimulation method is executed. This is intentional. You may want to save the last simulation performed a minute ago even if the data entered since then in the form (but not yet validated) is invalid. The same applies to the actions [View Simulations], [Clear Simulation], [Return to Simulator], and [End Session].

12.5. Use Cases for the Application

12.5.1. Displaying the home page

The home page is the following [saisie.xhtml] page:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
  </ui:composition>
</html>
  • line 8: it is displayed within the [layout.xhtml] page,
  • line 9: in place of the fragment named part1. In this fragment, the page [saisie2.xhtml] is displayed:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <h:panelGrid columns="3">
    <h:outputText value="#{msg['form.comboEmployees.label']}"/>
    <h:outputText value="#{msg['form.hoursWorked.label']}"/>
    <h:outputText value="#{msg['form.daysWorked.label']}"/>
    <h:selectOneMenu id="comboEmployees" value="#{form.comboEmployeesValue}">
      <f:selectItems .../>
    </h:selectOneMenu>
    <h:inputText id="heuresTravaillees" value="#{form.heuresTravaillées}" required="true" requiredMessage="#{msg['form.heuresTravaillées.required']}" validatorMessage="#{msg['form.heuresTravaillées.validation']}">
      <f:validateDoubleRange minimum="0" maximum="300"/>
    </h:inputText>
    <h:inputText id="daysWorked" value="#{form.daysWorked}" required="true" requiredMessage="#{msg['form.daysWorked.required']}" validatorMessage="#{msg['form.daysWorked.validation']}">
      <f:validateLongRange minimum="0" maximum="31"/>
    </h:inputText>
    <h:panelGroup></h:panelGroup>
    <h:message for="hoursWorked" styleClass="error"/>
    <h:message for="daysWorked" styleClass="error"/>
  </h:panelGrid>
  <hr/>
</html>

This code displays the following view:

 

Question: Complete line 13 of the XHTML code. The list of items in the employee combo box is provided by a method of the [Form] bean. Write this method. The items displayed in the combo box will have their itemValue property set to an employee’s social security number, and the itemLabel property will be a string consisting of the employee’s first and last names.



Question: How should the [SessionData] bean be initialized so that, during the initial GET request made to the form, the header menu is the one shown above?


12.5.2. The [ faireSimulation] action

The JSF code for the [runSimulation] action is as follows:


<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>

The [faireSimulation] action calculates a pay stub:

Image

From the previous page, we obtain the following result:

The simulation is displayed on the following [simulation.xhtml] page:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <ui:include src="saisie2.xhtml"/>
    </ui:define>
    <ui:define name="part2">
      <h:outputText value="#{msg['form.employeeInfo']}" styleClass="titleInfo"/>
      <br/><br/>
      <h:panelGrid columns="3" rowClasses="label,info">
        <h:outputText value="#{msg['form.employe.lastName']}"/>
        <h:outputText value="#{msg['form.employe.firstName']}"/>
        <h:outputText value="#{msg['form.employe.address']}"/>
        <h:outputText value="#{form.payroll.employee.lastName}"/>
        <h:outputText value="#{form.payroll.employee.firstName}"/>
        <h:outputText value="#{form.payroll.employee.address}"/>
      </h:panelGrid>
      <h:panelGrid columns="3" rowClasses="label,info">
        <h:outputText value="#{msg['form.employee.city']}"/>
        <h:outputText value="#{msg['form.employe.zipCode']}"/>
        <h:outputText value="#{msg['form.employe.index']}"/>
        <h:outputText value="#{form.payroll.employee.city}"/>
        <h:outputText value="#{form.payroll.employee.zipCode}"/>
        <h:outputText value="#{form.payroll.employee.allowance.index}"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="label,info">
        <h:outputText value="#{msg['form.cotisations.csgrds']}"/>
        <h:outputText value="#{msg['form.contributions.csgd']}"/>
        <h:outputText value="#{msg['form.contributions.retirement']}"/>
        <h:outputText value="#{msg['form.contributions.social-security']}"/>
        <h:outputText value="#{form.payroll.csgrds_contribution} %"/>
        <h:outputText value="#{form.payroll.contribution.csgd} %"/>
        <h:outputText value="#{form.payroll.pension.contribution} %"/>
        <h:outputText value="#{form.payroll.socialSecurityContribution} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="label,info">
        <h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
        <h:outputText value="#{msg['form.allowances.dailyMaintenance']}"/>
        <h:outputText value="#{msg['form.mealAllowanceDaily']}"/>
        <h:outputText value="#{msg['form.allowances.paidLeave']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.employee.baseHourlyRate}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.employee.dailyMaintenanceAllowance}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.employee.daily_meal_allowance}"/>
        </h:outputFormat>
        <h:outputText value="#{form.payroll.employee.allowance.CPallowances} %"/>
      </h:panelGrid>
      <br/>
      <h:outputText value="#{msg['form.payrollInfo']}" styleClass="titleInfo"/>
      <br/><br/>
      <h:panelGrid columns="4" rowClasses="label,info">
        <h:outputText value="#{msg['form.salary.base']}"/>
        <h:outputText value="#{msg['form.salary.socialSecurityContributions']}"/>
        <h:outputText value="#{msg['form.salary.maintenance']}"/>
        <h:outputText value="#{msg['form.salary.meals']}"/>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.payrollItems.baseSalary}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.payrollItems.socialSecurityContributions}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.payrollItems.maintenanceAllowance}"/>
        </h:outputFormat>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.payrollItems.mealAllowance}"/>
        </h:outputFormat>
      </h:panelGrid>
      <br/>
      <h:panelGrid columns="3" columnClasses="label,col2,info">
        <h:outputText value="#{msg['form.salaire.net']}"/>
        <h:panelGroup></h:panelGroup>
        <h:outputFormat value="{0,number,currency}">
          <f:param value="#{form.payroll.payrollElements.netPay}"/>
        </h:outputFormat>
      </h:panelGrid>
    </ui:define>
  </ui:composition>
</html>
  • Line 8: the [simulation.xhtml] page is inserted into the [layout.xhtml] page,
  • lines 9–11: the input area is displayed in the part1 fragment of the layout page,
  • lines 12–91: the simulation is displayed in the part2 fragment of the layout page.

Question: Write the [doSimulation] method for the [Form] class. The simulation will be saved in the SessionData bean.


12.5.3. Error Handling

We want to be able to properly handle exceptions that may occur during the calculation of a simulation. To do this, the code for the [runSimulation] method will use a try/catch block:


  // menu action
  public String runSimulation(){
    try{
      // calculate the pay stub
      payrollSheet = ...
      // display the simulation
      ...
      // update the menu
      ...
      // render the simulation view
      return "simulation";
    } catch (Throwable th) {
      // clear the list of previous errors
      ...
      // create a new list of errors
      ...
      // display the errorView
      ...
      // update the menu
      ...
      // display the error view
      return "errors";
    }
}

The list of errors created on line 15 is as follows:


  // the view template
  ...
  private List<Error> errors = new ArrayList<Error>();
...

The Error class is defined as follows:


package web.entities;

public class Error {
  
  public Error() {
  }
  
  // field
  private String class;
  private String message;

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

Errors will be exceptions whose class name is stored in the class field and whose message is stored in the message field.

The list of errors constructed in the [faireSimulation] method consists of:

  • the initial exception th of type Throwable that occurred,
  • its cause th.getCause() if it has one,
  • the cause of the cause h.getCause().getCause() if it exists.
  • ...

Here is an example of an error list:

Image

Above, the employee [Z Y] does not exist in the employee dictionary used by the simulated [business] layer. In this case, the simulated [business] layer throws an exception (line 6 below):


  public PayrollCalculatePayroll(String SS, double hoursWorked, int daysWorked) {
    // retrieve the employee
    Employee e = hashEmployees.get(SS);
    // throw an exception if the employee does not exist
    if(e==null){
      throw new PamException(String.format("Employee with SS # [%s] does not exist", SS), 1);
    }
...
}

The result is as follows:

Image

This view is displayed by the following [errors.xhtml] page:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <h3><h:outputText value="#{msg['error.title']}"/></h3>
      <h:dataTable value="#{form.errors}" var="error"
                   headerClass="errorHeaders" columnClasses="errorClass,errorMessage">
        <f:facet name="header">
          <h:outputText value="#{msg['error.exceptions']}"/>
        </f:facet>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.type']}"/>
          </f:facet>
          <h:outputText value="#{error.class}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['exception.message']}"/>
          </f:facet>
          <h:outputText value="#{error.message}"/>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>

Question: Complete the [faireSimulation] method so that, in the event of an exception, it displays the [vueErreur] view.


12.5.4. The action [ effacerSimulation]

The [clearSimulation] action allows the user to return to an empty form:

The JSF code for the [clearSimulation] link is as follows:


<h:commandLink id="cmdEffacerSimulation"  onclick="raz()" value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}" rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>

Clicking the [ClearSimulation] link first triggers a call to the JavaScript function raz(). This method is defined in the [layout.xhtml] page:


    <script type="text/javascript">
      function raz(){
        // we change the submitted values
        document.forms['form'].elements['form:employeesCombo'].value="0";
        document.forms['form'].elements['form:hoursWorked'].value="0";
        document.forms['form'].elements['form:daysWorked'].value="0";
      }
</script>

Lines 4–6 change the posted values. Note that

  • the posted values are valid, i.e., they will pass the validation checks for the hoursWorked and daysWorked input fields.
  • The `raz` function does not submit the form. Instead, the form will be submitted via the `cmdEffacerSimulation` link. This submission will occur after the JavaScript `raz` function has executed.

During the submission, the submitted values will follow the normal process: validation, then assignment to the model fields. These are as follows in the [Form] class:


  // the view model
  private String comboEmployeesValue;
  private String hoursWorked;
  private String daysWorked;
  ...

These three fields will receive the three posted values {"0", "0", "0"}. Once this assignment is complete, the clearSimulation method will be executed.


Question: Write the [effacerSimulation] method for the [Form] class. Ensure that:

  • only the input field is displayed,

  • the combo box is set to its first item,

  • the hoursWorked and daysWorked input fields display empty strings.


12.5.5. The [ enregistrerSimulation] action

The JSF code for the [saveSimulation] link is as follows:


<h:commandLink id="cmdEnregistrerSimulation" immediate="true" value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}" rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>

The [saveSimulation] action associated with the link saves the current simulation to a list of simulations maintained in the [SessionData] class:


private List<Simulation> simulations = new ArrayList<Simulation>();

The Simulation class in the web.entities is as follows:


package web.entities;

import business.Payroll;

public class Simulation {
  
  public Simulation() {
  }

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

  // getters and setters
...
}

This class allows you to save a simulation created by the user:

  • line 11: the simulation number,
  • line 12: the payslip that was calculated,
  • line 13: the number of hours worked,
  • line 14: the number of days worked.

Here is an example of a record:

Image

From the previous page, we get the following result:

Image

The simulation number is incremented with each new record. It belongs to the SessionData bean:


  // simulations
  private List<Simulation> simulations = new ArrayList<Simulation>();
  private int lastSimulationNumber = 0;
private Simulation simulation;
  • Line 2: the number of the last simulation performed.

The [saveSimulation] method can proceed as follows:

  • retrieve the number of the last simulation from the [SessionData] bean and increment it,
  • add the new simulation to the list of simulations maintained by the [SessionData] class,
  • display the simulation table:

The simulation table is displayed by the [simulations.xhtml] page:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <!-- simulation table -->
      <h:dataTable value="#{sessionData.simulations}" var="simulation"
                   headerClass="simulationsHeaders" columnClasses="simuNum,simuLastName,simuFirstName,simuGrossTax,simuNetTax,simuBaseSalary,simuAllowances,simuSocialContributions,simuNetSalary">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.numero']}"/>
          </f:facet>
          <h:outputText value="#{simulation.num}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.name']}"/>
          </f:facet>
          <h:outputText value="#{simulation.payroll.employee.lastName}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.firstName']}"/>
          </f:facet>
          <h:outputText value="#{simulation.payroll.employee.firstName}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.hoursWorked']}"/>
          </f:facet>
          <h:outputText value="#{simulation.hoursWorked}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.daysWorked']}"/>
          </f:facet>
          <h:outputText value="#{simulation.daysWorked}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.baseSalary']}"/>
          </f:facet>
          <h:outputText value="#{simulation.payroll.payrollItems.basePay}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.indemnities']}"/>
          </f:facet>
          <h:outputText value="#{simulation.indemnites}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
          </f:facet>
          <h:outputText value="#{simulation.payroll.payrollItems.socialSecurityContributions}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['simulations.headers.netPay']}"/>
          </f:facet>
          <h:outputText value="#{simulation.payroll.payrollItems.netPay}"/>
        </h:column>
        <h:column>
          <h:commandLink value="Remove" action="#{form.removeSimulation}">
            <f:setPropertyActionListener target="#{form.simulationNumberToDelete}" value="#{simulation.number}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • line 8, the page [simulations.xhtml] is inserted inside the page [layout.xhtml],
  • line 9, in place of the fragment named part1,
  • line 11, the <h:dataTable> tag uses the field #{sessionData.simulations} as its data source, i.e., the following field:

  // simulations
private List<Simulation> simulations;
  • The var="simulation" attribute sets the name of the variable representing the current simulation within the <h:datatable> tag

  • The headerClass="simulationsHeaders" attribute sets the style of the table column headers.

  • The columnClasses="...." attribute sets the style of each column in the table

Let’s examine one of the table columns and see how it is constructed:

The JSF code for the Name column is as follows:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.nom']}"/>
              </f:facet>
              <h:outputText value="#{simulation.payroll.employee.name}"/>
</h:column>
  • Lines 2–4: The <f:facet name="header"> tag defines the column header
  • line 5: the employee's name is written:
    • simulation refers to the var attribute of the <h:dataTable ...> tag:

<h:dataTable value="#{sessionData.simulations}" var="simulation" ...>

simulation refers to the current simulation in the list of simulations: first the 1st, then the 2nd, …

  • (continued)
    • simulation.payroll refers to the payroll field of the current simulation
    • simulation.payroll.employee refers to the employee field within the payroll field
    • simulation.payroll.employee.name refers to the name field of the employee field

The same technique is repeated for all columns in the table. There is an issue with the Allowances column, which is generated using the following code:


            <h:column>
              <f:facet name="header">
                <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
              </f:facet>
              <h:outputText value="#{simulation.indemnites}"/>
</h:column>

On line 5, the value of simulation.indemnites is displayed. However, the Simulation class does not have an indemnites field. It is important to remember here that the indemnites field is not used directly but via the simulation.getIndemnites() method. Therefore, it is sufficient for this method to exist. The indemnites field itself may not exist. The getIndemnites method must return the total of the employee’s allowances. This requires an intermediate calculation because this total is not directly available in the pay stub. The getIndemnites method is provided in Section 12.5.5.


Question: Write the [enregistrerSimulation] method for the [Form] class.


12.5.6. The action [ retourSimulateur]

The JSF code for the [retourSimulateur] link is as follows:


<h:commandLink id="cmdRetourSimulateur" immediate="true" value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}" rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>

The [returnSimulator] action associated with the link allows the user to return from the [simulationsView] to the [inputsView]:

Image

The result obtained:

Image


Question: Write the [retourSimulateur] method for the [Form] class. The input form shown must be empty, as above.


12.5.7. The [viewSimulations] action

The JSF code for the [viewSimulations] link is as follows:


<h:commandLink id="cmdVoirSimulations" immediate="true" value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}" rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>

The [viewSimulations] action associated with the link allows the user to view the table of simulations, regardless of the status of their entries:

Image

The result obtained:

Image


Question: Write the [viewSimulations] method for the [Form] class.


We will ensure that if the list of simulations is empty, the view displayed is [vueSimulationsVides]:

Image

To display the view above, we will use the following [simulationsVides.xhtml] page:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <ui:composition template="layout.xhtml">
    <ui:define name="part1">
      <h2>Your list of simulations is empty.</h2>
    </ui:define>
  </ui:composition>
</html>

12.5.8. The [ removeSimulation] action

The user can remove simulations from their list:

Image

The result is as follows:

Image

If we remove the last simulation above, we will get the following result:

Image

The JSF code for the [Remove] column in the simulation table is as follows:


          <h:dataTable value="#{form.simulations}" var="simulation"
                       headerClass="simulationsHeaders" columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaireNet">
...
            <h:column>
              <h:commandLink value="Remove" action="#{form.removeSimulation}">
                <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
              </h:commandLink>
            </h:column>
          </h:dataTable>
  • Line 5: The [Remove] link is associated with the [removeSimulation] method of the [Form] class. This method needs to know the number of the simulation to be removed. This is provided by the <f:setPropertyActionListener> tag on line 8. This tag has two attributes, target and value: the target attribute specifies a field in the model to which the value of the value attribute will be assigned. Here, the number of the simulation to be removed, #{simulation.num}, will be assigned to the numSimulationToDelete field of the [Form] class:

  // the view model
  ...
  private Integer numSimulationToDelete;

When the [removeSimulation] method of the [Form] class is executed, it will be able to use the value that was previously stored in the numSimulationToDelete field.


Question: Write the [retirerSimulation] method of the [Form] class.


12.5.9. The [ terminateSession] action

The JSF code for the [End Session] link is as follows:


<h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}" rendered="#{sessionData.menuTerminerSessionIsRendered}"/>

The [EndSession] action associated with the link allows the user to end their session and return to the empty input form:

Image

Image

If the user had a list of simulations, it is cleared. Additionally, the simulation numbering resets to 1.


Question: Write the [endSession] method for the [Form] class.


12.6. Integration of the web layer into a 3-tier architecture JSF/EJB

The architecture of the previous web application was as follows:

We replace the simulated [business] layer with the [business, DAO, JPA] layers implemented by EJBs in Section 7.1:


Practical exercise: Integrate the JSF and EJB layers following the methodology in Section 11.