7. Aplicación QuiEst
Aquí describimos una aplicación Struts un poco más sofisticada que las anteriores, que debían ser sencillas para fines didácticos.
7.1. La clase users
Disponemos de una clase Java que almacena la información sobre los usuarios de una máquina Unix. Esta información se guarda en tres archivos concretos:
- /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
nombre de usuario | |
su contraseña cifrada | |
su número de usuario | |
su número de grupo | |
su identidad | |
su directorio de conexión | |
su intérprete de comandos |
Así, la línea de un usuario podría ser la siguiente:
El usuario anterior tiene el n.º 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
nombre del grupo | |
su contraseña cifrada: normalmente este campo está vacío | |
el número del grupo | |
nombres de usuario: este campo puede estar vacío |
Así, la línea del grupo 57 anterior podría ser la siguiente:
lo que indica que el grupo 57 se llama iup2-auto.
- /etc/aliases
Las líneas de este archivo tienen el siguiente formato:
con
alias | |
una o varias tabulaciones | |
nombre de usuario al que pertenece el alias |
Así, la línea
guillaume.dupond: dupond
significa que el alias guillaume.dupond pertenece al usuario 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 de algunos métodos:
import java.io.*;
import java.util.*;
public class users{
// attributs
private Hashtable usersByLogin=new Hashtable(); // login --> login, pwd, ..., dir
private ArrayList erreurs=new ArrayList(); // lista de mensajes de error
....
// constructeur
public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
// usersFileName: nombre del archivo de usuarios con líneas del tipo
// login:pwd:uid:gid:id:dir: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
....
}// constructeur
// lista de usuarios
public Hashtable getUsersByLogin(){
return usersByLogin;
}
// erreurs
public ArrayList getErreurs(){
return erreurs;
}
diccionario (tabla hash) cuyas claves son los nombres de usuario del archivo passwd. El valor asociado a la clave es una matriz 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. | |
lista de mensajes de error: vacía si no hay errores |
7.2. La aplicación web que es
Nos proponemos crear 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 inicios de sesión sobre los que se puede solicitar información |
2 | btnChercher | <input type="submit" ...> | para iniciar la búsqueda |
Cuando el usuario pulsa 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 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 configurado. Eso es lo que hacemos aquí, para introducir un inicio de sesión 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, utilizado para recopilar los datos del formulario logins.jsp
- las acciones:
- SetupLoginAction, que prepara el contenido de formulaire.jsp y, a continuación, muestra esta vista
- InfosLoginAction, que procesa el contenido de logins.jsp una vez que este se ha enviado 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, añadiremos la siguiente línea en el archivo server.xml de Tomcat:
Una vez hecho esto, reiniciamos Tomcat para que tenga en cuenta el nuevo contexto. Podemos comprobar su validez solicitando el 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 aporta 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, que son passwdFileName (ubicación del archivo passwd) y groupFileName (ubicación del archivo group). El archivo aliases es innecesario 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í encontramos 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-resources>
7.4.4. Los objetos (beans) de formulario 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émoslo:
![]() |
n.º | nombre | tipo HTML | función |
1 | cmbLogins | <select ...>...</select> | muestra la lista de todos los inicios de sesión 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, 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á fijar el elemento de la lista 1 que se va a seleccionar.
7.4.5. Las acciones de la aplicación
Las acciones son gestionadas 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 el funcionamiento de la acción /init:
- la acción /init se ejecuta normalmente 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 en el 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 el url solicitado es http://localhost:8080/strutsquiest2/init.do. Esta URL puede ser solicitada por una GET o una POST. Basta con incluir en esta solicitud parámetros con los nombres de los campos de formLogins para que estos se inicialicen, tal y como se muestra en el siguiente ejemplo:

- la consulta 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 tiene 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:
Esto tendría como efecto inicializar el campo tLogins de formLogins con una matriz {"login1","login2"}. Sin embargo, veremos más adelante que la acción SetupLoginsAction asigna un valor al campo tLogins y sustituye la matriz así creada por una nueva matriz. Es esta última la que aparece, por tanto, en la vista logins.jsp.
- El análisis anterior, aunque algo complejo, tiene el mérito de demostrar que no se puede dar por sentado que la acción /init se activará sin parámetros procedentes del cliente. Por lo tanto, puede resultar ú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 llamado 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 hemos visto 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 comprueba 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, tabla que 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 ha podido 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 el funcionamiento de 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:

- 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 la forma http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, donde xx es un nombre de usuario elegido 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:

- Se ha llamado a la acción /infosLogin 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 enviada anteriormente. Ahora, utilicemos el enlace [Retour au formulaire] anterior:

- Es la acción /retourLogins la que se activa mediante el enlace [Retour au formulaire]. Esta acción se limita a mostrar la vista logins.jsp sin ninguna acción intermedia. Recordemos que el campo tLogins sirve para alimentar la lista de inicios de sesión de la vista logins.jsp. Como el usuario ha cambiado este valor a {"login1","login2"}, son estos dos inicios de sesión los que aparecen ahora en la lista. Una vez más, no podemos sino insistir en la necesidad absoluta de tener en cuenta, en el funcionamiento de una aplicación, el caso de los parámetros arbitrarios fijados 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 este modo, estaríamos seguros de obtener la lista correcta de inicios de sesión.
- 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. El campo tLogins, por su parte, no recibirá ningún valor (no hay ningún campo correspondiente en la solicitud enviada). Este funcionamiento nos conviene. Por lo tanto, no tendremos que escribir un método reset personalizado para formLogins.
- Una vez llamado el 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 comprueba 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 ha podido 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, que 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 es la del archivo de mensajes:
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 tras una llamada exitosa 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 denominado 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 finalizan 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 Java
El archivo web.xml hace referencia a una clase 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 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 durante 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 al 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
// retorno si se han producido 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 inicios de sesión
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 falta alguno, se rellena el atributo privado ActionErrors errores.
- Si los parámetros de inicialización están presentes, se crea un objeto users. Esta creación puede lanzar una excepción. En ese caso, se rellena el atributo ActionErrors errores.
- Si la creación se ha realizado correctamente, se extrae del objeto creado la lista de todos los inicios de sesión y se ordena en una matriz que se coloca 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, una vez colocado 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 recuperan los datos del servlet controlador
// infos=(ActionErrors erreurs, users u, String[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// ¿Ha habido errores de inicialización?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// se introducen 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 esta información se encuentra el atributo ActionErrors de errores del controlador. Si esta lista de errores no está vacía, se incluye en la solicitud y se solicita la visualización de la vista erreurs.jsp.
- Si la lista de errores está vacía, 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;
// constructeur
public InfosLoginBean(String titre, String[] infosLogin){
this.titre=titre;
this.infosLogin=infosLogin;
}
// getters
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 una matriz T[] unAttribut, se pueden crear métodos get y set para los elementos de la matriz:
- void setUnAttribut(T valor, int i){unAttribut[i]=valor;}
- T getUnAttribut(int i){ return unAttribut[i];}
Para entenderlo mejor, repasemos 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:
Solicita escribir el valor del campo título (property) del objeto infosLoginBean (name) incluido en la consulta (scope). El valor que se debe 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
solicita escribir el valor del elemento infosLogin[0] del objeto infosLoginBean incluido en la consulta. El valor que se va a 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
// infos=(ActionErrors erreurs, users u, LoginBean[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// ¿Ha habido errores de inicialización?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// ¿Hay que recuperar primero este nombre de usuario?
String login = (String) ( (DynaActionForm) form).get("cmbLogins");
// ¿Tenemos algo?
if (login == null) {
// no es normal; volvemos a enviar el formulario de nombres de usuario
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 ha encontrado 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 inicio de sesión - se introducen 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 comprueba que haya un inicio de sesión. Si el usuario ha pasado por el formulario de selección de inicio de sesión, este estará presente. Pero el usuario puede introducir el url de la acción directamente en su navegador sin pasar parámetros. Si no hay inicio de sesión, se vuelve a mostrar la lista de inicios de sesión.
- 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 introducir la información que necesita la vista infos.jsp. Este objeto se incluye en la consulta 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 hay que 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.











