Skip to content

7. Toepassing QuiEst

We beschrijven hier een Struts-toepassing die iets geavanceerder is dan de voorgaande, die juist eenvoudig moesten zijn om leerzaam te zijn.

7.1. De klasse users

Er is een Java-klasse beschikbaar die de gegevens van de gebruikers van een Unix-machine opslaat. Deze gegevens worden opgeslagen in drie specifieke bestanden:

  • /etc/passwd: lijst met gebruikers
  • /etc/group: lijst met groepen
  • /etc/aliases: lijst met e-mailaliassen

De inhoud van deze drie bestanden is als volgt:

- /etc/passwd

De regels in dit bestand hebben de volgende vorm:


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

met

login
gebruikersnaam
pwd
zijn versleutelde wachtwoord
uid
zijn gebruikersnummer
gid
zijn groepsnummer
id
zijn identiteit
dir
zijn inlogmap
shell
zijn command-interpreter

De regel voor een gebruiker zou er dus als volgt uit kunnen zien:

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

De vorige gebruiker heeft nummer 110 en behoort tot groep 57. De definitie van groep 57 is te vinden in het bestand /etc/group.

- /etc/group

De regels in dit bestand hebben de volgende vorm:


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

met

nomGroupe
groepsnaam
pwd
het versleutelde wachtwoord – meestal is dit veld leeg
gid
het groepsnummer
membrei
gebruikerslogins – dit veld kan leeg zijn

De regel van de vorige groep 57 zou er dus als volgt uit kunnen zien:

iup2-auto::57:

wat aangeeft dat groep 57 de naam iup2-auto draagt.

- /etc/aliases

De regels in dit bestand hebben de volgende vorm:

alias:[tab]login

met

alias
alias
[tab]
een of meer tabbladen
login
de gebruikersnaam van de gebruiker aan wie de alias toebehoort

De regel


    guillaume.dupond:    dupond

betekent dat de alias guillaume.dupond toebehoort aan de gebruiker met login dupond. Ter herinnering: aliassen worden gebruikt in e-mailadressen. Als in het vorige voorbeeld de Unix-machine dus shiva.istia.univ-angers.fr heet, wordt een e-mail gericht aan guillaume.dupond@shiva.istia.univ-angers.fr zal worden afgeleverd in de mailbox van de gebruiker met login dupond op deze machine.

We zullen ons hier niet bezighouden met de volledige interface van de klasse **users**, maar alleen met de constructor en enkele methoden:

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

public class users{


   // attributen
  private Hashtable usersByLogin=new Hashtable();       // login --> login, wachtwoord, ..., map
    private ArrayList erreurs=new ArrayList();             // lijst met foutmeldingen

....

   // constructor
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: naam van het gebruikersbestand met regels in de vorm
         // login:pwd:uid:gid:id:dir:shell
         // groupsFileName: bestandsnaam van de groepen met regels in de vorm
         // naam:wachtwoord:nummer:lid1,lid2,..
         // aliasesFileName: bestandsnaam van de aliassen met regels in de vorm
         // alias:[tab]login
         // stelt het woordenboek samen usersByLogin
....
    }// constructor

     // gebruikerslijst
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // fouten
  public ArrayList getErreurs(){
    return erreurs;
  }
usersByLogin
woordenboek (Hashtable) waarvan de sleutels de logins uit het passwd-bestand zijn. De waarde die aan de sleutel is gekoppeld, is een array van strings (String [7]) waarvan de elementen de 7 velden zijn van de regel in het passwd-bestand die bij de login hoort. Sommige velden kunnen leeg zijn als de regel minder dan 7 velden bevat.
erreurs
lijst met foutmeldingen – leeg als er geen fouten zijn

7.2. De webapplicatie die

We stellen voor om de volgende webapplicatie (formulierpagina) te bouwen:

nr.
naam
type HTML
rol
1
cmbLogins
<select ...>...</select>
toont de lijst met alle logins waarover informatie kan worden opgevraagd
2
btnChercher
<input type="submit" ...>
om de zoekopdracht te starten

Wanneer de gebruiker op de knop [Chercher] (2) drukt, wordt de login van (1) opgevraagd bij een U-object van het type users. Als de login bestaat, krijgen we het volgende antwoord (informatiepagina):

Zoals de URL van de browser hierboven laat zien, worden de formulierparameters via een GET naar de server verzonden. We kunnen deze geconfigureerde URL dus rechtstreeks aan de browser doorgeven. Dat is wat we hier doen, om een login in te voeren die niet bestaat. We krijgen het volgende antwoord (foutpagina):

7.3. De architectuur van de applicatie

In deze architectuur vinden we de volgende componenten:

  • de weergaven:
    • logins.jsp, gebruikt om de lijst met aanmeldingen weer te geven (weergave 1)
    • infos.jsp, gebruikt om de informatie over een login weer te geven (weergave 2)
    • erreurs.jsp, gebruikt om een lijst met fouten weer te geven (weergave 3)
  • formulieren van het type ActionForm die worden gebruikt door de acties:
    • formLogins wordt gebruikt om de gegevens van het formulier logins.jsp te verzamelen
  • de acties:
    • SetupLoginAction, dat de inhoud van formulaire.jsp voorbereidt en vervolgens deze weergave toont
    • InfosLoginAction, dat de inhoud van logins.jsp verwerkt zodra deze naar de server is verzonden
    • ForwardAction, dat de link [Retour vers le formulaire] van de weergaven infos.jsp en erreurs.jsp verwerkt
  • de businessklasse `users` die door de acties wordt gebruikt om hun gegevens op te halen
  • het model dat wordt geleverd door de drie platte bestanden `passwd`, `group` en `aliases`

7.4. De configuratiebestanden van de webapplicatie

7.4.1. Het bestand server.xml

De context van de applicatie krijgt de naam /strutsquiest2. We voegen daarom de volgende regel toe aan het Tomcat-bestand server.xml:

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

Zodra dit is gebeurd, starten we Tomcat eventueel opnieuw op, zodat de nieuwe context wordt overgenomen. We kunnen de geldigheid ervan controleren door de URL http://localhost:8080/strutsquiest2 op te vragen.

7.4.2. Het bestand web.xml

Het configuratiebestand web.xml van de applicatie ziet er als volgt uit:

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

Dit bestand web.xml bevat een nieuwigheid. De Struts-controller is niet langer org.apache.struts.action.ActionServlet, maar een afgeleide klasse die we hier istia.st.struts.quiest.Quiest2ActionServlet hebben genoemd. Hierdoor kunnen we de twee initialisatieparameters ophalen, namelijk passwdFileName (locatie van het passwd-bestand) en groupFileName (locatie van het group-bestand). Het aliases-bestand is in deze toepassing overbodig.

7.4.3. Het bestand struts-config.xml

Het bestand struts-config.xml ziet er als volgt uit:

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

Hierin zijn de drie grote secties te vinden:

  • de declaratie van de formulieren in de sectie <form-beans>
  • de declaratie van de acties in de sectie <action-mappings>
  • de declaratie van het bronnenbestand in <message-ressources>

7.4.4. De formulierobjecten (beans) van de applicatie

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

Er is slechts één formulier-bean in onze applicatie, genaamd formLogins en van het type afgeleid van DynaActionForm. Deze wordt in de volgende situaties gebruikt:

  • om de gegevens te bevatten die nodig zijn voor de weergave van weergave nr. 1
  • het ophalen van de waarden uit het formulier van weergave nr. 1 wanneer de gebruiker het formulier verzendt (submit)

De structuur van de bean formLogins is gekoppeld aan het formulier van weergave nr. 1. Laten we deze eens bekijken:

nr.
naam
type HTML
rol
1
cmbLogins
<select ...>...</select>
toont de lijst met alle logins waarover informatie kan worden opgevraagd
2
btnChercher
<input type="submit" ...>
om de zoekopdracht te starten

We onderscheiden verschillende gevallen:

  • van de client naar de server: het object formLogins wordt gebruikt om de waarden van het bovenstaande formulier HTML te bevatten, dat via de knop [Envoyer] wordt verzonden. Er is dus een veld cmbLogins nodig dat de waarde van het veld HTML, cmbLogins en c.a.d ontvangt, namelijk de door de gebruiker gekozen login.
  • Van de server naar de client wordt het object formLogins gebruikt om de initiële inhoud van weergave nr. 1 te verstrekken. Het veld tLogins ervan zal dienen als inhoud voor lijst 1. Met het veld cmbLogins kan worden bepaald welk element uit lijst 1 moet worden geselecteerd.

7.4.5. De acties van de applicatie

De acties worden uitgevoerd door objecten van het type Action of daarvan afgeleide objecten. De configuratie van de acties vindt plaats binnen de tags <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>

De actie /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>

Laten we eens beschrijven hoe de actie /init werkt:

  • De actie /init vindt normaal gesproken slechts één keer plaats tijdens de eerste vraag-antwoordcyclus, waarbij de gebruiker de URL-http://localhost:8080/strutsquiest2/init.do opvraagt
  • het object formsLogins wordt aangemaakt of hergebruikt. Het wordt opgehaald (hergebruik) of geplaatst (aanmaak) in de sessie, zoals bepaald door het scope-attribuut.
  • De methode `reset` wordt aangeroepen. Ter herinnering: deze methode doet standaard niets in de klassen `ActionForm` en afgeleide klassen. Ze wordt aangeroepen vlak voordat de gegevens van het verzoek van de klant worden gekopieerd naar het object `ActionForm` en dient om het object op te schonen vóór deze kopieeractie. Wat is hier het verzoek van de klant? De actie /init wordt geactiveerd wanneer de opgevraagde URL http://localhost:8080/strutsquiest2/init.do is. Dit URL-object kan worden aangeroepen door een GET of een POST. Het volstaat om in dit verzoek parameters op te nemen met de veldnamen van formLogins, zodat deze worden geïnitialiseerd zoals in het volgende voorbeeld wordt getoond:

Image

  • de aanvraag bevat de parameter cmbLogins (afterpak). De Struts-controller heeft dus de waarde van deze parameter overgenomen in het veld cmbLogins van formLogins. Vervolgens werd de actie SetupLoginsAction uitgevoerd, wat resulteerde in de weergave van de weergave logins.jsp. Deze weergave bevat een formulier waarvan bepaalde velden hun waarde ontvangen uit formLogins. Zo heeft het select-veld HTML, genaamd cmbLogins, zijn waarde ontvangen van het veld cmbLogins (=afterpak) uit formLogins. Daarom verschijnt de lijst met logins met de login afterpak als eerste.
  • We zouden voor de grap ook een parameter tLogins op de volgende manier kunnen doorgeven:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Dit zou tot gevolg hebben dat het veld tLogins wordt geïnitialiseerd vanuit formLogins met een array {"login1","login2"}. We zullen echter verderop zien dat de actie SetupLoginsAction een waarde toekent aan het veld tLogins en de aldus gecreëerde array vervangt door een nieuwe array. Het is deze laatste die dus verschijnt in de weergave logins.jsp.

  • De voorgaande bespreking, hoewel enigszins complex, laat in ieder geval zien dat we er niet vanuit kunnen gaan dat de actie /init wordt geactiveerd zonder parameters afkomstig van de client. Het kan daarom nuttig zijn om de methode reset te gebruiken om formLogins te wissen. In dat geval zouden we de klasse DynaActionForm moeten afleiden. Dat hebben we hier niet gedaan.
    • Zodra de methode reset van formLogins is aangeroepen, kopieert de controller de gegevens uit het verzoek van de client naar de velden met dezelfde naam in formLogins. Normaal gesproken wordt de actie /init aangeroepen zonder parameters van de client, maar we hebben eerder laten zien dat niets de client belet om de actie /init met willekeurige parameters aan te roepen. Aan het einde van deze fase kunnen de velden cmbLogins en tLogins dus heel goed een waarde hebben. We hebben gezien dat het veld cmbLogins deze waarde zou behouden, maar het veld tLogins niet.
    • De controller kijkt vervolgens naar het attribuut `validate` van de actie. Hier heeft het de waarde "false". De methode `validate` van formLogins wordt niet aangeroepen. We zullen deze dus niet schrijven.
    • Het object SetupLoginsAction wordt aangemaakt of, indien het al bestond, hergebruikt, waarna de methode `execute` wordt aangeroepen. De enige functie ervan is het toekennen van een waarde aan het veld tLogins van formLogins. Deze waarde is de lijst met inloggegevens, een lijst die wordt opgevraagd bij de businessklasse `users`. Deze bewerking kan mislukken. Daarom kan de actie /init worden gevolgd door twee weergaven:
      • de weergave erreurs.jsp als de klasse ‘users’ de lijst met inloggegevens niet heeft kunnen leveren
      • de weergave logins.jsp in het andere geval
    • de controller zal een van deze twee weergaven weergeven
    • de vraag-antwoordcyclus van de actie /init is voltooid.

De actie /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>

Laten we de werking van de actie / infosLogin beschrijven:

  • de actie /infosLogin vindt normaal gesproken plaats wanneer de gebruiker op de knop [Chercher] in de weergave logins.jsp klikt. Er wordt dan een verzoek naar de server gestuurd, vastgelegd door de tag HTML <form> van de weergave:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • We zien dat het verzoek via de GET-methode naar de server wordt verzonden. De gebruiker kan het dus handmatig invoeren:

Image

  • Het object formsLogins wordt aangemaakt of hergebruikt. Het wordt opgehaald (hergebruik) of geplaatst (aanmaak) in de sessie, zoals bepaald door het scope-attribuut.
  • De methode `reset` ervan wordt aangeroepen vlak voordat de gegevens van het verzoek van de client worden gekopieerd naar het object `ActionForm`. Deze heeft normaal gesproken de vorm http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, waarbij xx een login is die uit de lijst met logins is gekozen. Maar het kan ook van alles zijn als de gebruiker de vorige URL heeft gebruikt en daarbij willekeurige parameters heeft doorgegeven. Laten we de volgende reeks pagina’s eens bekijken:

Image

  • de actie /infosLogin is aangeroepen met de parameterreeks cmbLogins=xx&tLogins=login1&tLogins=login2. De velden cmbLogins en tLogins van formLogins krijgen dus respectievelijk de waarden "xx" en {"login1","login2"}. De actie /infosLogin vraagt aan de businessklasse users om de informatie die bij de login "xx" hoort. De klasse users antwoordt dat deze login niet bestaat. Vandaar de hierboven weergegeven weergave. Laten we nu de bovenstaande link [Retour au formulaire] gebruiken:

Image

  • Het is de actie /retourLogins die wordt geactiveerd door de link [Retour au formulaire]. Deze actie geeft alleen de weergave logins.jsp weer, zonder tussenstappen. We herinneren ons dat het veld tLogins dient om de lijst met logins van de weergave logins.jsp te vullen. Aangezien de gebruiker deze waarde heeft gewijzigd in {"login1","login2"}, verschijnen nu deze twee logins in de lijst. We kunnen niet genoeg benadrukken hoe absoluut noodzakelijk het is om bij de werking van een applicatie rekening te houden met willekeurige parameters die door een gebruiker of een programma worden ingesteld. De oplossing voor het hier beschreven probleem zou zijn dat de koppeling [Retour au formulaire] verwijst naar de actie /init. Zo zouden we er zeker van zijn dat we de juiste lijst met logins krijgen.
  • Laten we terugkeren naar een normale aanvraag voor de actie /infosLogin, bijvoorbeeld:

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

  • De Struts-controller zal een waarde toekennen aan het veld cmbLogins van het object ActionForm. Het veld tLogins krijgt daarentegen geen waarde toegewezen (er is geen overeenkomstig veld in de verzonden aanvraag). Deze werking bevalt ons. We hoeven dus geen aangepaste reset-methode te schrijven voor formLogins.
    • Zodra de reset-methode van formLogins is aangeroepen, kopieert de controller de gegevens uit het verzoek van de klant naar de velden met dezelfde naam in formLogins. Het veld cmbLogins krijgt een waarde toegewezen, namelijk de door de gebruiker gekozen login (afterpak).
    • Vervolgens controleert de controller het attribuut `validate` van de actie. Hier heeft dit de waarde "false". De methode `validate` van formLogins wordt niet aangeroepen.
    • Het object InfosLoginAction wordt aangemaakt of hergebruikt als het al bestond, en de methode `execute` ervan wordt gestart. Zijn taak is het ophalen van de informatie die bij de login cmbLogins hoort. Deze informatie wordt opgevraagd bij de businessklasse `users`. Deze bewerking kan mislukken (bijvoorbeeld omdat de login niet bestaat). Daarom kan de actie /infosLogin worden gevolgd door twee weergaven:
      • de weergave erreurs.jsp als de klasse ‘users’ de gevraagde informatie niet heeft kunnen leveren
      • de weergave infos.jsp in het andere geval
    • de controller zal een van deze twee weergaven weergeven
    • de vraag-antwoordcyclus van de actie /infosLogin is voltooid.

De actie /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • De actie /retourLogins wordt geactiveerd door op de link [Retour au formulaire] te klikken in de weergaven erreurs.jsp en infos.jsp.
  • Hier is er geen formulier gekoppeld aan de actie. We gaan dus direct over tot de uitvoering van de methode `execute` van een object `ForwardAction`, dat een object `ActionForward` zal retourneren dat verwijst naar de weergave `/vues/logins.jsp`.

7.4.6. Het berichtenbestand van de applicatie

Het derde gedeelte van het bestand struts-config.xml is het berichtenbestand:

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

Het bestand ApplicationResources.properties bevindt zich in WEB-INF/classes/istia/st/struts/quiest. De inhoud ervan is als volgt:

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. De code van de weergaven

De lezer wordt verzocht de les over het beheer van formulieren nog eens door te nemen als hij de code van de hieronder weergegeven weergaven niet begrijpt.

7.5.1. De weergave logins.jsp

Ter herinnering: deze weergave wordt in twee gevallen weergegeven:

  • bij het aanroepen van de actie /init tijdens de eerste vraag-antwoordcyclus
  • bij het aanroepen van de actie /retourLogins tijdens de volgende cycli

De code van de weergave logins.jsp is als volgt:

<%@ 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. Het scherm infos.jsp

Dit scherm wordt weergegeven na een succesvolle aanroep van de actie /infosLogin. De code ervan is als volgt:

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

Deze weergave maakt gebruik van een object met de naam infosLoginBean, dat via de actie /infosLogin in de query is geplaatst. Dit object heeft twee velden:

String titre;                        // te tonen titel in de weergave
String[] infosLogin;        // tabel met informatie die in de weergave moet worden getoond

We zullen deze klasse nader toelichten wanneer we de code van de klasse InfosLoginAction bespreken.

7.5.3. De weergave erreurs.jsp

Deze weergave wordt getoond wanneer de acties /init of /infosLogin met een fout eindigen. De code ervan is als volgt:

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

Het bestand web.xml verwijst naar een Java-klasse:

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

...
</web-app>

Het configuratiebestand struts-config.xml verwijst naar twee Java-klassen:

      <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. De klasse Quiest2ActionServlet

De klasse Quiest2ActionServlet is afgeleid van de klasse ActionServlet, de Struts-controllerklasse. We leiden de klasse ActionServlet af om de methode init aan te passen. Deze methode, die slechts één keer wordt uitgevoerd bij het eerste laden van de servlet, stelt ons namelijk in staat om een bedrijfsobject van het type users aan te maken. Dit object hoeft namelijk maar één keer te worden aangemaakt en de methode init is een geschikte plek om dit te doen. Het object ‘users’ heeft twee bestanden nodig om te worden aangemaakt: de bestanden ‘passwd’ en ‘group’. De locatie van deze twee bestanden wordt als parameters doorgegeven aan de servlet in het bestand web.xml van de applicatie:

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

De code van de servlet is als volgt:

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 {

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

   //init
  public void init() throws ServletException {

    // vergeet niet de bovenliggende klasse te initialiseren
    super.init();

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

    // we halen de initialisatieparameters van de servlet op
    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 = "";
      }
       // de parameter wordt opgeslagen
      params.setProperty(initParams[i], valeur);
    } //for
     // terugkeren als er initialisatiefouten zijn opgetreden
    if (erreurs.size() != 0) {
      return;
    }
     // er wordt een users-object aangemaakt
    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
     // de lijst met logins wordt opgehaald
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // de logins worden gesorteerd
    Arrays.sort(tLogins);
  } //init

   // methode voor toegang tot de privégegevens van de servlet
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

Kort samengevat werkt de methode init als volgt:

  • eerst wordt de init-methode van de bovenliggende klasse (ActionServlet) aangeroepen, zodat deze correct wordt geïnitialiseerd
  • Vervolgens worden de initialisatieparameters ingelezen. Als er parameters ontbreken, wordt het privé-attribuut ActionErrors ‘fouten’ bijgewerkt.
  • Als de initialisatieparameters wel aanwezig zijn, wordt een `users`-object aangemaakt. Bij het aanmaken kan een uitzondering worden gegenereerd. In dat geval wordt het attribuut `ActionErrors` `erreurs` gevuld.
  • Als het aanmaken goed is verlopen, wordt uit het aangemaakte object de lijst met alle logins opgehaald en wordt deze gesorteerd in een array die in het privé-attribuut String[] tLogins wordt opgeslagen.
  • Het aangemaakte object `users` wordt opgeslagen in het privé-attribuut `users u`.
  • Met de openbare methode getInfos kunnen de drie privé-attributen (u, fouten, tLogins) in een array van objecten worden opgehaald.

7.6.2. De klasse SetupLoginsAction

Deze actie heeft tot doel het object DynaActionForm formLogins te initialiseren. Dit object, dat in de sessie is geplaatst, hoeft daarna niet meer opnieuw te worden geïnitialiseerd. De actie SetupLoginsAction vindt dus slechts één keer plaats. De code ervan is als volgt:

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 {

     // het weer te geven formulier wordt voorbereid
     // de gegevens van de controller-servlet worden opgehaald
     // info=(ActionErrors fouten, gebruikers u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // zijn er initialisatiefouten opgetreden?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // we vullen de inloggegevens in het formulier in
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

Net als bij alle Struts-acties bevindt de code zich in de methode **execute**. Deze methode:

  • haalt bij de Struts-controller de informatie op die deze via de methode `init` heeft opgeslagen. Dit gebeurt via de methode `getServlet()` van de klasse `Action`.
  • daaronder bevindt zich het attribuut ActionErrors (fouten van de controller). Als deze foutenlijst niet leeg is, wordt deze in de query opgenomen en wordt de weergave van de view erreurs.jsp opgevraagd.
  • Als de foutenlijst leeg is, wordt aan het veld tLogins van de bean formLogins de lijst met aanmeldingen toegewezen die aanvankelijk door de controller is aangemaakt. Vervolgens wordt de weergave van de view logins.jsp opgevraagd, die de lijst met aanmeldingen weergeeft.

7.6.3. De klassen InfosLoginBean en InfosLoginAction

De actie InfosLoginAction heeft tot doel de informatie op te halen die hoort bij de door de gebruiker gekozen login en deze aan de gebruiker te presenteren. De informatie wordt verzameld in een object van het type InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean die de benodigde informatie voor de informatiepagina bevat
  private String titre;
  private String[] infosLogin;

  // constructor
  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];
  }
}

De vorige klasse is een bean, c.a.d. Een Java-klasse waarin een privé-attribuut T unAttribut automatisch wordt gekoppeld aan twee privé-methoden:

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

Let op de speciale syntaxis van de get- en set-methoden. Als het attribuut een array T[] unAttribut is, kunnen er get- en set-methoden worden aangemaakt voor de elementen van de array:

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

Om dit beter te begrijpen, bekijken we nog eens de code van de weergave infos.jsp, die moet worden verzonden na de actie 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>

Laten we de volgende tag eens bekijken:

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

Er wordt gevraagd om de waarde van het veld ‘titel’ (property) van het object infosLoginBean (name) dat in de query (scope) is opgenomen, te schrijven. De te schrijven waarde wordt verkregen via request.getAttribute("infosLoginBean").getTitre(). De methode getTitre moet dus bestaan in de klasse InfosLoginBean. Dat is het geval. De tag

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

vraagt om de waarde van het element infosLogin[0] van het object infosLoginBean, dat in de aanroep is opgegeven, te schrijven. De te schrijven waarde wordt verkregen via request.getAttribute("infosLoginBean").getInfosLogin(0). De methode getInfosLogin(int i) moet dus bestaan in de klasse InfosLoginBean. Dat is het geval.

De klasse InfosLoginAction is bedoeld om het voorgaande object InfosLoginBean te construeren op basis van een door de gebruiker gekozen login. De code ervan is als volgt:

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 {

     // moet de informatie weergeven die bij een login hoort

     // de informatie wordt opgehaald uit de controller-servlet
     // info=(ActionErrors fouten, gebruikers u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // zijn er initialisatiefouten opgetreden?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // Haal eerst deze login op
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // hebben we iets?
    if (login == null) {
      // Dit klopt niet – we sturen het inlogformulier terug
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // we hebben een login – we zoeken ernaar
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // Is het gevonden?
    if (infosLogin == null) {
      // de login is niet gevonden – we tonen de foutpagina
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // de login is gevonden – de gevonden gegevens worden in de verzoek-URL geplaatst
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

De methode **execute** werkt als volgt:

  • de informatie wordt opgehaald die door de Struts-controller tijdens de initialisatie is verzameld. Als de controller fouten heeft geregistreerd, wordt de uitvoering hier gestopt en wordt gevraagd om deze fouten weer te geven.
  • Er wordt gecontroleerd of er daadwerkelijk een login is. Als de gebruiker het formulier voor het kiezen van een login heeft doorlopen, is de login aanwezig. Maar de gebruiker kan de URL van de actie ook rechtstreeks in zijn browser invoeren zonder parameters door te geven. Als er geen login is, wordt de lijst met logins opnieuw weergegeven.
  • Als er een login is, worden de gegevens opgevraagd die gekoppeld zijn aan de businessklasse `users`. Als deze de gezochte login niet vindt, wordt de foutpagina weergegeven. Anders wordt een object InfosLoginBean aangemaakt om daarin de informatie op te nemen die de weergave infos.jsp nodig heeft. Dit object wordt in de query geplaatst en de pagina infos.jsp wordt weergegeven.

7.7. Implementatie

De boomstructuur van de applicatie is als volgt:

 

7.8. Conclusie

We hebben Struts gebruikt in een realistische toepassing met een businessklasse. Daarnaast hebben we aangetoond dat er bijzondere aandacht moet worden besteed aan het verzoek dat door een client wordt verzonden en dat er geen aannames mogen worden gedaan over de aard ervan. Een verzoek kan van alles zijn en elke toepassing moet eerst de geldigheid ervan controleren.