Skip to content

7. Aplikacja QuiEst

Opisujemy tutaj aplikację Struts nieco bardziej zaawansowaną niż poprzednie, które musiały być proste, aby służyć celom dydaktycznym.

7.1. Klasa users

Dysponujemy klasą Java przechowującą informacje o użytkownikach komputera z systemem Unix. Dane te są zapisane w trzech konkretnych plikach:

  • /etc/passwd: lista użytkowników
  • /etc/group: lista grup
  • /etc/aliases: lista aliasów pocztowych

Zawartość tych trzech plików jest następująca:

- /etc/passwd

Wiersze w tym pliku mają następującą postać:


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

z

login
nazwa użytkownika
pwd
jego zaszyfrowane hasło
uid
jego numer użytkownika
gid
jego numer grupy
id
jego tożsamość
dir
jego katalog logowania
shell
jego interpreter poleceń

W ten sposób wiersz użytkownika mógłby wyglądać następująco:

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

Poprzedni użytkownik ma numer 110 i należy do grupy 57. Definicję grupy 57 można znaleźć w pliku /etc/group.

- /etc/group

Wiersze w tym pliku mają następującą postać:


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

gdzie

nomGroupe
nazwa grupy
pwd
zaszyfrowane hasło – najczęściej to pole jest puste
gid
numer grupy
membrei
loginy użytkowników – to pole może być puste

W ten sposób wiersz dotyczący poprzedniej grupy 57 mógłby wyglądać następująco:

iup2-auto::57:

co oznacza, że grupa 57 nosi nazwę iup2-auto.

- /etc/aliases

Wiersze w tym pliku mają następującą postać:

alias:[tab]login

z

alias
alias
[tab]
jednym lub kilkoma znakami tabulacji
login
nazwa użytkownika, do którego należy alias

Zatem wiersz


    guillaume.dupond:    dupond

oznacza, że alias guillaume.dupond należy do użytkownika o nazwie logowania dupond. Przypomnijmy, że aliasy są wykorzystywane w adresach e-mail. Jeśli więc w poprzednim przykładzie komputer z systemem Unix nosi nazwę shiva.istia.univ-angers.fr, wiadomość adresowana do guillaume.dupond@shiva.istia.univ-angers.fr zostanie dostarczona do skrzynki pocztowej użytkownika o nazwie logowania dupond na tym komputerze.

W tym miejscu nie będziemy zajmować się całym interfejsem klasy **users**, a jedynie jej konstruktorem i kilkoma metodami:

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

public class users{


   // atrybuty
  private Hashtable usersByLogin=new Hashtable();       // login --> login, hasło, ..., katalog
    private ArrayList erreurs=new ArrayList();             // lista komunikatów o błędach

....

   // konstruktor
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: nazwa pliku użytkowników zawierającego wiersze w postaci
         // login:pwd:uid:gid:id:dir:shell
         // groupsFileName: nazwa pliku grup zawierającego wiersze w formacie
         // nazwa:hasło:numer:członek1,członek2,..
         // aliasesFileName: nazwa pliku zawierającego aliasy z wierszami o postaci
         // alias:[tab]login
         // tworzy słownik usersByLogin
....
    }// konstruktor

     // lista użytkowników
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // błędy
  public ArrayList getErreurs(){
    return erreurs;
  }
usersByLogin
słownik (tablica skrótów), którego kluczami są nazwy użytkowników z pliku passwd. Wartością powiązaną z kluczem jest tablica ciągów znaków (String [7]), której elementami jest 7 pól wiersza pliku passwd powiązanego z daną nazwą użytkownika. Niektóre pola mogą być puste, jeśli wiersz zawiera mniej niż 7 pól.
erreurs
lista komunikatów o błędach – pusta, jeśli nie ma błędów

7.2. Aplikacja internetowa, która jest

Proponujemy stworzyć następującą aplikację internetową (stronę z formularzem):

nr
nazwa
typ HTML
rola
1
cmbLogins
<select ...>...</select>
przedstawia listę wszystkich loginów, o których można uzyskać informacje
2
btnChercher
<input type="submit" ...>
służy do uruchomienia wyszukiwania

Gdy użytkownik kliknie przycisk [Chercher] (2), logowanie z pola (1) jest wysyłane do obiektu U typu users. Jeśli logowanie istnieje, otrzymujemy następującą odpowiedź (strona informacyjna):

Jak widać powyżej na przykładzie URL z przeglądarki, parametry formularza są wysyłane na serwer za pomocą GET. Można zatem bezpośrednio przekazać przeglądarce ten skonfigurowany URL. Właśnie to tutaj robimy, aby wprowadzić login, który nie istnieje. Otrzymujemy następującą odpowiedź (strona błędów):

7.3. Architektura aplikacji

W tej architekturze znajdujemy następujące komponenty:

  • widoki:
    • logins.jsp, służąca do wyświetlania listy logowań (widok 1)
    • infos.jsp, służąca do wyświetlania informacji dotyczących loginu (widok 2)
    • erreurs.jsp służąca do wyświetlania listy błędów (widok 3)
  • formularze typu ActionForm wykorzystywane przez akcje:
    • formLogins służy do zbierania danych z formularza logins.jsp
  • akcje:
    • SetupLoginAction, która przygotowuje zawartość formulaire.jsp, a następnie wyświetla ten widok
    • InfosLoginAction, który przetwarza zawartość logins.jsp po jej wysłaniu na serwer
    • ForwardAction, która przetwarza link [Retour vers le formulaire] z widoków infos.jsp i erreurs.jsp
  • klasa biznesowa „users” wykorzystywana przez akcje do pobierania danych
  • model zapewniany przez trzy pliki tekstowe: „passwd”, „group” i „aliases”

7.4. Pliki konfiguracyjne aplikacji internetowej

7.4.1. Plik server.xml

Kontekst aplikacji będzie nosił nazwę /strutsquiest2. W związku z tym dodamy następujący wiersz do pliku server.xml serwera Tomcat:

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

Po wykonaniu tej czynności ewentualnie uruchomimy ponownie Tomcat, aby uwzględnił nowy kontekst. Możemy sprawdzić jego poprawność, wywołując adres http://localhost:8080/strutsquiest2.

7.4.2. Plik web.xml

Plik konfiguracyjny aplikacji web.xml będzie wyglądał następująco:

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

Ten plik web.xml wprowadza nowość. Kontroler Struts nie nosi już nazwy org.apache.struts.action.ActionServlet, lecz jest to klasa pochodna, którą nazwaliśmy tutaj istia.st.struts.quiest.Quiest2ActionServlet. Pozwoli nam to pobrać dwa parametry inicjalizacyjne: passwdFileName (lokalizacja pliku passwd) oraz groupFileName (lokalizacja pliku group). Plik aliases jest zbędny w tej aplikacji.

7.4.3. Plik struts-config.xml

Plik struts-config.xml będzie wyglądał następująco:

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

Znajdują się tu trzy główne sekcje:

  • deklarację formularzy w sekcji <form-beans>
  • deklaracja akcji w sekcji <action-mappings>
  • deklaracja pliku zasobów w elemencie <message-ressources>

7.4.4. Obiekty (beany) formularzy aplikacji

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

W naszej aplikacji występuje tylko jeden bean formularza o nazwie formLogins, którego typ wywodzi się od DynaActionForm. Będzie on wykorzystywany w następujących sytuacjach:

  • przechowywanie danych niezbędnych do wyświetlenia widoku nr 1
  • pobieranie wartości z formularza widoku nr 1 po jego zatwierdzeniu (submit) przez użytkownika

Struktura bean’a formLogins jest powiązana z formularzem w widoku nr 1. Przyjrzyjmy się jej:

nr
nazwa
typ HTML
rola
1
cmbLogins
<select ...>...</select>
przedstawia listę wszystkich loginów, o których można uzyskać informacje
2
btnChercher
<input type="submit" ...>
aby rozpocząć wyszukiwanie

Rozróżnijmy kilka przypadków:

  • z klienta do serwera – obiekt formLogins służy do przechowywania wartości z powyższego formularza HTML, który zostanie wysłany za pomocą przycisku [Envoyer]. Potrzebne jest zatem pole cmbLogins, które przyjmie wartość pola HTML, cmbLogins oraz c.a.d – czyli login wybrany przez użytkownika.
  • Podczas przesyłania danych z serwera do klienta obiekt formLogins służy do przekazania początkowej zawartości widoku nr 1. Jego pole tLogins będzie stanowić zawartość listy 1. Jego pole cmbLogins pozwoli określić element listy 1, który ma zostać zaznaczony.

7.4.5. Akcje aplikacji

Akcje są realizowane przez obiekty typu Action lub pochodne. Konfiguracja akcji odbywa się wewnątrz tagów <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>

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

Opiszmy działanie akcji /init:

  • Operacja /init odbywa się zazwyczaj tylko raz podczas pierwszego cyklu żądanie-odpowiedź, w którym użytkownik wysyła żądanie dotyczące obiektu URL http://localhost:8080/strutsquiest2/init.do
  • obiekt formsLogins jest tworzony lub ponownie wykorzystywany. Jest on pobierany (ponowne wykorzystanie) lub umieszczany (tworzenie) w sesji zgodnie z atrybutem scope.
  • Wywoływana jest jego metoda reset. Należy pamiętać, że domyślnie metoda ta nie wykonuje żadnych czynności w klasach ActionForm i klasach od nich pochodnych. Jest ona wywoływana tuż przed skopiowaniem danych z żądania klienta do obiektu ActionForm i służy do wyczyszczenia obiektu przed tym skopiowaniem. Jakie jest w tym przypadku żądanie klienta? Akcja /init jest uruchamiana, gdy żądany adres URL to http://localhost:8080/strutsquiest2/init.do. Ten obiekt URL może być wywołany przez obiekt GET lub POST. Wystarczy, że w tym żądaniu umieści się parametry o nazwach pól z formLogins, aby zostały one zainicjowane, jak pokazuje poniższy przykład:

Image

  • zapytanie zawiera parametr cmbLogins (afterpak). Kontroler Struts skopiował zatem wartość tego parametru do pola cmbLogins w formLogins. Następnie przeprowadzono akcję SetupLoginsAction, która zakończyła się wyświetleniem widoku logins.jsp. Widok ten zawiera formularz, w którym niektóre pola pobierają swoje wartości z formLogins. W ten sposób pole wyboru HTML o nazwie cmbLogins otrzymało swoją wartość z pola cmbLogins (=afterpak) z formLogins. Dlatego lista loginów pojawia się z pozycją ustawioną na loginie afterpak.
  • Można by również dla zabawy przekazać parametr tLogins w następujący sposób:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Spowodowałoby to zainicjowanie pola tLogins o wartości formLogins wraz z tablicą {"login1","login2"}. Jednak, jak zobaczymy dalej, akcja SetupLoginsAction nadaje wartość polu tLogins i zastępuje utworzoną w ten sposób tablicę nową tablicą. To właśnie ta ostatnia pojawia się zatem w widoku logins.jsp.

  • Powyższa dyskusja, choć nieco skomplikowana, ma tę zaletę, że pokazuje, iż nie można zakładać, że akcja /init zostanie wywołana bez parametrów pochodzących od klienta. Dlatego też przydatne może być użycie metody reset w celu wyczyszczenia pola formLogins. W takim przypadku musielibyśmy utworzyć klasę pochodną od klasy DynaActionForm. Nie zrobiliśmy tego w tym przypadku.
    • Po wywołaniu metody reset klasy formLogins kontroler kopiuje dane z żądania klienta do pól o tej samej nazwie w klasie formLogins. Zazwyczaj akcja /init jest wywoływana bez parametrów od klienta, ale jak wykazaliśmy wcześniej, nic nie stoi na przeszkodzie, aby klient wywołał akcję /init z dowolnymi parametrami. Po zakończeniu tej fazy pola cmbLogins i tLogins mogą zatem z całą pewnością mieć przypisane wartości. Widzieliśmy, że pole cmbLogins zachowałoby tę wartość, ale nie pole tLogins.
    • Następnie kontroler sprawdza atrybut `validate` akcji. W tym przypadku ma on wartość „false”. Metoda `validate` dla pola formLogins nie zostanie wywołana. Nie będziemy jej więc pisać.
    • Obiekt SetupLoginsAction jest tworzony lub odnawiany, jeśli już istniał, a następnie uruchamiana jest jego metoda `execute`. Jego jedyną rolą jest przypisanie wartości do pola tLogins w obiekcie formLogins. Wartością tą jest tablica loginów, o którą zostanie zwrócona prośba do klasy biznesowej users. Operacja ta może zakończyć się niepowodzeniem. Dlatego po akcji /init mogą następować dwa widoki:
      • widok erreurs.jsp, jeśli klasa „users” nie była w stanie dostarczyć tablicy loginów
      • widok logins.jsp w przeciwnym razie
    • kontroler wyświetli jeden z tych dwóch widoków
    • cykl żądania-odpowiedzi akcji /init został zakończony.

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

Opiszmy działanie akcji / infosLogin:

  • akcja /infosLogin jest zwykle uruchamiana, gdy użytkownik kliknie przycisk [Chercher] w widoku logins.jsp. Następnie wysyłane jest żądanie do serwera, określone przez tag HTML <form> w widoku:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • widać, że żądanie jest wysyłane do serwera metodą GET. Użytkownik może zatem wpisać je ręcznie:

Image

  • obiekt formsLogins jest tworzony lub ponownie wykorzystywany. Jest on pobierany (ponowne wykorzystanie) lub umieszczany (tworzenie) w sesji zgodnie z atrybutem scope.
  • Jego metoda reset jest wywoływana tuż przed skopiowaniem danych z żądania klienta do obiektu ActionForm. Zazwyczaj ma ona postać http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, gdzie xx to login wybrany z listy loginów. Może to jednak być dowolna wartość, jeśli użytkownik skorzystał z poprzedniego obiektu URL, przekazując dowolne parametry. Rozważmy następującą sekwencję stron:

Image

  • akcja /infosLogin została wywołana z ciągiem parametrów cmbLogins=xx&tLogins=login1&tLogins=login2. Pola cmbLogins i tLogins w formLogins otrzymają zatem odpowiednio wartości „xx” oraz {"login1","login2"}. Akcja /infosLogin zwróci się do klasy biznesowej users o informacje związane z loginem „xx”. Klasa users odpowie, że ten login nie istnieje. Stąd widok przedstawiony powyżej. Teraz skorzystajmy z powyższego linku [Retour au formulaire]:

Image

  • To akcja /retourLogins jest uruchamiana przez link [Retour au formulaire]. Akcja ta po prostu wyświetla widok logins.jsp bez żadnych czynności pośrednich. Przypomnijmy, że pole tLogins służy do zasilania listy loginów w widoku logins.jsp. Ponieważ użytkownik zmienił tę wartość na {"login1","login2"}, to właśnie te dwa loginy pojawiają się teraz na liście. Po raz kolejny należy podkreślić absolutną konieczność uwzględnienia w działaniu aplikacji przypadków dowolnych parametrów ustalanych przez użytkownika lub program. Rozwiązaniem przedstawionego tutaj problemu byłoby skierowanie linku [Retour au formulaire] do akcji /init. W ten sposób mielibyśmy pewność, że otrzymamy prawidłową listę loginów.
  • Wróćmy do zwykłego żądania skierowanego do akcji /infosLogin, na przykład:

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

  • Kontroler Struts przypisze wartość do pola cmbLogins obiektu ActionForm. Natomiast pole tLogins nie zostanie przypisane (w wysłanym zapytaniu nie ma odpowiadającego mu pola). Takie działanie nam odpowiada. Nie będziemy więc musieli pisać własnej metody resetującej dla obiektu formLogins.
    • Po wywołaniu metody reset obiektu formLogins kontroler kopiuje dane z żądania klienta do pól o tej samej nazwie w obiekcie formLogins. Pole cmbLogins otrzyma wartość – login wybrany przez użytkownika (afterpak).
    • Następnie kontroler sprawdza atrybut „validate” akcji. W tym przypadku ma on wartość „false”. Metoda „validate” obiektu formLogins nie zostanie wywołana.
    • Obiekt InfosLoginAction zostaje utworzony lub odnowiony, jeśli już istniał, a następnie uruchamiana jest jego metoda execute. Jego zadaniem jest pobranie informacji związanych z loginem cmbLogins. Informacje te zostaną pobrane z klasy biznesowej users. Operacja ta może zakończyć się niepowodzeniem (na przykład w przypadku nieistniejącego loginu). Dlatego po akcji /infosLogin mogą następować dwa widoki:
      • widok erreurs.jsp, jeśli klasa „users” nie była w stanie dostarczyć żądanych informacji
      • widok infos.jsp w przeciwnym razie
    • kontroler wyświetli jeden z tych dwóch widoków
    • cykl żądania-odpowiedzi akcji /infosLogin został zakończony.

Akcja /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • Akcja /retourLogins jest uruchamiana poprzez aktywację linku [Retour au formulaire] w widokach erreurs.jsp i infos.jsp.
  • W tym przypadku nie ma formularza powiązanego z akcją. Przechodzi się zatem bezpośrednio do wykonania metody `execute` obiektu `ForwardAction`, która zwróci obiekt `ActionForward` wskazujący na widok `/vues/logins.jsp`.

7.4.6. Plik komunikatów aplikacji

Trzecia sekcja pliku struts-config.xml to sekcja pliku komunikatów:

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

Plik ApplicationResources.properties znajduje się w katalogu WEB-INF/classes/istia/st/struts/quiest. Jego zawartość jest następująca:

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. Kod widoków

Jeśli czytelnik nie rozumie kodu widoków przedstawionych poniżej, prosimy o ponowne zapoznanie się z lekcją dotyczącą obsługi formularzy.

7.5.1. Widok logins.jsp

Przypomnijmy, że ten widok jest wyświetlany w dwóch przypadkach:

  • przy wywołaniu akcji /init podczas pierwszego cyklu żądanie-odpowiedź
  • przy wywołaniu akcji /retourLogins w kolejnych cyklach

Kod widoku logins.jsp jest następujący:

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

Ten widok jest wyświetlany po pomyślnym wywołaniu akcji /infosLogin. Jego kod jest następujący:

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

Ten widok wykorzystuje obiekt o nazwie infosLoginBean, umieszczony w zapytaniu przez akcję /infosLogin. Obiekt ten posiada dwa pola:

String titre;                        // tytuł do wyświetlenia w widoku
String[] infosLogin;        // tabela informacji do wyświetlenia w widoku

Szczegółowo omówimy tę klasę, gdy przejdziemy do kodu klasy InfosLoginAction.

7.5.3. Widok erreurs.jsp

Ten widok jest wyświetlany, gdy operacje /init lub /infosLogin zakończą się błędem. Jego kod wygląda następująco:

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

Plik web.xml odwołuje się do klasy Java:

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

...
</web-app>

Plik konfiguracyjny struts-config.xml odwołuje się do dwóch klas 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. Klasa Quiest2ActionServlet

Klasa Quiest2ActionServlet wywodzi się z klasy ActionServlet, czyli klasy kontrolera Struts. Tworzymy klasę pochodną ActionServlet, aby dostosować jej metodę init. Metoda ta, wykonywana tylko raz podczas początkowego ładowania serwletu, pozwoli nam bowiem utworzyć obiekt biznesowy typu users. Obiekt ten musi zostać utworzony tylko raz, a metoda init jest odpowiednim miejscem do wykonania tej operacji. Obiekt typu „users” wymaga dwóch plików do utworzenia: plików „passwd” i „group”. Lokalizacja tych dwóch plików jest przekazywana jako parametry do serwletu w pliku web.xml aplikacji:

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

Kod serwletu wygląda następująco:

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 {

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

   //init
  public void init() throws ServletException {

    // nie zapomnij zainicjować klasy nadrzędnej
    super.init();

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

    // pobieramy parametry inicjalizacyjne serwletu
    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 = "";
      }
       // zapisujemy parametr
      params.setProperty(initParams[i], valeur);
    } //for
     // powrót, jeśli wystąpiły błędy inicjalizacji
    if (erreurs.size() != 0) {
      return;
    }
     // tworzy się obiekt 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
     // pobieramy listę loginów
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // sortujemy nazwy użytkowników
    Arrays.sort(tLogins);
  } //init

   // metoda dostępu do prywatnych informacji serwletu
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

W skrócie, działanie metody init wygląda następująco:

  • najpierw wywoływana jest metoda init klasy nadrzędnej (ActionServlet), aby ta mogła się poprawnie zainicjować
  • następnie odczytywane są parametry inicjalizacji. Jeśli którychś brakuje, wypełniany jest prywatny atrybut ActionErrors „błędy”.
  • jeśli parametry inicjalizacji są obecne, tworzony jest obiekt `users`. Tworzenie tego obiektu może spowodować wygenerowanie wyjątku. W takim przypadku wypełniany jest atrybut `ActionErrors` `erreurs`.
  • Jeśli tworzenie przebiegło pomyślnie, z utworzonego obiektu pobierana jest lista wszystkich loginów, a następnie jest ona sortowana do tablicy, którą umieszcza się w prywatnym atrybucie String[] tLogins.
  • Utworzony obiekt users jest zapisywany w prywatnym atrybucie users u.
  • Metoda publiczna getInfos pozwala uzyskać trzy atrybuty prywatne (u, błędy, tLogins) w postaci tablicy obiektów.

7.6.2. Klasa SetupLoginsAction

Celem tej akcji jest zainicjowanie obiektu DynaActionForm formLogins. Obiekt ten, umieszczony w sesji, nie będzie już wymagał ponownego inicjowania w przyszłości. Akcja SetupLoginsAction odbywa się zatem tylko raz. Jej kod jest następujący:

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 {

     // przygotowuje formularz do wyświetlenia
     // pobieranie informacji z serwletu kontrolera
     // informacje=(ActionErrors błędy, użytkownicy u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // czy wystąpiły błędy podczas inicjalizacji?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // w formularzu wpisujemy dane logowania
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

Podobnie jak w przypadku wszystkich akcji Struts, kod znajduje się w metodzie execute. Metoda ta:

  • pobiera z kontrolera Struts informacje, które ten zapisał za pomocą swojej metody init. Umożliwia to metoda getServlet() klasy Action.
  • wśród nich znajduje się atrybut ActionErrors – błędy kontrolera. Jeśli ta lista błędów nie jest pusta, jest ona umieszczana w zapytaniu i wyświetlany jest widok erreurs.jsp.
  • Jeśli lista błędów jest pusta, wówczas do pola tLogins w bean formLogins przypisywana jest lista logowań utworzona początkowo przez kontroler. Następnie wywoływane jest wyświetlenie widoku logins.jsp, który wyświetli listę logowań.

7.6.3. Klasy InfosLoginBean i InfosLoginAction

Akcja InfosLoginAction ma na celu pobranie informacji związanych z loginem wybranym przez użytkownika i przedstawienie ich użytkownikowi. Informacje te zostaną zebrane w obiekcie typu InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean zawierający informacje niezbędne dla strony informacyjnej
  private String titre;
  private String[] infosLogin;

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

   // metody pobierające
  public String getTitre(){
    return this.titre;
  }
  public String[] getInfosLogin(){
    return this.infosLogin;
  }
  public String getInfosLogin(int i){
    return this.infosLogin[i];
  }
}

Poprzednia klasa to bean o nazwie c.a.d. Jest to klasa Java, w której prywatny atrybut T unAttribut jest automatycznie powiązany z dwiema metodami prywatnymi:

  • void setUnAttribut(T wartość){unAttribut=wartość;}
  • T getUnAttribut(){ return unAttribut;}

Warto zwrócić uwagę na specjalną składnię metod get i set. Jeśli atrybutem jest tablica T[] unAttribut, można utworzyć metody get i set dla elementów tej tablicy:

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

Aby lepiej to zrozumieć, przyjrzyjmy się kodowi widoku infos.jsp, który powinien zostać wysłany w następstwie akcji 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>

Weźmy następujący tag:

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

Żądanie dotyczy zapisania wartości pola „title” (property) obiektu infosLoginBean (name) umieszczonego w zapytaniu (scope). Wartość do zapisania zostanie uzyskana za pomocą request.getAttribute("infosLoginBean").getTitre(). Musi zatem istnieć metoda getTitre w klasie InfosLoginBean. Tak właśnie jest. Tag

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

żąda zapisania wartości elementu infosLogin[0] obiektu infosLoginBean podanego w zapytaniu. Wartość do zapisania zostanie uzyskana za pomocą request.getAttribute("infosLoginBean").getInfosLogin(0). W związku z tym w klasie InfosLoginBean musi istnieć metoda getInfosLogin(int i). Tak właśnie jest.

Klasa InfosLoginAction służy do utworzenia poprzedniego obiektu InfosLoginBean na podstawie loginu wybranego przez użytkownika. Jej kod wygląda następująco:

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 {

     // musi wyświetlać informacje związane z danymi logowania

     // pobieramy informacje z serwletu kontrolera
     // informacje=(ActionErrors błędy, użytkownicy u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // czy wystąpiły błędy podczas inicjalizacji?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // najpierw pobierz ten login
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // czy jest coś?
    if (login == null) {
      // To nie w porządku – odsyłamy formularz z danymi logowania
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // mamy login – szukamy go
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // czy znaleźliśmy?
    if (infosLogin == null) {
      // nazwa użytkownika nie została znaleziona – wyświetlamy stronę błędów
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // nazwa użytkownika została znaleziona – umieszczamy znalezione informacje w żądaniu
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

Działanie metody **execute** przebiega następująco:

  • pobierane są informacje zebrane przez kontroler Struts podczas jego inicjalizacji. Jeśli kontroler odnotował błędy, wykonanie zatrzymuje się w tym momencie i wyświetlane jest okno z listą tych błędów.
  • sprawdza się, czy podano login. Jeśli użytkownik skorzystał z formularza wyboru loginu, login jest obecny. Użytkownik może jednak równie dobrze wpisać adres URL akcji bezpośrednio w przeglądarce, bez przekazywania parametrów. Jeśli nie ma loginu, wyświetlana jest ponownie lista loginów.
  • Jeśli login istnieje, pobierane są informacje powiązane z klasą biznesową users. Jeśli klasa ta nie znajdzie szukanego loginu, wyświetlana jest strona błędów. W przeciwnym razie tworzony jest obiekt InfosLoginBean, w którym umieszczane są informacje potrzebne widokowi infos.jsp. Obiekt ten jest umieszczany w żądaniu, a następnie wyświetlana jest strona infos.jsp.

7.7. Wdrożenie

Struktura drzewa aplikacji wygląda następująco:

 

7.8. Wnioski

Wykorzystaliśmy Struts w realistycznej aplikacji opartej na klasie biznesowej. Pokazaliśmy również, że należy zwracać szczególną uwagę na żądanie wysyłane przez klienta i nie przyjmować żadnych założeń dotyczących jego charakteru. Żądanie może mieć dowolną postać, a każda aplikacja musi najpierw sprawdzić jego poprawność.