Skip to content

7. Applicazione QuiEst

Qui descriviamo un'applicazione Struts leggermente più sofisticata rispetto alle precedenti, che erano state mantenute semplici a scopo didattico.

7.1. La classe Users

Abbiamo una classe Java che memorizza le informazioni relative agli utenti di una macchina Unix. Queste informazioni sono memorizzate in tre file specifici:

  • /etc/passwd: elenco degli utenti
  • /etc/group: elenco dei gruppi
  • /etc/aliases: elenco degli alias e-mail

Il contenuto di questi tre file è il seguente:

- /etc/passwd

Le righe in questo file hanno il seguente formato:


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

dove

login
login utente
password
la loro password crittografata
uid
ID utente
gid
ID del suo gruppo
ID
la sua identità
dir
la sua directory di accesso
shell
la sua shell

Pertanto, la riga di un utente potrebbe apparire così:

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

L'utente precedente ha l'ID 110 e appartiene al gruppo 57. La definizione del gruppo 57 si trova nel file /etc/group.

- /etc/group

Le righe in questo file hanno il seguente formato:


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

con

nomeGruppo
nome del gruppo
pwd
password crittografata - questo campo è solitamente vuoto
gid
ID del gruppo
membrei
Accessi utente - questo campo può essere vuoto

Pertanto, la riga relativa al gruppo 57 sopra riportato potrebbe essere la seguente:

iup2-auto::57:

il che indica che il gruppo 57 è denominato iup2-auto.

- /etc/aliases

Le righe in questo file hanno il seguente formato:

alias:[tab]login

con

alias
alias
[tab]
una o più tabulazioni
login
il login dell'utente a cui appartiene l'alias

Quindi, la riga


    guillaume.dupond:    dupond

significa che l'alias guillaume.dupond appartiene all'utente con nome di accesso dupond. Ricorda che gli alias sono utilizzati negli indirizzi e-mail. Quindi, se nell'esempio precedente la macchina Unix si chiama shiva.istia.univ-angers.fr, un'e-mail indirizzata a guillaume.dupond@shiva.istia.univ-angers.fr verrà recapitata nella casella di posta dell'utente con nome di accesso dupond su quella macchina.

Qui non esamineremo l'intera interfaccia della classe users, ma semplicemente il suo costruttore e alcuni metodi:

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

public class users{


   // attributes
  private Hashtable usersByLogin=new Hashtable();       // login --> login, pwd, ..., dir
    private ArrayList erreurs=new ArrayList();             // list of error messages

....

   // manufacturer
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: name of the user file with lines of the form
         // login:pwd:uid:gid:id:dir:shell
         // groupsFileName: name of the group file with lines of the form
         // name:pwd:number:member1,member2,...
         // aliasesFileName: name of alias file with lines of the form
         // alias:[tab]login
         // builds the usersByLogin dictionary
....
    }// manufacturer

     // user list
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // errors
  public ArrayList getErreurs(){
    return erreurs;
  }
utentiPerLogin
Un dizionario (Hashtable) le cui chiavi sono gli utenti presenti nel file passwd. Il valore associato a ciascuna chiave è un array di stringhe (String[7]) i cui elementi corrispondono ai 7 campi della riga del file passwd associata all'utente. Alcuni campi potrebbero essere vuoti se la riga contiene meno di 7 campi.
errori
elenco dei messaggi di errore - vuoto se non ci sono errori

7.2. L'applicazione web

Proponiamo di realizzare la seguente applicazione web (pagina con modulo):

N.
nome
Tipo HTML
ruolo
1
cmbLogins
<select ...>...</select>
visualizza un elenco di tutti gli accessi per i quali è possibile richiedere informazioni
2
btnSearch
<input type="submit" ...>
per avviare la ricerca

Quando l'utente clicca sul pulsante [Cerca] (2), il login da (1) viene interrogato da un oggetto U di tipo users. Se il login esiste, viene restituita la seguente risposta (pagina informativa):

Come mostra l'URL del browser sopra, i parametri del modulo vengono inviati al server tramite una richiesta GET. Possiamo quindi fornire direttamente al browser questo URL contenente i parametri. È ciò che stiamo facendo qui, per inserire un login che non esiste. Otteniamo la seguente risposta (pagina di errore):

7.3. L'architettura dell'applicazione

Questa architettura comprende i seguenti componenti:

  • le viste:
    • logins.jsp, utilizzata per visualizzare l'elenco degli accessi (vista 1)
    • infos.jsp, utilizzato per visualizzare le informazioni relative a un accesso (vista 2)
    • erreurs.jsp, utilizzato per visualizzare un elenco di errori (vista 3)
  • Moduli di tipo ActionForm utilizzati dalle azioni:
    • formLogins, utilizzato per raccogliere i dati dal modulo logins.jsp
  • le azioni:
    • SetupLoginAction, che prepara il contenuto di form.jsp e quindi visualizza questa vista
    • InfosLoginAction, che elabora il contenuto di logins.jsp una volta che è stato inviato al server
    • ForwardAction, che gestisce il link [Torna al modulo] nelle viste infos.jsp ed erreurs.jsp
  • la classe business degli utenti utilizzata dalle azioni per recuperare i loro dati
  • il modello fornito dai tre file flat passwd, group e aliases

7.4. I file di configurazione dell'applicazione web

7.4.1. Il file server.xml

Il contesto dell'applicazione sarà denominato /strutsquiest2. Aggiungeremo quindi la seguente riga al file server.xml di Tomcat:

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

Una volta fatto ciò, potrebbe essere necessario riavviare Tomcat affinché riconosca il nuovo contesto. Possiamo verificarne la validità richiedendo l'URL http://localhost:8080/strutsquiest2.

7.4.2. Il file web.xml

Il file di configurazione web.xml dell'applicazione sarà il seguente:

<?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>

Questo file web.xml introduce una nuova funzionalità. Il controller Struts non è più org.apache.struts.action.ActionServlet ma una classe derivata che qui abbiamo chiamato istia.st.struts.quiest.Quiest2ActionServlet. Questo ci permetterà di recuperare i due parametri di inizializzazione: passwdFileName (posizione del file passwd) e groupFileName (posizione del file group). Il file degli alias non è necessario in questa applicazione.

7.4.3. Il file struts-config.xml

Il file struts-config.xml sarà il seguente:

<?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>

Contiene tre sezioni principali:

  • la dichiarazione dei moduli nella sezione <form-beans>
  • la dichiarazione delle azioni nella sezione <action-mappings>
  • la dichiarazione del file delle risorse nella sezione <message-resources>

7.4.4. Gli oggetti modulo (bean) dell'applicazione

    <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>    

Nella nostra applicazione è presente un solo form bean, denominato formLogins e di tipo derivato da DynaActionForm. Verrà utilizzato nelle seguenti situazioni:

  • per contenere i dati necessari alla visualizzazione della vista n. 1
  • per recuperare i valori dal form nella vista n. 1 quando l'utente lo invia

La struttura del bean formLogins è collegata al modulo nella vista n. 1. Esaminiamola:

N.
nome
Tipo HTML
ruolo
1
cmbLogins
<select ...>...</select>
visualizza un elenco di tutti gli accessi per i quali è possibile richiedere informazioni
2
btnSearch
<input type="submit" ...>
per avviare la ricerca

Distinguiamo diversi casi:

  • Dal client al server, l'oggetto formLogins viene utilizzato per contenere i valori del modulo HTML sopra riportato, che verranno inviati tramite il pulsante [Invia]. Richiede quindi un campo cmbLogins per ricevere il valore del campo cmbLogins dell'HTML, ovvero il login scelto dall'utente.
  • Dal server al client, l'oggetto formLogins viene utilizzato per fornire il contenuto iniziale della vista n. 1. Il suo campo tLogins fungerà da contenuto per l'elenco n. 1. Il suo campo cmbLogins verrà utilizzato per impostare l'elemento da selezionare nell'elenco n. 1.

7.4.5. Azioni dell'applicazione

Le azioni sono gestite da oggetti di tipo Action o tipi derivati. Le azioni sono configurate all'interno dei tag <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>

L'azione /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>

Descriviamo come funziona l'azione /init:

  • L'azione /init si verifica normalmente una sola volta durante il primo ciclo richiesta-risposta, quando l'utente richiede l'URL http://localhost:8080/strutsquiest2/init.do
  • L'oggetto formsLogins viene creato o riciclato. Viene recuperato (riciclaggio) o inserito (creazione) nella sessione come specificato dall'attributo scope.
  • Viene chiamato il suo metodo reset. Si noti che questo metodo non fa nulla per impostazione predefinita nella classe ActionForm e nelle sue derivate. Viene chiamato appena prima che i dati della richiesta del client vengano copiati nell'oggetto ActionForm e serve a cancellare l'oggetto prima di questa copia. Qual è la richiesta del client in questo caso? L'azione /init viene attivata quando l'URL richiesto è http://localhost:8080/strutsquiest2/init.do. Questo URL può essere richiesto tramite un GET o un POST. È sufficiente includere in questa richiesta parametri che riportino i nomi dei campi formLogins affinché questi vengano inizializzati, come mostrato nell'esempio seguente:

Image

  • La richiesta contiene il parametro cmbLogins (afterpak). Il controller Struts ha quindi copiato il valore di questo parametro nel campo cmbLogins di formLogins. L'azione SetupLoginsAction è stata quindi eseguita e si è conclusa con la visualizzazione della vista logins.jsp. Questa vista presenta un modulo in cui alcuni campi ricevono i propri valori da formLogins. Pertanto, il campo di selezione HTML denominato cmbLogins ha ricevuto il proprio valore dal campo cmbLogins (=afterpak) in formLogins. Questo è il motivo per cui l'elenco degli accessi appare posizionato sull'accesso afterpak.
  • Potremmo anche provare a passare un parametro tLogins come segue:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Ciò inizializzerebbe il campo tLogins di formLogins con un array {"login1","login2"}. Tuttavia, come vedremo in seguito, l'azione SetupLoginsAction assegna un valore al campo tLogins e sostituisce l'array così creato con un nuovo array. È proprio quest'ultimo array che compare nella vista logins.jsp.

  • La discussione precedente, sebbene un po' complessa, dimostra che non possiamo dare per scontato che l'azione /init venga attivata senza parametri dal client. Potrebbe quindi essere utile utilizzare il metodo reset per cancellare formLogins. In questo caso, dovremmo estendere la classe DynaActionForm. Qui non l'abbiamo fatto.
    • Una volta chiamato il metodo reset di formLogins, il controller copia i dati dalla richiesta del client nei campi con lo stesso nome in formLogins. Normalmente, l'azione /init viene chiamata senza parametri del client, ma abbiamo mostrato in precedenza che nulla impedisce al client di invocare l'azione /init con parametri arbitrari. Al termine di questa fase, i campi cmbLogins e tLogins potrebbero quindi benissimo avere un valore. Abbiamo visto che il campo cmbLogins manterrebbe questo valore, ma non il campo tLogins.
    • Il controller verifica quindi l'attributo validate dell'azione. In questo caso, ha il valore "false". Il metodo validate di formLogins non verrà chiamato. Non lo scriveremo quindi.
    • L'oggetto SetupLoginsAction viene creato o riciclato se già esisteva, e viene chiamato il suo metodo execute. Il suo unico scopo è assegnare un valore al campo tLogins di formLogins. Questo valore è l'array di login, che verrà richiesto alla classe business users. Questa operazione potrebbe fallire. Questo è il motivo per cui l'azione /init può essere seguita da due viste:
      • la vista errors.jsp se la classe users non è stata in grado di fornire l'array di login
      • la vista logins.jsp in caso contrario
    • il controller visualizzerà una di queste due viste
    • il ciclo richiesta-risposta per l'azione /init è completato.

L'azione /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>

Descriviamo come funziona l'azione /infosLogin:

  • L'azione /infosLogin viene normalmente attivata quando l'utente fa clic sul pulsante [Cerca] nella vista logins.jsp. Viene quindi inviata una richiesta al server, come specificato dal tag HTML <form> nella vista:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • Possiamo notare che la richiesta viene inviata al server utilizzando il metodo GET. L'utente può quindi digitarla manualmente:

Image

  • L'oggetto formsLogins viene creato o riciclato. Viene recuperato (riciclaggio) o inserito (creazione) nella sessione come specificato dall'attributo scope.
  • Il suo metodo reset viene chiamato appena prima che i dati della richiesta del client vengano copiati nell'oggetto ActionForm. Normalmente ha la forma http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, dove xx è un login scelto dall'elenco dei login. Ma può anche essere qualsiasi cosa se l'utente ha utilizzato l'URL precedente passando parametri arbitrari. Consideriamo la seguente sequenza di pagine:

Image

  • L'azione /infosLogin è stata chiamata con la stringa di parametri cmbLogins=xx&tLogins=login1&tLogins=login2. I campi cmbLogins e tLogins di formLogins riceveranno quindi rispettivamente i valori "xx" e {"login1","login2"}. L'azione /infosLogin richiederà le informazioni associate al login "xx" dalla classe business degli utenti. La classe degli utenti risponderà che questo login non esiste. Da qui la vista mostrata sopra. Ora, utilizziamo il link [Torna al modulo] sopra:

Image

  • L'azione /retourLogins viene attivata dal link [Torna al modulo]. Questa azione visualizza semplicemente la vista logins.jsp senza alcuna azione intermedia. Ricordiamo che il campo tLogins viene utilizzato per popolare l'elenco degli accessi nella vista logins.jsp. Poiché l'utente ha modificato questo valore in {"login1","login2"}, questi due accessi ora compaiono nell'elenco. Ancora una volta, non possiamo sottolineare abbastanza l'assoluta necessità di tenere conto dei parametri arbitrari impostati da un utente o da un programma nel funzionamento di un'applicazione. La soluzione al problema qui presentato sarebbe quella di far puntare il link [Torna al modulo] all'azione /init. Ciò garantirebbe che venga restituito l'elenco corretto di login.
  • Torniamo a una normale richiesta all'azione /infosLogin, come ad esempio:

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

  • Il controller Struts assegnerà un valore al campo cmbLogins dell'oggetto ActionForm. Al campo tLogins, invece, non verrà assegnato alcun valore (non esiste un campo corrispondente nella richiesta inviata). Questo comportamento ci va bene. Pertanto, non sarà necessario scrivere un metodo di reset personalizzato per formLogins.
    • Una volta chiamato il metodo reset di formLogins, il controller copia i dati dalla richiesta del client nei campi con lo stesso nome in formLogins. Il campo cmbLogins riceverà un valore: il login scelto dall'utente (afterpak).
    • Il controller verifica quindi l'attributo validate dell'azione. In questo caso, ha il valore "false". Il metodo validate di formLogins non verrà chiamato.
    • L'oggetto InfosLoginAction viene creato o riutilizzato se già esistente, e viene chiamato il suo metodo execute. Il suo ruolo è quello di recuperare le informazioni associate al login cmbLogins. Queste informazioni saranno richieste alla classe di business dell'utente. Questa operazione potrebbe fallire (ad esempio, il login non esiste). Questo è il motivo per cui l'azione /infosLogin può essere seguita da due viste:
      • la vista errors.jsp se la classe users non è riuscita a fornire le informazioni richieste
      • la vista infos.jsp in caso contrario
    • il controller visualizzerà una di queste due viste
    • il ciclo richiesta-risposta per l'azione /infosLogin è completato.

L'azione /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • L'azione /retourLogins viene attivata cliccando sul link [Torna al modulo] nelle pagine erreurs.jsp e infos.jsp.
  • In questo caso, non vi è alcun modulo associato all'azione. Si procede quindi immediatamente all'esecuzione del metodo execute di un oggetto ForwardAction, che restituirà un oggetto ActionForward puntante alla vista /vues/logins.jsp.

7.4.6. Il file dei messaggi dell'applicazione

La terza sezione del file struts-config.xml è il file dei messaggi:

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

Il file ApplicationResources.properties si trova in WEB-INF/classes/istia/st/struts/quiest. Il suo contenuto è il seguente:

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. Visualizza codice

Si invitano i lettori a rivedere la lezione sulla gestione dei moduli se non comprendono il codice della vista presentato di seguito.

7.5.1. La vista logins.jsp

Ricordate che questa vista viene visualizzata in due casi:

  • quando l'azione /init viene chiamata durante il primo ciclo richiesta-risposta
  • quando viene chiamata l'azione /retourLogins durante i cicli successivi

Il codice della vista logins.jsp è il seguente:

<%@ 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

Questa vista viene visualizzata quando l'azione /infosLogin viene richiamata con successo. Il suo codice è il seguente:

<%@ 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>

Questa vista utilizza un oggetto denominato infosLoginBean, che viene inserito nella richiesta dall'azione /infosLogin. Questo oggetto ha due campi:

String titre;                        // titre à afficher dans la vue
String[] infosLogin;        // tableau des informations à afficher dans la vue

Approfondiremo questa classe quando tratteremo il codice della classe InfosLoginAction.

7.5.3. La vista errors.jsp

Questa vista viene visualizzata quando le azioni /init o /infosLogin generano un errore. Il suo codice è il seguente:

<%@ 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. Classi Java

Il file web.xml fa riferimento a una classe Java:

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

...
</web-app>

Il file di configurazione struts-config.xml fa riferimento a due classi 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 classe Quiest2ActionServlet

La classe Quiest2ActionServlet deriva dalla classe ActionServlet, la classe di controllo di Struts. Deriviamo dalla classe ActionServlet per personalizzare il suo metodo init. Questo metodo, eseguito una sola volta durante il caricamento iniziale del servlet, ci permette di costruire un oggetto di business di tipo users. Questo oggetto deve essere costruito una sola volta, e il metodo init è il luogo ideale per eseguire questa costruzione. L'oggetto users richiede due file per essere costruito: i file passwd e group. Le posizioni di questi due file vengono passate come parametri al servlet nel file web.xml dell'applicazione:

    <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>

Il codice del servlet è il seguente:

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 {

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

   //init
  public void init() throws ServletException {

    // don't forget to initialize the parent class
    super.init();

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

    // retrieve servlet initialization parameters
    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 = "";
      }
       // the parameter
      params.setProperty(initParams[i], valeur);
    } //for
     // return if there were initialization errors
    if (erreurs.size() != 0) {
      return;
    }
     // create a users object
    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
     // retrieve the list of logins
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // sort logins
    Arrays.sort(tLogins);
  } //init

   // servlet private info access method
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

In sintesi, il metodo init funziona come segue:

  • In primo luogo, viene chiamato il metodo init della classe padre (ActionServlet) in modo che si inizializzi correttamente
  • poi vengono letti i parametri di inizializzazione. Se ne manca qualcuno, viene popolato l'attributo privato `ActionErrors` `errors`.
  • Se i parametri di inizializzazione sono presenti, viene costruito un oggetto users. Questa costruzione può generare un'eccezione. In questo caso, viene popolato l'attributo errors di ActionErrors.
  • Se la creazione ha avuto esito positivo, l'elenco di tutti i login viene recuperato dall'oggetto creato e ordinato in un array, che viene memorizzato nell'attributo privato `String[] tLogins`.
  • L'oggetto users costruito viene memorizzato nell'attributo privato users u.
  • Il metodo pubblico getInfos recupera i tre attributi privati (u, errors, tLogins) in un array di oggetti.

7.6.2. La classe SetupLoginsAction

Lo scopo di questa azione è inizializzare l'oggetto DynaActionForm formLogins. Questo oggetto, inserito nella sessione, non dovrà più essere reinizializzato in seguito. L'azione SetupLoginsAction viene quindi eseguita una sola volta. Il suo codice è il seguente:

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 {

     // prepares the form to be displayed
     // retrieve info from the controller servlet
     // infos=(ActionErrors errors, users u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // were there any initialization errors?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // put the logins in the form
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

Come per tutte le azioni Struts, il codice si trova nel metodo execute. Questo metodo:

  • recupera dal controller Struts le informazioni che il controller ha memorizzato tramite il proprio metodo init. Ciò avviene tramite il metodo getServlet() della classe Action.
  • Tra queste informazioni c'è l'attributo ActionErrors del controller. Se questo elenco di errori non è vuoto, viene inserito nella richiesta e viene visualizzata la vista errors.jsp.
  • Se l'elenco degli errori è vuoto, l'elenco degli accessi inizialmente creato dal controller viene assegnato al campo tLogins del bean formLogins. Viene quindi richiesta la vista logins.jsp, che visualizzerà l'elenco degli accessi.

7.6.3. Le classi InfosLoginBean e InfosLoginAction

Lo scopo dell'azione InfosLoginAction è recuperare le informazioni associate al login scelto dall'utente e presentarle all'utente. Le informazioni saranno raccolte in un oggetto di tipo InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean holding the info needed for the info page
  private String titre;
  private String[] infosLogin;

  // manufacturer
  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 classe precedente è un bean, ovvero una classe Java in cui un attributo privato T unAttribut è automaticamente associato a due metodi privati:

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

Si noti la sintassi speciale dei metodi get e set. Se l'attributo è un array T[] unAttribut, possiamo creare metodi get e set per gli elementi dell'array:

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

Per comprendere meglio questo concetto, esaminiamo il codice della vista infos.jsp, che deve essere inviata a seguito dell'azione 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>

Prendiamo il seguente tag:

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

Indica al sistema di scrivere il valore del campo titolo (property) dell'oggetto infosLoginBean (name) incluso nella richiesta (scope). Il valore da scrivere verrà ottenuto tramite request.getAttribute("infosLoginBean").getTitre(). Pertanto, il metodo getTitre deve esistere nella classe InfosLoginBean. È così. Il tag

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

indica al sistema di scrivere il valore dell'elemento infosLogin[0] dell'oggetto infosLoginBean presente nella richiesta. Il valore da scrivere verrà ottenuto tramite request.getAttribute("infosLoginBean").getInfosLogin(0). Pertanto, il metodo getInfosLogin(int i) deve essere presente nella classe InfosLoginBean. E lo è.

Lo scopo della classe InfosLoginAction è quello di costruire il suddetto oggetto InfosLoginBean a partire da un login scelto dall'utente. Il suo codice è il seguente:

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 {

     // should display login information

     // retrieve info from the controller servlet
     // infos=(ActionErrors errors, users u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // were there any initialization errors?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // first retrieve this login
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // do we have anything?
    if (login == null) {
      // not normal - the login form is returned
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // we have a login - we're looking for it
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // have we found?
    if (infosLogin == null) {
      // login not found - error page displayed
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // the login has been found - we put the information found in the query
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

Il metodo execute funziona come segue:

  • Recupera le informazioni raccolte dal controller Struts durante l'inizializzazione. Se il controller ha rilevato degli errori, l'esecuzione si interrompe e viene richiesto all'utente di visualizzare tali errori.
  • Verifichiamo che sia presente un login. Se l'utente ha compilato il modulo di selezione del login, il login è presente. Tuttavia, l'utente potrebbe benissimo digitare l'URL dell'azione direttamente nel proprio browser senza passare alcun parametro. Se non è presente alcun login, visualizziamo nuovamente l'elenco dei login.
  • Se è presente un login, recuperiamo le informazioni associate alla classe business dell'utente. Se la classe non riesce a trovare il login cercato, viene visualizzata la pagina di errore. Altrimenti, viene creato un oggetto InfosLoginBean per contenere le informazioni necessarie alla vista infos.jsp. Questo oggetto viene inserito nella richiesta e viene visualizzata la pagina infos.jsp.

7.7. Distribuzione

La struttura delle directory dell'applicazione è la seguente:

 

7.8. Conclusione

Abbiamo utilizzato Struts in un'applicazione realistica che impiega una classe di business. Abbiamo inoltre dimostrato che occorre prestare particolare attenzione alla richiesta inviata da un client e che non si devono formulare ipotesi sulla sua natura. Una richiesta può essere di qualsiasi tipo e ogni applicazione deve iniziare verificandone la validità.