Skip to content

7. Aplicación QuiEst

Aquí describimos una aplicación de Struts un poco más sofisticada que las anteriores, las cuales debían ser sencillas para fines didácticos.

7.1. La clase users

Contamos con una clase Java que almacena la información sobre los usuarios de una máquina Unix. Esta información se guarda en tres archivos específicos:

  • /etc/passwd: lista de usuarios
  • /etc/group: lista de grupos
  • /etc/aliases: lista de alias para el correo

El contenido de estos tres archivos es el siguiente:

- /etc/passwd

Las líneas de este archivo tienen el siguiente formato:


    login:pwd:uid:gid:id:dir:shell

con

login
nombre de usuario
pwd
su contraseña cifrada
uid
su número de usuario
gid
su número de grupo
id
su identidad
dir
su directorio de conexión
shell
su intérprete de comandos

Así, la línea de un usuario podría ser la siguiente:

dupond:xg675SDFEkl09:110:57:Guillaume Dupond:/home/iup2-auto/dupond:/bin/bash

El usuario anterior tiene el número 110 y pertenece al grupo 57. La definición del grupo 57 se encuentra en el archivo /etc/group.

- /etc/group

Las líneas de este archivo tienen el siguiente formato:


    nomGroupe:pwd:gid:membre1,membre2,....

con

nomGroupe
nombre del grupo
pwd
su contraseña cifrada —por lo general, este campo está en blanco—
gid
el número del grupo
membrei
nombres de usuario: este campo puede estar vacío

Así, la línea del grupo 57 anterior podría ser la siguiente:

iup2-auto::57:

lo cual indica que el grupo 57 se llama iup2-auto.

- /etc/aliases

Las líneas de este archivo tienen el siguiente formato:

alias:[tab]login

con

alias
alias
[tab]
una o más tabulaciones
login
nombre de usuario de la persona a quien pertenece el alias

Así, la línea


    guillaume.dupond:    dupond

significa que el alias guillaume.dupond pertenece al usuario con nombre de inicio de sesión dupond. Recordemos que los alias se utilizan en las direcciones de correo electrónico. Así, si en el ejemplo anterior la máquina Unix se llama shiva.istia.univ-angers.fr, un correo dirigido a guillaume.dupond@shiva.istia.univ-angers.fr se entregará en el buzón del usuario de inicio de sesión dupond de esa máquina.

Aquí no nos ocuparemos de toda la interfaz de la clase users, sino simplemente de su constructor y algunos métodos:

import java.io.*;
import java.util.*;

public class users{


   // atributos
  private Hashtable usersByLogin=new Hashtable();       // inicio de sesión --> nombre de usuario, contraseña, ..., directorio
    private ArrayList erreurs=new ArrayList();             // lista de mensajes de error

....

   // constructor
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: nombre del archivo de usuarios con líneas del tipo
         // nombre de usuario:contraseña:uid:gid:id:directorio:shell
         // groupsFileName: nombre del archivo de grupos con líneas del tipo
         // nombre:contraseña:número:miembro1,miembro2,..
         // aliasesFileName: nombre del archivo de alias con líneas del tipo
         // alias:[tab]login
         // crea el diccionario usersByLogin
....
    }// constructor

     // lista de usuarios
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // errores
  public ArrayList getErreurs(){
    return erreurs;
  }
usersByLogin
diccionario (tabla hash) cuyas claves son los nombres de usuario del archivo passwd. El valor asociado a la clave es un array de cadenas (String [7]) cuyos elementos son los 7 campos de la línea del archivo passwd asociada al nombre de usuario. Algunos campos pueden estar vacíos si la línea tiene menos de 7 campos.
erreurs
lista de mensajes de error: vacía si no hay errores

7.2. La aplicación web que es

Nos proponemos desarrollar la siguiente aplicación web (página de formulario):

N.º
nombre
tipo HTML
función
1
cmbLogins
<select ...>...</select>
muestra la lista de todos los usuarios sobre los que se puede solicitar información
2
btnChercher
<input type="submit" ...>
para iniciar la búsqueda

Cuando el usuario presiona el botón [Chercher] (2), se solicita el inicio de sesión de (1) a un objeto U de tipo usuarios. Si el inicio de sesión existe, se obtiene la siguiente respuesta (página de información):

Como lo muestra el URL del navegador arriba, los parámetros del formulario se envían al servidor mediante un GET. Por lo tanto, podemos proporcionar directamente al navegador este URL con los parámetros configurados. Eso es lo que hacemos aquí, para introducir un nombre de usuario que no existe. Obtenemos la siguiente respuesta (página de errores):

7.3. La arquitectura de la aplicación

En esta arquitectura encontramos los siguientes componentes:

  • las vistas:
    • logins.jsp, utilizada para mostrar la lista de inicios de sesión (vista 1)
    • infos.jsp, utilizada para mostrar la información relacionada con un inicio de sesión (vista 2)
    • erreurs.jsp, utilizada para mostrar una lista de errores (vista 3)
  • los formularios de tipo ActionForm utilizados por las acciones:
    • formLogins, que se utiliza para recopilar los datos del formulario logins.jsp
  • las acciones:
    • SetupLoginAction, que prepara el contenido de formulaire.jsp y luego muestra esta vista
    • InfosLoginAction, que procesa el contenido de logins.jsp una vez que este se envía al servidor
    • ForwardAction, que procesa el enlace [Retour vers le formulaire] de las vistas infos.jsp y erreurs.jsp
  • la clase de negocio «users» utilizada por las acciones para obtener sus datos
  • el modelo proporcionado por los tres archivos planos «passwd», «group» y «aliases»

7.4. Los archivos de configuración de la aplicación web

7.4.1. El archivo server.xml

El contexto de la aplicación se llamará /strutsquiest2. Por lo tanto, agregaremos la siguiente línea en el archivo server.xml de Tomcat:

    <Context path="/strutsquiest2" docBase="..." />

Una vez hecho esto, reiniciamos Tomcat si es necesario para que reconozca el nuevo contexto. Podemos verificar su validez solicitando el archivo URL http://localhost:8080/strutsquiest2.

7.4.2. El archivo web.xml

El archivo de configuración web.xml de la aplicación será el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
    <servlet>
        <servlet-name>strutsquiest2</servlet-name>
        <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>            
        </init-param>
        <init-param>
            <param-name>passwdFileName</param-name>
            <param-value>data/passwd</param-value>            
        </init-param>
        <init-param>
            <param-name>groupFileName</param-name>
            <param-value>data/group</param-value>            
        </init-param>        
    </servlet>

    <servlet-mapping>
        <servlet-name>strutsquiest2</servlet-name>
        <url-pattern>*.do</url-pattern>        
    </servlet-mapping>
</web-app>

Este archivo web.xml presenta una novedad. El controlador Struts ya no es org.apache.struts.action.ActionServlet, sino una clase derivada a la que aquí hemos llamado istia.st.struts.quiest.Quiest2ActionServlet. Esto nos permitirá recuperar los dos parámetros de inicialización: passwdFileName (ubicación del archivo passwd) y groupFileName (ubicación del archivo group). El archivo aliases no es necesario en esta aplicación.

7.4.3. El archivo struts-config.xml

El archivo struts-config.xml será el siguiente:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <form-beans>
        <form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
            <form-property name="cmbLogins" type="java.lang.String" initial=""/>
            <form-property name="tLogins" type="java.lang.String[]"/>            
        </form-bean>            
    </form-beans>

    <action-mappings>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

    </action-mappings>

        <message-resources 
          parameter="istia.st.struts.quiest.ApplicationResources"
    null="false"
  />    

</struts-config>

Aquí se encuentran las tres secciones principales:

  • la declaración de los formularios en la sección <form-beans>
  • la declaración de las acciones en la sección <action-mappings>
  • la declaración del archivo de recursos en <message-ressources>

7.4.4. Los objetos (beans) de formularios de la aplicación

    <form-beans>
        <form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
            <form-property name="cmbLogins" type="java.lang.String" initial=""/>
            <form-property name="tLogins" type="java.lang.String[]"/>            
        </form-bean>            
    </form-beans>    

Solo hay un bean de formulario en nuestra aplicación, llamado formLogins y de tipo derivado de DynaActionForm. Se utilizará en las siguientes situaciones:

  • contener los datos necesarios para mostrar la vista n.º 1
  • recuperar los valores del formulario de la vista n.º 1 cuando el usuario lo valide (submit)

La estructura del bean formLogins está vinculada al formulario de la vista n.º 1. Analicémosla:

n.º
nombre
tipo HTML
función
1
cmbLogins
<select ...>...</select>
muestra la lista de todos los usuarios sobre los que se puede solicitar información
2
btnChercher
<input type="submit" ...>
para iniciar la búsqueda

Distinguamos varios casos:

  • del cliente al servidor, el objeto formLogins se utiliza para contener los valores del formulario HTML anterior, que se enviará mediante el botón [Envoyer]. Por lo tanto, necesita un campo cmbLogins que recibirá el valor del campo HTML, cmbLogins y c.a.d, es decir, el nombre de usuario elegido por el usuario.
  • Del servidor al cliente, se utiliza el objeto formLogins para proporcionar el contenido inicial de la vista n.º 1. Su campo tLogins servirá como contenido para la lista 1. Su campo cmbLogins permitirá establecer el elemento de la lista 1 que se debe seleccionar.

7.4.5. Las acciones de la aplicación

Las acciones son manejadas por objetos de tipo Action o derivados. La configuración de las acciones se realiza dentro de las etiquetas <action-mappings>:

    <action-mappings>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

    </action-mappings>

La acción /init

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

Describamos cómo funciona la acción /init:

  • La acción /init normalmente se lleva a cabo una sola vez durante el primer ciclo de solicitud-respuesta, en el que el usuario solicita el URL http://localhost:8080/strutsquiest2/init.do
  • se crea o recicla el objeto formsLogins. Se recupera (reciclaje) o se coloca (creación) en la sesión según lo indique el atributo scope.
  • Se invoca su método reset. Recordemos que este método no hace nada por defecto en las clases ActionForm y derivadas. Se invoca justo antes de copiar los datos de la solicitud del cliente al objeto ActionForm y sirve para limpiar el objeto antes de dicha copia. ¿Cuál es aquí la solicitud del cliente? La acción /init se activa cuando la URL solicitada es http://localhost:8080/strutsquiest2/init.do. Este URL puede ser solicitado por un GET o un POST. Basta con incluir en esta solicitud parámetros con los nombres de los campos de formLogins para que estos se inicialicen como se muestra en el siguiente ejemplo:

Image

  • La solicitud contiene el parámetro cmbLogins (afterpak). Por lo tanto, el controlador Struts ha copiado el valor de este parámetro en el campo cmbLogins de formLogins. A continuación, se ejecutó la acción SetupLoginsAction, que concluyó con la visualización de la vista logins.jsp. Esta vista cuenta con un formulario cuyos campos reciben sus valores de formLogins. Así, el campo de selección HTML, denominado cmbLogins, recibió su valor del campo cmbLogins (=afterpak) de formLogins. Por eso, la lista de inicios de sesión aparece posicionada en el inicio de sesión afterpak.
  • Podríamos divertirnos pasando también un parámetro tLogins de la siguiente manera:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Esto tendría como efecto inicializar el campo tLogins de formLogins con un array {"login1", "login2"}. Sin embargo, veremos más adelante que la acción SetupLoginsAction asigna un valor al campo tLogins y reemplaza la matriz así creada por una nueva matriz. Es esta última la que aparece, por lo tanto, en la vista logins.jsp.

  • La explicación anterior, aunque un poco compleja, tiene el mérito de mostrar que no se puede suponer que la acción /init se activará sin parámetros provenientes del cliente. Por lo tanto, puede ser útil utilizar el método reset para limpiar formLogins. En este caso, tendríamos que derivar la clase DynaActionForm. No lo hemos hecho aquí.
    • Una vez que se invoca el método reset de formLogins, el controlador copia los datos de la solicitud del cliente en los campos del mismo nombre de formLogins. Normalmente, la acción /init se invoca sin parámetros del cliente, pero ya hemos mostrado anteriormente que nada impide que el cliente invoque la acción /init con parámetros arbitrarios. Al final de esta fase, los campos cmbLogins y tLogins pueden, por lo tanto, tener un valor. Hemos visto que el campo cmbLogins conservaría este valor, pero no el campo tLogins.
    • A continuación, el controlador revisa el atributo `validate` de la acción. En este caso, tiene el valor «false». El método `validate` de formLogins no se llamará. Por lo tanto, no lo escribiremos.
    • Se crea el objeto SetupLoginsAction, o se recicla si ya existía, y se ejecuta su método `execute`. Su única función es asignar un valor al campo tLogins de formLogins. Este valor es la tabla de inicios de sesión, la cual se solicitará a la clase de negocio «users». Esta operación puede fallar. Por eso, la acción /init puede ir seguida de dos vistas:
      • la vista erreurs.jsp si la clase «users» no pudo proporcionar la tabla de inicios de sesión
      • la vista logins.jsp en caso contrario
    • el controlador mostrará una de estas dos vistas
    • el ciclo de solicitud-respuesta de la acción /init ha finalizado.

La acción /infosLogin

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

Describamos cómo funciona la acción / infosLogin:

  • La acción /infosLogin se ejecuta normalmente cuando el usuario hace clic en el botón [Chercher] de la vista logins.jsp. A continuación, se envía una solicitud al servidor, definida por la etiqueta HTML <form> de la vista:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • Se observa que la solicitud se envía al servidor mediante el método GET. Por lo tanto, el usuario puede escribirla manualmente:

Image

  • Se crea o recicla el objeto formsLogins. Se recupera (reciclaje) o se coloca (creación) en la sesión según lo indique el atributo scope.
  • Su método reset se invoca justo antes de copiar los datos de la solicitud del cliente en el objeto ActionForm. Normalmente tiene el formato http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, donde xx es un nombre de usuario seleccionado de la lista de nombres de usuario. Pero también puede ser cualquier cosa si el usuario ha utilizado el URL anterior pasando parámetros arbitrarios. Consideremos la siguiente secuencia de páginas:

Image

  • La acción /infosLogin se invocó con la cadena de parámetros cmbLogins=xx&tLogins=login1&tLogins=login2. Por lo tanto, los campos cmbLogins y tLogins de formLogins recibirán, respectivamente, los valores «xx» y {"login1", "login2"}. La acción /infosLogin solicitará a la clase de negocio «users» la información asociada al inicio de sesión «xx». La clase «users» le responderá que ese inicio de sesión no existe. De ahí la vista mostrada anteriormente. Ahora, utilicemos el enlace [Retour au formulaire] anterior:

Image

  • La acción /retourLogins es la que se activa mediante el enlace [Retour au formulaire]. Esta acción simplemente muestra la vista logins.jsp sin ninguna acción intermedia. Recordemos que el campo tLogins sirve para alimentar la lista de nombres de usuario de la vista logins.jsp. Como el usuario ha cambiado este valor a {"login1","login2"}, son estos dos nombres de usuario los que ahora aparecen en la lista. Una vez más, no podemos dejar de insistir en la necesidad absoluta de tomar en cuenta, en el funcionamiento de una aplicación, el caso de los parámetros arbitrarios establecidos por un usuario o un programa. La solución al problema planteado aquí sería que el enlace [Retour au formulaire] apuntara a la acción /init. De esta manera, estaríamos seguros de obtener la lista correcta de nombres de usuario.
  • Volvamos a una solicitud normal a la acción /infosLogin del tipo:

http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak

  • El controlador Struts asignará un valor al campo cmbLogins del objeto ActionForm. En cambio, al campo tLogins no se le asignará ningún valor (no hay un campo correspondiente en la solicitud enviada). Este comportamiento nos conviene. Por lo tanto, no tendremos que escribir un método reset personalizado para formLogins.
    • Una vez que se llama al método reset de formLogins, el controlador copia los datos de la solicitud del cliente en los campos del mismo nombre de formLogins. El campo cmbLogins recibirá un valor: el nombre de usuario elegido por el usuario (afterpak).
    • A continuación, el controlador revisa el atributo `validate` de la acción. En este caso, tiene el valor «false». No se llamará al método `validate` de formLogins.
    • Se crea el objeto InfosLoginAction o se recicla si ya existía, y se ejecuta su método «execute». Su función es obtener la información asociada al nombre de usuario cmbLogins. Esta información se solicitará a la clase de negocio «users». Esta operación puede fallar (por ejemplo, si el nombre de usuario no existe). Por eso, la acción /infosLogin puede ir seguida de dos vistas:
      • la vista erreurs.jsp si la clase «users» no pudo proporcionar la información solicitada
      • la vista infos.jsp en caso contrario
    • el controlador mostrará una de estas dos vistas
    • el ciclo de solicitud-respuesta de la acción /infosLogin ha finalizado.

La acción /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • La acción /retourLogins se activa al hacer clic en el enlace [Retour au formulaire] de las vistas erreurs.jsp y infos.jsp.
  • Aquí no hay ningún formulario asociado a la acción. Por lo tanto, se pasa inmediatamente a la ejecución del método `execute` de un objeto `ForwardAction`, el cual devolverá un objeto `ActionForward` que apunta a la vista `/vues/logins.jsp`.

7.4.6. El archivo de mensajes de la aplicación

La tercera sección del archivo struts-config.xml corresponde al archivo de mensajes:

        <message-resources 
      parameter="istia.st.struts.quiest.ApplicationResources"
    null="false"
  />        

El archivo ApplicationResources.properties se encuentra en WEB-INF/classes/istia/st/struts/quiest. Su contenido es el siguiente:

errors.header=<ul>
errors.footer=</ul>
parametreManquant=<li>Le paramètre [{0}] n'a pas été initialisé</li>
usersException=<li>Erreur d'initialisation de l'application : {0}</li>
loginInconnu=<li>Le login [{0}] n'existe pas</li>

7.5. El código de las vistas

Se recomienda al lector que vuelva a leer la lección sobre la gestión de formularios si no comprende el código de las vistas que se presentan a continuación.

7.5.1. La vista logins.jsp

Recordemos que esta vista se muestra en dos casos:

  • al llamar a la acción /init durante el primer ciclo de solicitud-respuesta
  • al llamar a la acción /retourLogins en los ciclos siguientes

El código de la vista logins.jsp es el siguiente:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Quiest - formulaire</title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <center>
        <h2>Application QuiEst</h2>
      <hr>
      <html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
          <table>
            <tr>
              <td>Login cherché</td>
            <td>
                <html:select name="formLogins" property="cmbLogins">
                      <html:options name="formLogins" property="tLogins"/>
                  </html:select>
            </td>
            <td>
                <html:submit value="Chercher"/>
            </td>
          </tr>
        </table>
      </html:form>
    </center>
  </body>
</html>

7.5.2. La vista infos.jsp

Esta vista se muestra cuando se realiza con éxito una llamada a la acción /infosLogin. Su código es el siguiente:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
    <head>
      <title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
    <hr>
    <table border="1">
            <tr>
                <th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
            </tr>
            <tr>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>            
            </tr>                                                                                                                                                                                                                
    </table>                                      
    <br>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>        
  </body>
</html>

Esta vista utiliza un objeto llamado infosLoginBean, que se inserta en la consulta mediante la acción /infosLogin. Este objeto tiene dos campos:

String titre;                        // título que se mostrará en la vista
String[] infosLogin;        // tabla de información que se mostrará en la vista

Detallaremos esta clase cuando abordemos el código de la clase InfosLoginAction.

7.5.3. La vista erreurs.jsp

Esta vista se muestra cuando las acciones /init o /infosLogin terminan con un error. Su código es el siguiente:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Application QuiEst - erreurs</title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
        <h2 align="center">Application QuiEst - Erreurs</h2>
        <hr>
      <h2>Les erreurs suivantes se sont produites</h2>
        <html:errors/>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>    
  </body>
</html>

7.6. Las clases de Java

El archivo web.xml hace referencia a una clase de Java:

<web-app>
    <servlet>
      <servlet-name>strutsquiest2</servlet-name>
    <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
....
  </servlet>

...
</web-app>

El archivo de configuración struts-config.xml hace referencia a dos clases de Java:

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

7.6.1. La clase Quiest2ActionServlet

La clase Quiest2ActionServlet deriva de la clase ActionServlet, la clase del controlador Struts. Derivamos la clase ActionServlet para personalizar su método init. De hecho, este método, que se ejecuta una sola vez al momento de la carga inicial del servlet, nos permitirá construir un objeto de negocio de tipo users. Este objeto solo necesita construirse una vez y el método init es un buen lugar para realizar esta construcción. El objeto «users» necesita dos archivos para crearse: los archivos «passwd» y «group». La ubicación de estos dos archivos se pasa como parámetros a la servlet en el archivo web.xml de la aplicación:

    <servlet>
      <servlet-name>strutsquiest2</servlet-name>
    <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
        <param-name>passwdFileName</param-name>
      <param-value>data/passwd</param-value>
    </init-param>
    <init-param>
        <param-name>groupFileName</param-name>
      <param-value>data/group</param-value>
    </init-param>        
  </servlet>

El código del servlet es el siguiente:

package istia.st.struts.quiest;

import java.util.*;
import javax.servlet.*;
import org.apache.struts.action.*;
import istia.st.users.*;

public class Quiest2ActionServlet
  extends ActionServlet {

   // atributos del servlet
  private users u = null;
  private ActionErrors erreurs = new ActionErrors();
  private String[] tLogins;

   //init
  public void init() throws ServletException {

    // no olvides inicializar la clase padre
    super.init();

     // variables locales
    final String[] initParams = {"passwdFileName", "groupFileName"};
    Properties params = new Properties();

    // se recuperan los parámetros de inicialización del servlet
    ServletConfig config = getServletConfig();
    String servletPath = config.getServletContext().getRealPath("/");
    for (int i = 0; i < initParams.length; i++) {
      String valeur = config.getInitParameter(initParams[i]);
      if (valeur == null) {
        erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("parametreManquant", initParams[i]));
        valeur = "";
      }
       // se almacena el parámetro
      params.setProperty(initParams[i], valeur);
    } //for
     // salir si hubo errores de inicialización
    if (erreurs.size() != 0) {
      return;
    }
     // se crea un objeto «users»
    try {
      u = new users(servletPath + "/" + params.getProperty("passwdFileName"),
                    servletPath + "/" + params.getProperty("groupFileName"), null);
    }
    catch (Exception ex) {
      erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("usersException", ex.getMessage()));
      return;
    } //catch
     // se recupera la lista de inicios de sesión
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // se ordenan los nombres de usuario
    Arrays.sort(tLogins);
  } //init

   // método de acceso a la información privada del servlet
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

En resumen, el funcionamiento del método init es el siguiente:

  • en primer lugar, se llama al método `init` de la clase padre (ActionServlet) para que esta se inicialice correctamente
  • luego se leen los parámetros de inicialización. Si faltan algunos, se llena el atributo privado ActionErrors errores.
  • Si los parámetros de inicialización están presentes, se crea un objeto «users». Esta creación puede generar una excepción. En ese caso, se asigna un valor al atributo ActionErrors «errores».
  • Si la creación se realizó correctamente, se extrae del objeto creado la lista de todos los nombres de usuario y se ordena en un array que se almacena en el atributo privado String[] tLogins.
  • El objeto «users» creado se almacena en el atributo privado «users u».
  • El método público getInfos permite obtener los tres atributos privados (u, errores, tLogins) en una matriz de objetos.

7.6.2. La clase SetupLoginsAction

El objetivo de esta acción es inicializar el objeto DynaActionForm formLogins. Este objeto, ubicado en la sesión, ya no necesitará ser reinicializado posteriormente. Por lo tanto, la acción SetupLoginsAction solo se ejecuta una vez. Su código es el siguiente:

package istia.st.struts.quiest;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;

public class SetupLoginsAction
  extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {

     // prepara el formulario para mostrarlo
     // se recupera la información del servlet controlador
     // información=(ActionErrors errores, usuarios u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // ¿Hubo errores de inicialización?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // se ingresan los datos de inicio de sesión en el formulario
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

Al igual que con todas las acciones de Struts, el código se encuentra en el método execute. Este:

  • obtiene del controlador Struts la información que este ha almacenado mediante su método init. El método getServlet() de la clase Action es el que permite esto.
  • Entre ellas se encuentra el atributo ActionErrors «errores del controlador». Si esta lista de errores no está vacía, se incluye en la consulta y se solicita que se muestre la vista erreurs.jsp.
  • Si la lista de errores está vacía, entonces se asigna al campo tLogins del bean formLogins la lista de inicios de sesión creada inicialmente por el controlador. A continuación, se solicita la visualización de la vista logins.jsp, que mostrará la lista de inicios de sesión.

7.6.3. Las clases InfosLoginBean y InfosLoginAction

La acción InfosLoginAction tiene como objetivo recuperar la información asociada al inicio de sesión elegido por el usuario y presentársela. La información se recopilará en un objeto de tipo InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean que contiene la información necesaria para la página de información
  private String titre;
  private String[] infosLogin;

  // constructor
  public InfosLoginBean(String titre, String[] infosLogin){
    this.titre=titre;
    this.infosLogin=infosLogin;
  }

   // getter
  public String getTitre(){
    return this.titre;
  }
  public String[] getInfosLogin(){
    return this.infosLogin;
  }
  public String getInfosLogin(int i){
    return this.infosLogin[i];
  }
}

La clase anterior es un bean, c.a.d. Una clase Java en la que un atributo privado T unAttribut se asocia automáticamente a dos métodos privados:

  • void setUnAttribut(T valor){unAttribut=valor;}
  • T getUnAttribut(){ return unAttribut;}

Cabe destacar la sintaxis especial de los métodos get y set. Si el atributo es un array T[] unAttribut, se pueden crear métodos get y set para los elementos del array:

  • void setUnAttribut(T valor, int i){unAttribut[i]=valor;}
  • T getUnAttribut(int i){ return unAttribut[i];}

Para entenderlo mejor, revisemos el código de la vista infos.jsp, que debe enviarse tras la acción InfosLoginAction:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
    <head>
      <title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
    <hr>
    <table border="1">
            <tr>
                <th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
            </tr>
            <tr>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>            
            </tr>                                                                                                                                                                                                                
    </table>                                      
    <br>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>        
  </body>
</html>

Tomemos la siguiente etiqueta:

<bean:write name="infosLoginBean" scope="request" property="titre"/>

Solicita escribir el valor del campo «título» (property) del objeto infosLoginBean (name) incluido en la consulta (scope). El valor que se escribirá se obtendrá mediante request.getAttribute("infosLoginBean").getTitre(). Por lo tanto, es necesario que el método getTitre exista en la clase InfosLoginBean. Así es. La etiqueta

<bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/>

solicita escribir el valor del elemento infosLogin[0] del objeto infosLoginBean incluido en la consulta. El valor que se debe escribir se obtendrá mediante request.getAttribute("infosLoginBean").getInfosLogin(0). Por lo tanto, es necesario que el método getInfosLogin(int i) exista en la clase InfosLoginBean. Así es.

La clase InfosLoginAction tiene como objetivo construir el objeto InfosLoginBean anterior a partir de un nombre de usuario elegido por el usuario. Su código es el siguiente:

package istia.st.struts.quiest;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import istia.st.users.*;

public class InfosLoginAction
  extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {

     // debe mostrar la información relacionada con un inicio de sesión

     // se recupera la información del servlet controlador
     // información=(ActionErrors errores, usuarios u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // ¿Hubo errores de inicialización?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // Primero, recupera este inicio de sesión
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // ¿Hay algo?
    if (login == null) {
      // Esto no está bien; vamos a reenviar el formulario de inicio de sesión
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // Tenemos un nombre de usuario; lo estamos buscando
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // ¿Lo hemos encontrado?
    if (infosLogin == null) {
      // No se encontró el nombre de usuario: se muestra la página de errores
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // se ha encontrado el nombre de usuario: se colocan los datos encontrados en la solicitud
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

El funcionamiento del método **execute** es el siguiente:

  • se recupera la información recopilada por el controlador Struts durante su inicialización. Si este hubiera detectado errores, la ejecución se detiene ahí y se solicita que se muestren dichos errores.
  • Se verifica que haya un nombre de usuario. Si el usuario ha pasado por el formulario de selección de nombre de usuario, este estará presente. Sin embargo, el usuario también puede escribir la URL de la acción directamente en su navegador sin pasar parámetros. Si no hay un nombre de usuario, se vuelve a mostrar la lista de nombres de usuario.
  • Si hay un nombre de usuario, se solicitan los datos asociados a la clase de negocio «users». Si esta no encuentra el nombre de usuario buscado, se muestra la página de errores. De lo contrario, se crea un objeto InfosLoginBean para incluir la información que necesita la vista infos.jsp. Este objeto se incluye en la solicitud y se muestra la página infos.jsp.

7.7. Implementación

La estructura de la aplicación es la siguiente:

 

7.8. Conclusión

Hemos utilizado Struts en una aplicación realista que emplea una clase de negocio. Además, hemos demostrado que es necesario prestar especial atención a la solicitud enviada por un cliente y no hacer ninguna suposición sobre su naturaleza. Una solicitud puede ser de cualquier tipo y toda aplicación debe comenzar por verificar su validez.