Skip to content

7. Applicazione QuiEst

Qui descriviamo un'applicazione Struts leggermente più sofisticata rispetto a quelle precedenti, che dovevano essere semplici per motivi didattici.

7.1. La classe users

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

  • /etc/passwd: elenco degli utenti
  • /etc/group: elenco dei gruppi
  • /etc/aliases: elenco degli alias per la posta

Il contenuto di questi tre file è il seguente:

- /etc/passwd

Le righe di questo file hanno la seguente struttura:


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

con

login
nome utente
pwd
la sua password crittografata
uid
il suo numero utente
gid
il suo numero di gruppo
id
la sua identità
dir
la sua directory di connessione
shell
il suo interprete di comandi

Pertanto, la riga di un utente potrebbe essere la seguente:

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

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

- /etc/group

Le righe di questo file hanno la seguente forma:


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

con

nomGroupe
nome del gruppo
pwd
la sua password crittografata - nella maggior parte dei casi questo campo è vuoto
gid
il numero del gruppo
membrei
nomi utente - questo campo può essere vuoto

Pertanto, la riga del gruppo 57 precedente potrebbe essere la seguente:

iup2-auto::57:

il che indica che il gruppo 57 ha il nome iup2-auto.

- /etc/aliases

Le righe di questo file hanno la seguente forma:

alias:[tab]login

con

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

Quindi la riga


    guillaume.dupond:    dupond

significa che l'alias guillaume.dupond appartiene all'utente con login dupond. Ricordiamo che gli alias vengono utilizzati negli indirizzi e-mail. Pertanto, 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à recapitato nella casella di posta dell'utente con login dupond su quella macchina.

In questa sede non ci occuperemo dell’intera interfaccia della classe **users**, ma solo del suo costruttore e di alcuni metodi:

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

public class users{


   // attributi
  private Hashtable usersByLogin=new Hashtable();       // login --> login, password, ..., directory
    private ArrayList erreurs=new ArrayList();             // elenco dei messaggi di errore

....

   // costruttore
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: nome del file degli utenti con righe della forma
         // login:pwd:uid:gid:id:dir:shell
         // groupsFileName: nome del file dei gruppi con righe del tipo
         // nome:pwd:numero:membro1,membro2,..
         // aliasesFileName: nome del file degli alias con righe del tipo
         // alias:[tab]login
         // crea il dizionario usersByLogin
....
    }// costruttore

     // elenco degli utenti
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

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

7.2. L'applicazione web in questione

Si propone di realizzare la seguente applicazione web (pagina modulo):

n.
nome
tipo HTML
ruolo
1
cmbLogins
<select ...>...</select>
presenta l'elenco di tutti gli accessi sui quali è possibile richiedere informazioni
2
btnChercher
<input type="submit" ...>
per avviare la ricerca

Quando l'utente preme il pulsante [Chercher] (2), l'accesso indicato in (1) viene richiesto a un oggetto U di tipo users. Se l'accesso esiste, si ottiene la seguente risposta (pagina delle informazioni):

Come mostra il URL del browser sopra riportato, i parametri del modulo vengono inviati al server tramite un GET. È quindi possibile fornire direttamente al browser questo URL con i parametri impostati. È ciò che facciamo qui, per inserire un login che non esiste. Si ottiene la seguente risposta (pagina degli errori):

7.3. L’architettura dell’applicazione

In questa architettura troviamo i seguenti componenti:

  • le viste:
    • logins.jsp, utilizzata per visualizzare l'elenco degli accessi (vista 1)
    • infos.jsp, utilizzata per visualizzare le informazioni relative a un accesso (vista 2)
    • erreurs.jsp, utilizzata per visualizzare un elenco di errori (vista 3)
  • i moduli di tipo ActionForm utilizzati dalle azioni:
    • formLogins utilizzato per raccogliere i dati del modulo logins.jsp
  • le azioni:
    • SetupLoginAction che prepara il contenuto di formulaire.jsp e quindi visualizza questa vista
    • InfosLoginAction che elabora il contenuto di logins.jsp una volta che questo è stato inviato al server
    • ForwardAction che gestisce il collegamento [Retour vers le formulaire] delle viste infos.jsp e erreurs.jsp
  • la classe di business users utilizzata dalle azioni per ottenere i propri dati
  • il modello fornito dai tre file di testo passwd, group e aliases

7.4. I file di configurazione dell’applicazione web

7.4.1. Il file server.xml

Il contesto dell'applicazione si chiamerà /strutsquiest2. Aggiungeremo quindi la seguente riga nel file server.xml di Tomcat:

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

Fatto ciò, se necessario, riavviamo Tomcat affinché riconosca il nuovo contesto. Possiamo verificarne la validità richiedendo l'http://localhost:8080/strutsquiest2 URL.

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 novità. Il controller Struts non è più org.apache.struts.action.ActionServlet, ma una classe derivata che qui abbiamo denominato istia.st.struts.quiest.Quiest2ActionServlet. Questo ci consentirà di recuperare i due parametri di inizializzazione, ovvero passwdFileName (posizione del file passwd) e groupFileName (posizione del file group). Il file aliases 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>

Qui si trovano le 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 in <message-ressources>

7.4.4. Gli oggetti (bean) dei moduli 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 bean di modulo, denominato formLogins e di tipo derivato da DynaActionForm. Verrà utilizzato nelle seguenti situazioni:

  • contenere i dati necessari per la visualizzazione della vista n. 1
  • recuperare i valori del modulo della vista n. 1 quando l’utente lo convalida (submit)

La struttura del bean formLogins è legata al modulo della vista n. 1. Esaminiamola:

n.
nome
tipo HTML
ruolo
1
cmbLogins
<select ...>...</select>
presenta l'elenco di tutti gli accessi sui quali è possibile richiedere informazioni
2
btnChercher
<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 verrà inviato tramite il pulsante [Envoyer]. È quindi necessario un campo cmbLogins che riceverà il valore del campo HTML, cmbLogins, c.a.d, 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 1. Il suo campo cmbLogins consentirà di stabilire l’elemento dell’elenco 1 da selezionare.

7.4.5. Le azioni dell’applicazione

Le azioni sono gestite da oggetti di tipo Action o derivati. La configurazione delle azioni avviene 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 il funzionamento dell'azione /init:

  • L'azione /init viene eseguita normalmente una sola volta durante il primo ciclo richiesta-risposta in cui l'utente richiede l'oggetto URLhttp://localhost:8080/strutsquiest2/init.do
  • viene creato o riciclato l'oggetto formsLogins. Esso viene recuperato (riciclaggio) o inserito (creazione) nella sessione come richiesto dall'attributo scope.
  • Viene chiamato il suo metodo reset. Ricordiamo che questo metodo non esegue alcuna operazione per impostazione predefinita nelle classi ActionForm e nelle classi derivate. Viene chiamato immediatamente prima della copia dei dati della richiesta del client nell’oggetto ActionForm e serve a ripulire l’oggetto prima di tale copia. Qual è, in questo caso, la richiesta del client? L’azione /init viene attivata quando l’URL richiesto è http://localhost:8080/strutsquiest2/init.do. Questo URL può essere richiesto da un GET o da un POST. È sufficiente inserire in questa richiesta i parametri che riportano i nomi dei campi di 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 il proprio valore da formLogins. Pertanto, il campo a selezione HTML denominato cmbLogins ha ricevuto il proprio valore dal campo cmbLogins (=afterpak) di formLogins. Ecco perché l’elenco degli accessi appare posizionato sull’accesso afterpak.
  • Ci si potrebbe divertire a passare anche un parametro tLogins nel modo seguente:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Ciò avrebbe l’effetto di inizializzare il campo tLogins di formLogins con un array {"login1","login2"}. Tuttavia, vedremo più avanti che l’azione SetupLoginsAction assegna un valore al campo tLogins e sostituisce l’array così creato con un nuovo array. È quest’ultimo, quindi, che appare nella vista logins.jsp.

  • La discussione precedente, sebbene un po’ complessa, ha il merito di dimostrare che non si può dare per scontato che l’azione /init venga attivata senza parametri provenienti dal client. Può quindi essere utile utilizzare il metodo reset per azzerare formLogins. In questo caso, dovremmo derivare la classe DynaActionForm. Qui non l’abbiamo fatto.
    • Una volta chiamato il metodo reset di formLogins, il controller copia i dati della richiesta del client nei campi omonimi di formLogins. Normalmente, l’azione /init viene chiamata senza parametri da parte 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 possono quindi benissimo avere un valore. Abbiamo visto che il campo cmbLogins manterrebbe questo valore, ma non il campo tLogins.
    • Il controller esamina quindi l’attributo `validate` dell’azione. In questo caso, il valore è “false”. Il metodo `validate` di formLogins non verrà chiamato. Non lo scriveremo quindi.
    • L'oggetto SetupLoginsAction viene creato o riciclato se già esistente e viene avviato il suo metodo `execute`. Il suo unico scopo è quello di assegnare un valore al campo tLogins di formLogins. Questo valore è l'array dei login, che verrà richiesto alla classe di business users. Questa operazione potrebbe fallire. Per questo motivo l'azione /init può essere seguita da due viste:
      • la vista erreurs.jsp se la classe «users» non è riuscita a fornire l’array dei login
      • la vista logins.jsp in caso contrario
    • il controller visualizzerà una di queste due viste
    • il ciclo richiesta-risposta dell’azione /init è terminato.

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 il funzionamento dell'azione / infosLogin:

  • l'azione /infosLogin viene normalmente eseguita quando l'utente fa clic sul pulsante [Chercher] della vista logins.jsp. Viene quindi inviata una richiesta al server, definita dal tag HTML <form> della vista:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • si nota che la richiesta viene inviata al server tramite il metodo GET. L’utente può quindi digitarla manualmente:

Image

  • viene creato o riciclato l’oggetto formsLogins. Esso viene recuperato (riciclaggio) o inserito (creazione) nella sessione come richiesto 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 il precedente URL passando parametri arbitrari. Consideriamo la seguente sequenza di pagine:

Image

  • l'azione /infosLogin è stata richiamata 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à alla classe di business users le informazioni associate al login "xx". La classe users risponderà che tale login non esiste. Da qui la vista inviata sopra. Ora utilizziamo il link [Retour au formulaire] riportato sopra:

Image

  • È l’azione /retourLogins che viene attivata dal link [Retour au formulaire]. Questa azione si limita a visualizzare la vista logins.jsp senza alcuna azione intermedia. Ricordiamo che il campo tLogins serve ad alimentare l'elenco dei login della vista logins.jsp. Poiché l'utente ha modificato questo valore in {"login1","login2"}, sono proprio questi due login ad apparire ora nell'elenco. Ancora una volta, non possiamo che sottolineare l’assoluta necessità di tenere conto, nel funzionamento di un’applicazione, del caso di parametri arbitrari impostati da un utente o da un programma. La soluzione al problema qui esposto sarebbe che il collegamento [Retour au formulaire] puntasse all’azione /init. In questo modo si avrebbe la certezza di ottenere l’elenco corretto dei login.
  • Torniamo a una richiesta normale all’azione /infosLogin del tipo:

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

  • Il controller Struts assegnerà un valore al campo cmbLogins dell'oggetto ActionForm. Il campo tLogins, invece, non riceverà alcun valore (non esiste un campo corrispondente nella richiesta inviata). Questo comportamento ci va bene. Non dovremo quindi scrivere un metodo reset personalizzato per formLogins.
    • Una volta richiamato il metodo reset di formLogins, il controller copia i dati della richiesta del cliente nei campi con lo stesso nome di formLogins. Il campo cmbLogins riceverà un valore, ovvero il login scelto dall’utente (afterpak).
    • Il controller verifica quindi l’attributo `validate` dell’azione. In questo caso, il valore è “false”. Il metodo `validate` di formLogins non verrà chiamato.
    • L’oggetto InfosLoginAction viene creato o riutilizzato se già esistente e viene eseguito il suo metodo `execute`. Il suo ruolo è quello di ottenere le informazioni associate al login cmbLogins. Queste informazioni saranno richieste alla classe di business `users`. Questa operazione potrebbe fallire (ad esempio, login inesistente). Per questo motivo l’azione /infosLogin può essere seguita da due viste:
      • la vista erreurs.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 dell’azione /infosLogin è terminato.

L'azione /retourLogins

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

7.4.6. Il file dei messaggi dell’applicazione

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

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

Il file ApplicationResources.properties si trova nella directory 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. Il codice delle viste

Si invita il lettore a rileggere la lezione sulla gestione dei moduli qualora non comprenda il codice delle viste presentate di seguito.

7.5.1. La vista logins.jsp

Ricordiamo che questa vista viene visualizzata in due casi:

  • quando viene richiamata l’azione /init durante il primo ciclo richiesta-risposta
  • all'invocazione dell'azione /retourLogins nei 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 in seguito a una chiamata riuscita all'azione /infosLogin. 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, inserito nella query dall'azione /infosLogin. Questo oggetto presenta due campi:

String titre;                        // titolo da visualizzare nella vista
String[] infosLogin;        // tabella delle informazioni da visualizzare nella vista

Approfondiremo questa classe quando tratteremo il codice della classe InfosLoginAction.

7.5.3. La vista erreurs.jsp

Questa vista viene visualizzata quando le azioni /init o /infosLogin terminano con 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. Le 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 del controller Struts. Deriviamo la classe ActionServlet per personalizzarne il metodo init. Infatti, questo metodo, eseguito una sola volta al momento del caricamento iniziale del servlet, ci consentirà di costruire un oggetto di business di tipo users. Questo oggetto, infatti, deve essere costruito una sola volta e il metodo init è il luogo ideale per effettuare tale costruzione. L’oggetto users necessita di due file per essere creato: i file passwd e group. Il percorso di questi due file viene passato 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 della 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 {

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

   //init
  public void init() throws ServletException {

    // non dimenticare di inizializzare la classe padre
    super.init();

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

    // si recuperano i parametri di inizializzazione 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 = "";
      }
       // si memorizza il parametro
      params.setProperty(initParams[i], valeur);
    } //for
     // ritorno in caso di errori di inizializzazione
    if (erreurs.size() != 0) {
      return;
    }
     // si crea un oggetto 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
     // si recupera l'elenco degli accessi
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // si ordinano gli accessi
    Arrays.sort(tLogins);
  } //init

   // metodo di accesso alle informazioni private del servlet
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

In sintesi, il funzionamento del metodo init è il seguente:

  • innanzitutto, viene chiamato il metodo init della classe padre (ActionServlet) affinché questa si inizializzi correttamente
  • poi vengono letti i parametri di inizializzazione. Se ne mancano alcuni, viene popolato l’attributo privato ActionErrors errori.
  • Se i parametri di inizializzazione sono presenti, viene creato un oggetto `users`. Questa creazione può generare un'eccezione. In tal caso, viene popolato l'attributo `ActionErrors` `erreurs`.
  • Se la creazione è andata a buon fine, dall’oggetto creato si estrae l’elenco di tutti i login e lo si ordina in un array che viene inserito nell’attributo privato String[] tLogins.
  • L’oggetto users creato viene memorizzato nell’attributo privato users u.
  • Il metodo pubblico getInfos consente di ottenere i tre attributi privati (u, errori, tLogins) in un array di oggetti.

7.6.2. La classe SetupLoginsAction

Lo scopo di questa azione è inizializzare l’oggetto DynaActionForm formLogins. Una volta inserito nella sessione, questo oggetto 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 {

     // prepara il modulo da visualizzare
     // si recuperano le informazioni dal servlet di controllo
     // informazioni=(ActionErrors errori, utenti u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // Si sono verificati errori di inizializzazione?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // si inseriscono le credenziali nel modulo
    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. Quest'ultimo:

  • recupera dal controller Struts le informazioni che quest’ultimo ha memorizzato tramite il suo metodo init. È il metodo getServlet() della classe Action a consentire questa operazione.
  • Tra questi figura l'attributo ActionErrors "Errori del controller". Se questo elenco di errori non è vuoto, viene inserito nella query e viene richiesta la visualizzazione della vista erreurs.jsp.
  • Se l’elenco degli errori è vuoto, al campo tLogins del bean formLogins viene assegnato l’elenco degli accessi creato inizialmente dal controller. Viene quindi richiesta la visualizzazione della vista logins.jsp, che mostrerà l’elenco degli accessi.

7.6.3. Le classi InfosLoginBean e InfosLoginAction

L’azione InfosLoginAction ha lo scopo di recuperare le informazioni associate al login scelto dall’utente e di presentarle a quest’ultimo. Le informazioni saranno raccolte in un oggetto di tipo InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean contenente le informazioni necessarie per la pagina informativa
  private String titre;
  private String[] infosLogin;

  // costruttore
  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 classe precedente è un bean, c.a.d. Si tratta di una classe Java in cui un attributo privato T unAttribut è automaticamente associato a due metodi privati:

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

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

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

Per comprendere meglio, riprendiamo 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"/>

Richiede di scrivere il valore del campo titolo (property) dell'oggetto infosLoginBean (name) inserito nella query (scope). Il valore da scrivere sarà ottenuto tramite request.getAttribute("infosLoginBean").getTitre(). È quindi necessario che il metodo getTitre esista nella classe InfosLoginBean. È così. Il tag

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

richiede di scrivere il valore dell’elemento infosLogin[0] dell’oggetto infosLoginBean specificato nella richiesta. Il valore da scrivere sarà ottenuto tramite request.getAttribute("infosLoginBean").getInfosLogin(0). È quindi necessario che il metodo getInfosLogin(int i) esista nella classe InfosLoginBean. È così.

La classe InfosLoginAction ha lo scopo di costruire l'oggetto InfosLoginBean precedente 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 {

     // deve visualizzare le informazioni relative a un login

     // si recuperano le informazioni dal servlet di controllo
     // informazioni=(ActionErrors errori, utenti u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // si sono verificati errori di inizializzazione?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // Per prima cosa recuperare questo login
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // c'è qualcosa?
    if (login == null) {
      // Non è normale - inviamo nuovamente il modulo dei dati di accesso
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // abbiamo un login - lo stiamo cercando
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // L'abbiamo trovato?
    if (infosLogin == null) {
      // Il login non è stato trovato - viene visualizzata la pagina degli errori
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // il login è stato trovato - si inseriscono le informazioni trovate nella richiesta
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

Il funzionamento del metodo execute è il seguente:

  • vengono recuperate le informazioni raccolte dal controller Struts durante la sua inizializzazione. Se quest’ultimo ha rilevato degli errori, l’esecuzione si interrompe a questo punto con la richiesta di visualizzazione di tali errori.
  • si verifica che sia presente un login. Se l’utente ha utilizzato il modulo di selezione del login, il login è presente. Tuttavia, l’utente può tranquillamente digitare l’URL dell’azione direttamente nel browser senza passare parametri. Se non c’è alcun login, viene visualizzato nuovamente l’elenco dei login.
  • Se è presente un login, vengono richieste le informazioni associate alla classe di business «users». Se quest’ultima non trova il login cercato, viene visualizzata la pagina degli errori. Altrimenti, viene creato un oggetto InfosLoginBean per inserirvi 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 ad albero dell’applicazione è la seguente:

 

7.8. Conclusione

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