Skip to content

10. Versión 5 - Aplicación web PAM / JSF

10.1. Arquitectura de la aplicación

La arquitectura de la aplicación web PAM será la siguiente:

En esta versión, el servidor Glassfish alojará todas las capas de la aplicación:

  • la capa [web] está alojada en el contenedor de servlets del servidor (1, más abajo)
  • las demás capas [metier, DAO, jpa] están alojadas en el contenedor EJB3 del servidor (2 a continuación)

Los elementos [metier, DAO] de la aplicación que se ejecutan en el contenedor EJB3 ya se han descrito en la aplicación cliente/servidor analizada en el apartado 7.1, cuya arquitectura era la siguiente:

Las capas [metier, DAO] se ejecutaban en el contenedor EJB3 del servidor Glassfish y la capa [ui] en una aplicación de consola o Swing en otra máquina:

En la arquitectura de la nueva aplicación:

solo hay que escribir la capa [web / jsf]. Las demás capas [metier, DAO, jpa] ya están implementadas.

En el documento [ref3] se muestra que una aplicación web en la que la capa web se implementa con Java Server Faces tiene una arquitectura similar a la siguiente:

Esta arquitectura implementa el patrón de diseño MVC (Modelo, Vista, Controlador). El procesamiento de una solicitud de un cliente se lleva a cabo de la siguiente manera:

Si la solicitud se realiza mediante un GET, se ejecutan los dos pasos siguientes:

  1. solicitud: el navegador del cliente envía una solicitud al controlador [Faces Servlet]. Este controlador gestiona todas las solicitudes de los clientes. Es la puerta de entrada a la aplicación. Es la «C» de MVC.
  2. respuesta: el controlador C solicita que se muestre la página JSF seleccionada. Esta es la vista, la «V» de MVC. La página JSF utiliza una plantilla M para inicializar las partes dinámicas de la respuesta que debe enviar al cliente. Esta plantilla es una clase Java que puede recurrir a la capa [métier] [4a] para proporcionar a la vista V los datos que necesita.

Si la solicitud se realiza con un POST, se intercalan dos pasos adicionales entre la solicitud y la respuesta:

  1. solicitud: el navegador del cliente realiza una solicitud al controlador [Faces Servlet].
  2. procesamiento: el controlador C procesa esta solicitud. De hecho, una solicitud POST va acompañada de datos que deben procesarse. Para ello, el controlador cuenta con la ayuda de gestores de eventos específicos de la aplicación [2a]. Estos gestores pueden necesitar la capa de negocio [2b]. El gestor del evento puede tener que actualizar ciertos modelos M [2c]. Una vez procesada la solicitud del cliente, esta puede generar diversas respuestas. Un ejemplo clásico es:
    • una página de errores si la solicitud no se ha podido procesar correctamente
    • una página de confirmación en caso contrario

El gestor de eventos devuelve al controlador [Faces Servlet] un resultado de tipo cadena de caracteres denominado «clave de navegación».

  1. navegación: el controlador selecciona la página JSF (= vista) que se enviará al cliente. Esta selección se realiza a partir de la clave de navegación devuelta por el gestor de eventos.
  2. respuesta: la página JSF seleccionada enviará la respuesta al cliente. Utiliza su plantilla M para inicializar sus partes dinámicas. Esta plantilla también puede recurrir a la capa [métier] [4a] para proporcionar a la página JSF los datos que necesita.

En un proyecto JSF:

  • el controlador C es el servlet [javax.faces.webapp.FacesServlet]. Este se encuentra en la biblioteca [jsf-api.jar].
  • Las vistas V se implementan mediante páginas JSF.
  • Los modelos M y los gestores de eventos se implementan mediante clases Java, a menudo denominadas «backing beans».
  • En las versiones JSF y 1.x, la definición de los beans, así como las reglas de navegación de una página a otra, se definen en el archivo [faces-config.xml]. En él se encuentra la lista de vistas y las reglas de transición de una a otra. A partir de la versión JSF 2, las definiciones de los beans pueden realizarse mediante anotaciones y las transiciones entre páginas pueden definirse de forma «estática» en el código de los beans.

10.2. Funcionamiento de la aplicación

Cuando se accede a la aplicación por primera vez, aparece la siguiente página:

A continuación, se rellena el formulario y se solicita el salario:

Se obtiene el siguiente resultado:

Esta versión calcula un salario ficticio. No hay que prestar atención al contenido de la página, sino a su formato. Al pulsar el botón [Raz], se vuelve a la página [A].

Las entradas erróneas se señalan, tal y como se muestra en el siguiente ejemplo:

10.3. El proyecto NetBeans

Vamos a crear una primera versión de la aplicación en la que se simulará la capa [métier]. Tendremos la siguiente arquitectura:

Cuando los gestores de eventos o los modelos soliciten datos a la capa [métier] [2b, 4a], esta les proporcionará datos ficticios. El objetivo es conseguir una capa web que responda correctamente a las solicitudes del usuario. Una vez logrado esto, solo nos quedará instalar la capa de servidor desarrollada en el apartado 7.1:

Esta será la versión 2 de la versión web de nuestra aplicación PAM.

El proyecto NetBeans de la versión 1 es el siguiente proyecto Maven:

  • en [1], los archivos de configuración
  • en [2], las páginas XHTML y la hoja de estilo
  • en [3], las clases de la capa [web]
  • en [4], los objetos intercambiados entre la capa [web] y la capa [métier] y la propia capa [métier]
  • en [5], el archivo de mensajes para la internacionalización de la aplicación
  • en [6], las dependencias de la aplicación

Repasamos algunos de estos elementos.

10.3.1. Los archivos de configuración

El archivo [web.xml] es el que genera NetBeans por defecto, además de la configuración de una página de excepciones:

<?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">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>
</web-app>
  • línea 30: [index.html] es la página de inicio de la aplicación
  • líneas 32-39: configuración de la página de excepción

La página [exception.html] se ha extraído de [ref3]. Su código es el siguiente:

<?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">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <h3><h:outputText value="#{msg['exception.header']}"/></h3>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['exception.httpCode']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
          <h:outputText value="#{msg['exception.message']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
          <h:outputText value="#{msg['exception.requestUri']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
          <h:outputText value="#{msg['exception.servletName']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
        </h:panelGrid>
      </h:form>
    </h:body>
  </f:view>
</html>

Cualquier excepción que no esté gestionada explícitamente por el código de la aplicación web provocará que se muestre una página similar a la que se muestra a continuación:

El archivo [faces-config.xml] será el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.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-facesconfig_2_0.xsd">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

Cabe destacar los siguientes puntos:

  • líneas 9-14: el archivo [messages.properties] se utilizará para la internacionalización de las páginas. Se podrá acceder a él en las páginas XHTML mediante la clave msg.
  • línea 15: define el archivo [messages.properties] como el que debe consultarse con prioridad para los mensajes de error mostrados por las etiquetas <h:messages> y <h:message>. Esto permite redefinir algunos mensajes de error predeterminados de JSF. Esta posibilidad no se utiliza aquí.

10.3.2. La hoja de estilo

El archivo [styles.css] es el siguiente:


.libelle{
   background-color: #ccffff;
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}
body{
   background-color: #ffccff
}

.error{
   color: #ff3333
}

.info{
   background-color: #99cc00
}

.titreInfos{
   background-color: #ffcc00
}

A continuación se muestran ejemplos de código JSF que utilizan estos estilos:


<h:outputText value="#{msg['form.infos.employé']}"
 styleClass="titreInfos"/>

<h:panelGrid columns="3" 
rowClasses="libelle,info">

<h:message for="heuresTravaillées"
 styleClass="error"/>

10.3.3. El archivo de mensajes

El archivo de mensajes [messages_fr.properties] es el siguiente:


form.titre=Feuille de salaire
form.comboEmployes.libell\u00e9=Employ\u00e9
form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
form.btnSalaire.libell\u00e9=Salaire
form.btnRaz.libell\u00e9=Raz
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
form.infos.employ\u00e9=Informations Employ\u00e9
form.employe.nom=Nom
form.employe.pr\u00e9nom=Pr\u00e9nom
form.employe.adresse=Adresse
form.employe.ville=Ville
form.employe.codePostal=Code postal
form.employe.indice=Indice
form.infos.cotisations=Informations Cotisations sociales
form.cotisations.csgrds=CSGRDS
form.cotisations.csgd=CSGD
form.cotisations.retraite=Retraite
form.cotisations.secu=S\u00e9curit\u00e9 sociale
form.infos.indemnites=Informations Indemnit\u00e9s
form.indemnites.salaireHoraire=Salaire horaire
form.indemnites.entretienJour=Entretien / Jour
form.indemnites.repasJour=Repas / Jour
form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
form.infos.salaire=Informations Salaire
form.salaire.base=Salaire de base
form.salaire.cotisationsSociales=Cotisations sociales
form.salaire.entretien=Indemnit\u00e9s d'entretien
form.salaire.repas=Indemnit\u00e9s de repas
form.salaire.net=Salaire net

Todos estos mensajes se utilizan en la página [index.xhtml], a excepción de los de las líneas 11-15, que se utilizan en la página [exception.xhtml].

10.3.4. El ámbito de los beans

El bean [web.forms.Form] tendrá un ámbito de solicitud:


import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class Form implements Serializable {

El bean [web.utils.ChangeLocale] tendrá un ámbito de aplicación:


package web.utils;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // la configuración regional de las páginas
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  public String setFrenchLocale(){
    locale="fr";
    return null;
  }
  
  public String setEnglishLocale(){
    locale="en";
    return null;
  }

  public String getLocale() {
    return locale;
  }

  public void setLocale(String locale) {
    this.locale = locale;
  }
  
  
}

10.3.5. La capa [métier]

La capa [métier] implementa la siguiente interfaz IMetierLocal:


package metier;

import java.util.List;
import javax.ejb.Local;
import jpa.Employe;

@Local
public interface IMetierLocal {
  // obtener la nómina
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // lista de empleados
  List<Employe> findAllEmployes();
}

Esta interfaz es la que se utiliza en la parte de servidor de la aplicación cliente/servidor descrita en el apartado 7.1.

La clase Metier, que vamos a utilizar para probar la capa [web], implementa esta interfaz de la siguiente manera:


package metier;

...
public class Metier implements IMetierLocal {
  
  // diccionario de empleados indexado por número SS
  private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
  // lista de empleados 
  private List<Employe> listEmployes;
  
  // obtener la nómina
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
    // se recupera el empleado con el n.º SS
    Employe e=hashEmployes.get(SS);
    // se genera una nómina ficticia
    return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100));
  }
  
  // lista de empleados
  public List<Employe> findAllEmployes() {
    if(listEmployes==null){
      // se crea una lista de dos empleados
      listEmployes=new ArrayList<Employe>();
      listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
      listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",new Indemnite(1,1.93,2,3,12)));
      // diccionario de empleados indexado por el n.º SS
      for(Employe e:listEmployes){
        hashEmployes.put(e.getSS(),e);
      }
    }
    // se muestra la lista de empleados
    return listEmployes;
  }
}

Dejamos al lector la tarea de descifrar este código. Cabe destacar el método utilizado: para no tener que implementar la parte EJB de la aplicación, simulamos la capa [métier]. Cuando la capa [web] se declare correcta, podremos sustituirla por la capa real [métier].

10.4. El formulario [index.xhtml] y su plantilla [Form.java]

Ahora creamos la página XHTML del formulario, así como su plantilla.

Lecturas recomendadas en [ref3]:

  • ejemplo n.º 3 (mv-jsf2-03) para la lista de etiquetas que se pueden utilizar en un formulario
  • ejemplo n.º 4 (mv-jsf2-04) para las listas desplegables rellenadas por la plantilla
  • ejemplo n.º 6 (mv-jsf2-06) para la validación de los datos introducidos
  • ejemplo n.º 7 (mv-jsf2-07) para la gestión del botón [Raz]

10.4.1. paso 1


Pregunta: Crea el formulario [index.xhtml] y su modelo [Form.java] necesarios para obtener la siguiente página:


Los componentes de entrada son los siguientes:

id
tipo JSF
plantilla
función
1
comboEmployes
<h:selectOneMenu>
Cadena comboEmployesValue
List<Empleado> getEmployes()
contiene la lista de empleados en el formato
«nombre apellido».
2
heuresTravaillees
<h:inputText>
Cadena heuresTravaillées
número de horas trabajadas - número real
3
joursTravailles
<h:inputText>
Cadena joursTravaillés
Número de días trabajados - número entero
4
btnSalaire
<h:commandButton>
 
inicia el cálculo del salario
5
btnRaz
<h:commandButton>
 
restablece el formulario a su estado inicial
  • El método getEmployes devolverá una lista de empleados que obtendrá de la capa [métier]. Los objetos mostrados por el cuadro combinado tendrán como atributo itemValue el número de empleado SS y, como atributo itemLabel, una cadena formada por el nombre y los apellidos del empleado.
  • Los botones [Salaire] y [Raz] no estarán conectados, por el momento, a ningún gestor de eventos.
  • Se comprobará la validez de los datos introducidos.

Image

Prueba esta versión. Comprueba, en particular, que los errores de introducción de datos se señalen correctamente.

Nota: es importante que los atributos «id» de los componentes de la página no contengan caracteres acentuados. Con Glassfish 3.1.2, esto provoca un fallo en la aplicación.

10.4.2. Paso 2


Pregunta: rellena el formulario [index.xhtml] y su plantilla [Form.java] para obtener la siguiente página una vez que se haya pulsado el botón [Salaire]:


El botón [Salaire] se conectará al gestor de eventos calculerSalaire de la plantilla. Este método utilizará el método calculerFeuilleSalaire de la capa [métier]. Esta nómina se generará para el empleado seleccionado en [1].

En la plantilla, la nómina se representará mediante el siguiente campo privado:


  private FeuilleSalaire feuilleSalaire;

que dispone de los métodos get y set.

Para obtener la información contenida en este objeto, se podrán escribir en la página JSF expresiones como la siguiente:


<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>

La expresión del atributo «value» se evaluará de la siguiente manera:

[form].getFeuilleSalaire().getEmploye().getNom(), donde [form] representa una instancia de la clase [Form.java]. El lector podrá comprobar que los métodos get utilizados aquí existen efectivamente en las clases [Form], [FeuilleSalaire] y [Employe], respectivamente. Si no fuera así, se lanzaría una excepción al evaluar la expresión.

Prueba esta nueva versión.

10.4.3. Paso 3


Pregunta: rellene el formulario [index.xhtml] y su plantilla [Form.java] para obtener la siguiente información adicional:


Seguiremos el mismo procedimiento que antes. Hay una dificultad con el símbolo monetario del euro que aparece, por ejemplo, en [1]. En el marco de una aplicación internacionalizada, sería preferible disponer del formato de visualización y del símbolo monetario de la locale utilizada (en, de, fr, ...). Esto se puede obtener de la siguiente manera:


          <h:outputFormat value="{0,number,currency}">
            <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>

Se podría haber escrito:


          <h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} є">

pero con la configuración regional en_GB (inglés GB) se seguiría mostrando el importe en euros, cuando lo correcto sería utilizar la libra esterlina (£). La etiqueta <h:outputFormat> permite mostrar información en función de la configuración locale de la página JSF que se está visualizando:

  • línea 1: muestra el parámetro {0}, que es un número (number) que representa una cantidad de dinero (currency)
  • línea 2: la etiqueta <f:param> asigna un valor al parámetro {0}. Una segunda etiqueta <f:param> asignaría un valor al parámetro {1}, y así sucesivamente.

10.4.4. paso 4

Lecturas recomendadas: ejemplo n.º 7 (mv-jsf2-07) en [ref3].


Pregunta: completa el formulario [index.xhtml] y su plantilla [Form.java] para gestionar el botón [Raz].


El botón [Raz] devuelve el formulario al estado en el que se encontraba cuando se solicitó por primera vez mediante un GET. Aquí hay varias dificultades. Algunas de ellas se han explicado en [ref3].

El formulario que se obtiene al pulsar el botón [Raz] no es el formulario completo, sino solo la parte saisie del mismo:

Image

Este resultado se puede obtener utilizando una etiqueta <f:subview> de la siguiente manera:


      <f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
... la partie du formulaire qu'on veut pouvoir ne pas afficher
</f:subview>

La etiqueta <f:subview> enmarca toda la parte del formulario que puede mostrarse u ocultarse. Cualquier componente puede mostrarse u ocultarse mediante el atributo rendered. Si rendered="true", el componente se muestra; si rendered="false", no se muestra. Si el atributo rendered toma su valor en la plantilla, entonces la visualización del componente se puede controlar mediante programación.

En el ejemplo anterior, se controlará la visualización de la vista viewInfos con el siguiente campo:


  private boolean viewInfosIsRendered;

junto con sus métodos get y set. Los métodos que gestionan los clics en los botones [Salaire] y [Raz] actualizarán este valor booleano en función de si la vista viewInfos debe mostrarse o no.