Skip to content

11. Przykład 09 – Konwersja i walidacja liczb całkowitych

Przechodzimy teraz do serii przykładów dotyczących konwersji i walidacji parametrów formularza. Problem jest następujący. Aby przetworzyć URL o postaci [http://machine:port/.../Action], kontroler [FilterDispatcher] tworzy instancję klasy, która implementuje żądaną akcję, i wywołuje jedną z jej metod – domyślnie metodę o nazwie execute. Wywołanie tej metody execute przechodzi przez szereg interceptorów:

Lista interceptorów jest zdefiniowana w pliku [struts-default.xml] znajdującym się w katalogu głównym archiwum [struts2-core.jar]. Zdefiniowana tam lista interceptorów wygląda następująco:


             <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params">
                  <param name="excludeParams">dojo\..*,^struts\..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
</interceptor-stack>

Wśród interceptorów znajduje się jeden, który odpowiada za wstawienie do akcji wartości valeuri parametrów parami, które towarzyszą żądaniu w postaci parami=valeuri. Wiadomo, że wartość valeuri zostanie wstrzyknięta do pola parami akcji za pomocą metody setParami, o ile ta metoda istnieje. W przeciwnym razie nie dochodzi do wstrzyknięcia i nie zgłaszany jest żaden błąd.

Ciąg parami=valeuri jest ciągiem znaków. Do tej pory wstawianie wartości valeuri odbywało się w polach parami typu String:

private String parami ;

Wprowadzenie ciągu znaków valeuri jako wartości ciągu znaków parami nie stanowiło problemu. Jeśli parami nie jest typu String, wówczas valeuri musi zostać przekonwertowane z typu parami na typ Ti. Na tym polega problem konwersji. Na przykład, chcemy, aby wiek był liczbą całkowitą, i w akcji zapiszemy:

private int age ;

Ponadto możemy chcieć ograniczyć wiek do przedziału od 1 do 150. Mamy tu do czynienia z problemem walidacji. Parametr parami może zostać przekonwertowany na właściwy typ, ale niekoniecznie będzie prawidłowy. Należy więc wykonać dwa etapy. Wracając do schematu przetwarzania zapytania:

Dwa interceptory zajmą się odpowiednio konwersją i walidacją parametrów. Jeśli którykolwiek z etapów zakończy się niepowodzeniem, żądanie nie będzie kontynuowane w kierunku akcji (czerwony obwód powyżej). Formularz, z którego wysłano błędne parametry, zostanie ponownie wyświetlony wraz z komunikatami o błędach.

Interceptory odpowiedzialne za konwersję i walidację parametrów to interceptory conversionError i validation z wierszy 19 i 20 listy interceptorów przedstawionej powyżej. W wierszach 20–22 należy zauważyć, że interceptor validation nie jest stosowany, jeśli wywoływana metoda jest jedną z metod input, back, cancel, browse. W dalszej części wykorzystamy tę właściwość.

Zaczniemy od omówienia konwersji i walidacji liczb całkowitych. Poświęcimy nieco czasu na ten pierwszy przykład, ponieważ walidacja obejmuje wiele elementów. Gdy już je poznamy, kolejne przykłady omówimy szybciej.

11.1. Formularz

  • na [1], formularz wprowadzania danych
  • na [2], wynik po zatwierdzeniu bez wprowadzania wartości

11.2. Projekt NetBeans

Projekt NetBeans wygląda następująco:

  • w [1], trzy widoki aplikacji
  • w pliku [2]: kody źródłowe, pliki z międzynarodowymi komunikatami oraz pliki konfiguracyjne Struts.

11.3. Konfiguracja Struts

Aplikacja jest konfigurowana za pomocą plików [struts.xml] i [example.xml].

Plik [struts.xml] ma następującą treść:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <constant name="struts.custom.i18n.resources" value="messages" />
  
  <include file="example/example.xml"/>

  <package name="default" namespace="/" extends="struts-default">
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Accueil</param>
        <param name="namespace">/example</param>
      </result>
    </action>
  </package>
</struts>

Wiersze 12–18 definiują akcję [/example/Accueil] jako akcję domyślną, gdy użytkownik nie określi innej.

Plik [example.xml] ma następującą treść:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Accueil">
      <result name="success">/example/Accueil.JSP</result>
    </action>
    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
    </action>
  </package>
</struts>
  • wiersze 8–10: akcja [Accueil] powoduje wyświetlenie widoku [Accueil.JSP]
  • wiersz 11: akcja [FormInt] domyślnie uruchamia metodę execute klasy [example.FormInt]. Zobaczymy, że zostaną wykonane dwie inne metody: input i cancel. Metody te zostaną następnie określone w parametrach zapytania.
  • wiersz 12: klucz input spowoduje wyświetlenie widoku [FormInt.JSP] (wiersz 5). Jest to widok formularza.
  • wiersz 13: klucz cancel zostanie zwrócony przez metodę cancel powiązaną z linkiem [Annuler]. Wyświetlany widok będzie wówczas widokiem [Accueil.JSP] po przekierowaniu (type=redirect).
  • Wiersz 14: klucz success jest zwracany przez metodę execute akcji [FormInt]. Jeśli żądanie dotrze do metody execute, oznacza to, że pomyślnie przeszło przez wszystkie interceptory, w szczególności te sprawdzające poprawność parametrów. Metoda execute ogranicza się wówczas do zwrócenia klucza success, który spowoduje wyświetlenie widoku potwierdzenia [ConfirmationInt.JSP].

11.4. Pliki komunikatów

Plik [messages.properties] ma następującą treść:


Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormInt=Saisie de nombres entiers
Form.titre=Conversions et validations
FormInt.message=Struts 2 - Conversion et validation de nombres entiers
Form.submitText=Valider
Form.cancelText=Annuler
Form.clearModel=Raz mod\u00e8le
Confirmation.titre=Confirmation
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.lien=Formulaire de test

Oprócz tego pliku widoki wykorzystują następujący plik [FormInt.properties]:


int1.prompt=1-Nombre entier positif de deux chiffres
int1.error=Tapez un nombre entier positif de deux chiffres
int2.prompt=2-Nombre entier
int2.error=Tapez un nombre entier
int3.prompt=3-Nombre entier >=-1
int3.error=Tapez un nombre entier >=-1
int4.prompt=4-Nombre entier <=10
int4.error=Tapez un nombre entier <=10
int5.prompt=5-Nombre entier dans l''intervalle [1,10]
int5.error=Tapez un nombre entier dans l''intervalle [1,10]
int6.prompt=6-Nombre entier dans l''intervalle [2,20]
int6.error=Tapez un nombre entier dans l''intervalle [2,20]

Plik [FormInt.properties] jest wykorzystywany tylko wtedy, gdy akcją, która wygenerowała widok, jest akcja [FormInt]. Jest to sposób na podzielenie pliku komunikatów, jeśli jest on zbyt duży. Komunikaty akcji „Action” są dostosowywane do różnych języków w pliku [Action.properties].

11.5. Widoki i akcje

Przedstawiamy teraz widoki i akcje aplikacji. Zgodnie z konfiguracją aplikacji:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Accueil">
      <result name="success">/example/Accueil.JSP</result>
    </action>
    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
    </action>
  </package>
</struts>

widać, że istnieją trzy widoki [Accueil.JSP, FormInt.JSP, ConfirmationFormInt.JSP] i dwie akcje [Accueil, FormInt].

11.5.1. Accueil.JSP

Widok [Accueil.JSP] wygląda następująco:

 

Jego kod wygląda następująco:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Accueil.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Accueil.message"/></h2>
    <ul>
      <li>
        <s:url id="URL" action="FormInt!input"/>
        <s:a href="%{URL}"><s:text name="Accueil.FormInt"/></s:a>
      </li>
    </ul>
  </body>
</html>

Link w wierszu 14 generuje następujący kod HTML:

<a href="<a href="view-source:http://localhost:8084/exemple-09/example/FormInt.action">/exemple-09/example/FormInt!input.action</a>">Saisie de nombres entiers</a>

Jest to zatem link do akcji [FormInt] skonfigurowanej w następujący sposób w [example.xml]:


    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
</action>

Kliknięcie tego linku spowoduje zatem utworzenie instancji klasy [example.FormInt] i wywołanie jej metody input. Ponieważ ta metoda nie istnieje, zostanie wykonana metoda input klasy nadrzędnej ActionSupport. Metoda ta nie wykonuje żadnych działań poza zwróceniem klucza input. W związku z tym wyświetlony zostanie widok [/example/FormInt.JSP].

Ponadto metoda input jest jedną z metod ignorowanych przez interceptor walidacji:


        <interceptor-ref name="validation">
          <param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>

W związku z tym nie nastąpi walidacja parametrów. Jest to istotne, ponieważ w tym przypadku nie ma żadnych parametrów, a jak zobaczymy później, reguły walidacji będą wymagały obecności sześciu parametrów.

11.5.2. Akcja [FormInt]

Akcja [FormInt] jest powiązana z następującą klasą [FormInt]:


package example;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;

public class FormInt extends ActionSupport implements ModelDriven, SessionAware {

  // konstruktor bez parametrów
  public FormInt() {
  }

  // szablon akcji
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
  }

  public String cancel() {
    // czyścimy model
    ((FormIntModel) getModel()).clearModel();
    // wynik
    return "cancel";
  }

  @SkipValidation
  public String clearModel() {
    // wyzerowanie modelu
    ((FormIntModel) getModel()).clearModel();
    // wynik
     return INPUT;
  }
  
  // SessionAware
  private Map<String, Object> session;

  public void setSession(Map<String, Object> session) {
    this.session = session;
  }

  // weryfikacja
  @Override
  public void validate() {
    // czy wprowadzone int6 jest prawidłowe?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
  }
}

Będziemy omawiać ten kod w miarę potrzeb. Na razie:

  • w wierszu 9 klasa [FormInt] implementuje dwa interfejsy:
    • ModelDriven, która ma tylko jedną metodę, oraz getModel z wiersza 16
    • SessionAware, która posiada tylko jedną metodę, setSession z linii 41
  • wiersze 16–21: implementacja interfejsu ModelDriven. Przypominamy, że interfejs ten pozwala przenieść model widoku do klasy zewnętrznej, w tym przypadku do następującej klasy [FormIntModel]:

package example;

public class FormIntModel {

  // konstruktor bez parametrów
  public FormIntModel() {
  }

  // pola formularza
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
  private String int6;

  // szablon „raz”
  public void clearModel(){
    int1=null;
    int2=null;
    int3=null;
    int4=null;
    int5=null;
    int6=null;
  }

  // metody pobierające i ustawiające
   ...
}

Model [FormIntModel] posiada sześć pól odpowiadających sześciu polom wprowadzania danych w widoku [FormInt.JSP]. To właśnie te sześć pól otrzyma przesłane wartości. Cztery z nich mają typ Integer. W związku z tym pojawi się problem konwersji String --> Integer. Metoda clearModel pozwala zresetować model.

Wróćmy do metody getModel akcji [FormInt]:


  // model akcji
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}
  • wiersze 3–5: szukany jest model w sesji. Jeśli go tam nie ma, tworzona jest instancja modelu i umieszczana w sesji.
  • wiersz 6: podczas gdy instancja akcji jest tworzona przy każdym nowym wywołaniu akcji, jej model pozostaje w sesji.

Widzimy, że klasa nie definiuje metody input, ale klasa nadrzędna posiada metodę, która zwraca klucz input. Wywołanie tej metody powoduje wyświetlenie widoku [FormInt.JSP], który teraz przedstawiamy.

11.5.3. Widok [FormInt.JSP]

Widok [FormInt.JSP] wygląda następująco:

  • w widoku [1] – pusty formularz
  • w [2] – formularz po walidacji błędnych parametrów.

Jego kod wygląda następująco:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="FormInt.message"/></h2>
    <s:form name="formulaire" action="FormInt">
      <s:textfield name="int1" key="int1.prompt"/>
      <s:textfield name="int2" key="int2.prompt"/>
      <s:textfield name="int3" key="int3.prompt"/>
      <s:textfield name="int4" key="int4.prompt"/>
      <s:textfield name="int5" key="int5.prompt"/>
      <s:textfield name="int6" key="int6.prompt"/>
      <s:submit key="Form.submitText" method="execute"/>
    </s:form>
    <br/>
    <s:url id="URL" action="FormInt" method="cancel"/>
    <s:a href="%{URL}"><s:text name="Form.cancelText"/></s:a>
      <br/>
    <s:url id="URL" action="FormInt" method="clearModel"/>
    <s:a href="%{URL}"><s:text name="Form.clearModel"/></s:a>
  </body>
</html>
  • wiersze 12–17: sześć pól wprowadzania danych odpowiadających sześciu polom szablonu [FormIntModel] akcji [FormInt]. Podczas wyświetlania widoku to właśnie atrybuty value pól wprowadzania danych są wykorzystywane do wyświetlania wartości w tych polach. W przypadku braku atrybutu value wykorzystywany jest atrybut name.
  • wiersz 12: pole wprowadzania danych jest powiązane (name) z polem int1 akcji lub jej szablonu, jeśli akcja implementuje interfejs ModelDriven. Tak jest w tym przypadku. Dotyczy to również wszystkich pozostałych pól.
  • wiersz 18: przycisk [Valider] przesyła wprowadzone dane do akcji [FormInt] zdefiniowanej w wierszu 11. Zostanie wykonana jej metoda execute.
  • wiersze 21–22: link [Annuler] uruchamia metodę [FormInt.cancel].
  • wiersze 24–25: odnośnik [Raz modèle] uruchamia metodę [FormInt.clearModel].

11.5.4. Widok [ConfirmationFormInt.JSP]

Jest wyświetlany, gdy wszystkie dane wprowadzone w formularzu [FormInt.JSP] są prawidłowe.

  • w [1] księguje się prawidłowe wartości
  • w widoku [2] wyświetlana jest strona potwierdzenia

Kod widoku [ConfirmationInt.JSP] jest następujący:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Confirmation.titre"/></title>
    <s:head/>
  </head>

  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="int1.prompt"/></td>
        <td><s:property value="int1"/></td>
      </tr>
      <tr>
        <td><s:text name="int2.prompt"/></td>
        <td><s:property value="int2"/></td>
      </tr>
      <tr>
        <td><s:text name="int3.prompt"/></td>
        <td><s:property value="int3"/></td>
      </tr>
      <tr>
        <td><s:text name="int4.prompt"/></td>
        <td><s:property value="int4"/></td>
      </tr>
      <tr>
        <td><s:text name="int5.prompt"/></td>
        <td><s:property value="int5"/></td>
      </tr>
      <tr>
        <td><s:text name="int6.prompt"/></td>
        <td><s:property value="int6"/></td>
      </tr>
    </table>
    <br/>
    <s:url id="URL" action="FormInt" method="input"/>
    <s:a href="%{URL}"><s:text name="Confirmation.lien"/></s:a>
  </body>
</html>

Aby zrozumieć ten kod, należy pamiętać, że widok jest wyświetlany po instancjonowaniu klasy [FormInt]. Pola tej klasy oraz jej modelu [FormIntModel] są zatem dostępne dla widoku.

  • wiersze 16–38: wyświetlane są wartości sześciu pól
  • wiersze 42–43: link do akcji [FormInt]. Kod wygenerowany dla tego linku jest następujący:

<a href="/exemple-09/example/FormInt!input.action">Formulaire de test</a>

Specyficzny identyfikator URL tego linku wskazuje, że żądanie powinno zostać przetworzone przez metodę input akcji [FormInt]. Przypomnijmy konfigurację akcji [FormInt] w [example.xml]:


    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
</action>

Metoda input klasy [FormInt] będzie metodą jej klasy nadrzędnej ActionSupport. Wykonanie metody input klasy [FormInt] następuje po wykonaniu interceptorów

Wiadomo, że wywołanie metody input jest ignorowane przez interceptor walidacji. Nie nastąpi zatem żadna walidacja.

Wyświetlany jest widok [FormInt.JSP]:

W widoku [2] pola wprowadzania danych odzyskują swoje wartości. Może się to wydawać normalne, ale tak nie jest. Po wywołaniu akcji [FormInt] utworzono instancję powiązanej klasy [FormInt]. Ponieważ klasa ta implementuje interfejs ModelDriven, wywołano jej metodę getModel:


  // model akcji
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}

Widać, że model akcji jest pobierany z sesji. W poprzednim kroku model ten został zaktualizowany o wartości przesłane w żądaniu POST. Zatem odzyskujemy te wartości. Gdybyśmy nie umieścili modelu w sesji, w widoku [FormInt.JSP] mielibyśmy sześć pustych pól.

11.5.5. Akcja [FormInt!clearModel]

Akcja [Formint!clearModel] jest uruchamiana przez kliknięcie linku [Raz modèle]:

  • w [1] – formularz po wprowadzeniu błędnych danych
  • w [2] – formularz po kliknięciu linku [Raz modèle].

Metoda [FormInt.clearModel] wygląda następująco:


  @SkipValidation
  public String clearModel() {
    // wyczyść model
    ((FormIntModel) getModel()).clearModel();
    // wynik
    return INPUT;
}
  • wiersz 1: nie ma potrzeby przeprowadzania walidacji. Aby to zaznaczyć, stosuje się notację @SkipValidation. Interceptor walidacji nie przeprowadzi wówczas żadnych sprawdzeń.
  • wiersz 4: wykonywana jest metoda [FormIntModel].clearModel. Spotkaliśmy się z nią już wcześniej. Resetuje ona sześć pól modelu do wartości null.
  • wiersz 7: metoda zwraca klucz input.

Wracając do konfiguracji akcji [FormInt]:


    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
</action>

widać, że klucz input spowoduje wyświetlenie widoku [FormInt.JSP]. Widok ten wyświetla sześć pól modelu. Ponieważ pola te znajdują się w null, widok wyświetla sześć pustych pól [2].

11.5.6. Akcja [FormInt!cancel]

Akcja [Formint!cancel] jest uruchamiana przez kliknięcie linku [Annuler]:

  • w [1] – formularz po wprowadzeniu błędnych danych
  • w [2] – strona główna po kliknięciu linku [Annuler].

Metoda [FormInt.cancel] wygląda następująco:


  public String cancel() {
    // czyścimy szablon
    ((FormIntModel) getModel()).clearModel();
    // wynik
    return "cancel";
}
  • wiersz 1: należy zauważyć, że metoda nie jest poprzedzona adnotacją SkipValidation. Nie chcemy jednak przeprowadzać walidacji. Metoda cancel należy do czterech metod: input, back, cancel, browse, które są ignorowane przez moduł przechwytujący walidację, dlatego też adnotacja SkipValidation nie jest konieczna.
  • wiersz 3: opróżnia szablon
  • wiersz 5: zwraca klucz cancel

Wracając do konfiguracji akcji [FormInt]:


    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
</action>

widać, że klucz cancel spowoduje wyświetlenie widoku [Accueil.JSP] po przekierowaniu klienta. Pokazuje to widok [2].

11.6. Proces walidacji

Przechodzimy teraz do walidacji sześciu pól wprowadzania danych powiązanych z następującymi sześcioma polami modelu:


  // pola formularza
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
private String int6;

Walidacja ta odbywa się za każdym razem, gdy instancja klasy [FormInt] jest tworzona, a wykonywana metoda nie jest pomijana przez interceptor walidacji. Jest ona sterowana przez:

  • plikiem [FormInt-validation.xml], o ile znajduje się on w tym samym folderze co klasa [FormInt]
  • metodą [FormInt.validate], o ile istnieje.
  • w [1]: plik [xwork-validator-1.0.2.dtd] niezbędny do procesu walidacji
  • na [2]: plik [FormInt-validation.xml] w tym samym folderze co klasa [FormInt]

Plik [FormInt-validation.xml] ma następującą treść:


<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/przykład-09/example/xwork-validator-1.0.2.dtd">

<validators>

  <field name="int1" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="int1.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{2}$</param>
      <param name="trim">true</param>
      <message key="int1.error"/>
    </field-validator>
  </field>

  <field name="int2" >
    ...
  </field>

...
</validators>
  • w [3], plik URL z pliku DTD (definicja typu dokumentu) pliku walidacyjnego. Plik ten musi być dostępny, w przeciwnym razie plik walidacyjny nie zostanie wykorzystany.
  • w [7], plik URL z pliku DTD używanego przez aplikację. Umieściliśmy plik DTD w folderze [example] projektu exemple-09 [1], aby mieć go pod ręką nawet w przypadku braku dostępu do Internetu.
  • Wiersze 11–20: określają warunki walidacji parametru int1 powiązanego z polem int1 w modelu.

Tag o nazwie int1 w formularzu wygląda następująco:


<s:textfield name="int1" key="int1.prompt" />

Pole int1 w szablonie jest zadeklarowane w następujący sposób:


private String int1;
  • wiersze 12–14: sprawdzają, czy parametr int1 istnieje (nie null) i ma długość niezerową. Jeśli tak nie jest, do pola wprowadzania danych przypisywany jest komunikat o błędzie. Jest on zdefiniowany w [FormInt.properties] w następujący sposób:

int1.error=Tapez un nombre entier positif de deux chiffres

W przypadku wystąpienia błędu proces walidacji parametru int1 zostaje przerwany (short-circuit=true).

  • wiersze 15–19: poprawność parametru int1 jest sprawdzana za pomocą wyrażenia regularnego.
  • wiersz 16: wyrażenie regularne, w tym przypadku dwie cyfry bez żadnych znaków przed i po nich.
  • wiersz 17: parametr int1 przed porównaniem z wyrażeniem regularnym zostanie pozbawiony spacji na początku i na końcu.
  • wiersz 18: ewentualny komunikat o błędzie. Jest taki sam jak w przypadku poprzedniego walidatora.

Zobaczmy, jak to działa:

  • w [1], błędne wprowadzenie danych w polu int1
  • w [2], strona zwracana:
    • pojawia się komunikat o błędzie klucza int1.error. Jest on zaznaczony na czerwono.
    • nazwa błędnego pola jest również zaznaczona na czerwono.
    • Błędna wartość jest ponownie wyświetlana. Należy to przewidzieć, ponieważ nie jest to koniecznie zachowanie domyślne.

Zauważyliśmy, że walidacja formularza powoduje wykonanie metody [FormInt].execute, jeśli żądanie przejdzie przez wszystkie interceptory, w szczególności ten odpowiedzialny za walidację:

  • jeśli żądanie dotrze do metody execute akcji, ta zwraca klucz success do kontrolera, jak już widzieliśmy...
  • jeśli interceptor walidacji zatrzyma żądanie, ponieważ sprawdzane parametry są nieprawidłowe, wówczas do kontrolera zwracany jest klucz input.

Ponieważ akcja [FormInt] jest skonfigurowana w następujący sposób:


    <action name="FormInt" class="example.FormInt">
      <result name="input">/example/FormInt.JSP</result>
      <result name="cancel" type="redirect">/example/Accueil.JSP</result>
      <result name="success">/example/ConfirmationFormInt.JSP</result>
</action>

w przypadku błędu walidacji wyświetlany jest widok [FormInt.JSP], a więc formularz. Znaczniki Struts są skonstruowane w taki sposób, że wyświetlają ewentualne komunikaty o błędach, które są do nich przypisane. Otrzymamy zatem widok [FormInt.JSP] z komunikatami o błędach przypisanymi do poszczególnych pól. Pokazuje to widok [2].

Przyjrzyjmy się teraz walidacji pola int2, zadeklarowanego w modelu w następujący sposób:


private Integer int2;

Walidacja pola int2 w [FormInt-validation.xml] wygląda następująco:


<field name="int2" >
    <field-validator type="required" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
  </field>
  • wiersze 2–4: sprawdzają, czy istnieje parametr int2.
  • wiersze 5–7: sprawdzają, czy konwersja typu String na Integer jest możliwa
  • wiersze 3 i 6: komunikat o błędzie klucza int2.error brzmi następująco:

int2.error=Tapez un nombre entier

Walidacja pól Integer i int3 w modelu w [FormInt-validation.xml] wygląda następująco:


<field name="int3" >
    <field-validator type="required" short-circuit="true">
      <message key="int3.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
    <field-validator type="int" short-circuit="true">
      <param name="min">-1</param>
      <message key="int3.error"/>
    </field-validator>
  </field>
  • wiersze 8–11: sprawdzają, czy pole int3 jest typu całkowitego >=-1
  • wiersze 3, 7: komunikat o błędzie klucza int3.error brzmi następująco:

int3.error=Tapez un nombre entier >=-1

Walidacja pól Integer i int4 szablonu w [FormInt-validation.xml] wygląda następująco:


<field name="int4" >
    <field-validator type="required" short-circuit="true">
      <message key="int4.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
    <field-validator type="int" short-circuit="true">
      <param name="max">10</param>
      <message key="int4.error"/>
    </field-validator>
  </field>
  • wiersze 8–11: sprawdzają, czy jest to liczba całkowita <=10
  • wiersze 3, 7: komunikat o błędzie klucza int4.error brzmi następująco:

int4.error=Tapez un nombre entier <=10

Walidacja pola Integer int5 w modelu w [FormInt-validation.xml] wygląda następująco:


<field name="int5" >
    <field-validator type="required" short-circuit="true">
      <message key="int5.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
    <field-validator type="int" short-circuit="true">
      <param name="min">1</param>
      <param name="max">10</param>
      <message key="int5.error"/>
    </field-validator>
  </field>
  • wiersze 5–9: sprawdzają, czy jest to liczba całkowita z przedziału [1, 10].
  • wiersze 3, 8: komunikat o błędzie klucza int5.error brzmi następująco:

int5.error=Tapez un nombre entier dans l''intervalle [1,10]

Walidacja pól String i int6 w szablonie [FormInt-validation.xml] wygląda następująco:


<field name="int6" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="int6.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{1,2}$</param>
      <param name="trim">true</param>
      <message key="int6.error"/>
    </field-validator>
  </field>
  • wiersze 5–9: sprawdzają, czy int6 jest ciągiem składającym się z 2 cyfr.
  • wiersz 3, 8: komunikat o błędzie klucza int6.error brzmi następująco:

int6.error=Tapez un nombre entier dans l''intervalle [2,20]

Poprzednia walidacja nie sprawdza, czy parametr int6 jest liczbą całkowitą z przedziału [2,20]. Sprawdzenie to odbywa się w metodzie [FormInt].validate, która jest wykonywana po przetworzeniu pliku [FormInt-validation.xml]. Metoda ta wygląda następująco:


  // sprawdzanie poprawności
  @Override
  public void validate() {
    // czy wartość typu int6 jest prawidłowa?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
}
  • wiersz 5: sprawdzane jest, czy występują błędy związane z polem int6. Jeśli tak, proces nie jest kontynuowany.
  • wiersz 6: jeśli nie wystąpił żaden błąd, pobieramy pole String int6 z modelu i przekształcamy je na liczbę całkowitą.
  • wiersz 7: sprawdzamy, czy pobrana liczba całkowita mieści się w przedziale [2,20].
  • wiersz 8: jeśli tak nie jest, do pola int6 dołączany jest komunikat o błędzie. Komunikat ten jest wyszukiwany w pliku komunikatów przy użyciu klucza int6.error.

Jeśli po zakończeniu tego procesu walidacji wystąpią błędy, wywołanie metody [FormInt].execute zostaje przerwane, a klucz input jest zwracany do kontrolera Struts.

11.7. Ostatnie szczegóły

Omówiliśmy kilka sposobów wprowadzania liczb całkowitych. Nie wszystkie z nich są równoważne. Rozważmy na przykład pola wprowadzania danych int5 i int6:

W widoku [FormInt.JSP] są one zadeklarowane w następujący sposób:


      <s:textfield name="int5" key="int5.prompt"/>
<s:textfield name="int6" key="int6.prompt"/>

Ich szablon jest zadeklarowany w polu [FormIntModel.java]:


  private Integer int5;
private String int6;

Pole int5 ma typ Integer, podczas gdy pole int6 ma typ String. Ich reguły walidacji są różne:


<field name="int5" >
    <field-validator type="required" short-circuit="true">
      <message key="int5.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="int2.error"/>
    </field-validator>
    <field-validator type="int" short-circuit="true">
      <param name="min">1</param>
      <param name="max">10</param>
      <message key="int5.error"/>
    </field-validator>
  </field>
  
  <field name="int6" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="int6.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{1,2}$</param>
      <param name="trim">true</param>
      <message key="int6.error"/>
    </field-validator>
  </field>

Walidacja pola int6 jest uzupełniana metodą validate w ramach akcji [FormInt]:


  public void validate() {
    // czy wartość int6 jest prawidłowa?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
}

Chociaż reguły walidacji są sformułowane w różny sposób, obie mają na celu sprawdzenie, czy wprowadzone pole jest liczbą całkowitą mieszczącą się w określonym przedziale. Zachowanie pól int5 i int6 podczas wykonywania jest jednak różne, co pokazują poniższe zrzuty ekranu:

  • w polu [1] – ten sam nieprawidłowy wpis w obu polach
  • w [2] – wyświetlona strona błędów. Oba pola wyświetlają różne komunikaty o błędach.
  • w [3] pojawia się dla pola int5 niepożądany komunikat, ponieważ jest on w języku angielskim. Wynika to z nieudanej konwersji typu String na Integer. Ponadto w logach serwera Apache występuje wyjątek:
Avertissement: Error setting expression 'int5' with value '[Ljava.lang.String;@1ad405d8'
ognl.MethodFailedException: Method "setInt5" failed for object example.FormIntModel@21b63266 [java.lang.NoSuchMethodException: example.FormIntModel.setInt5([Ljava.lang.String;)]

Co dość dziwne, Struts szukał metody FormIntModel.setInt5(String value), której nie znalazł.

Kluczem niepożądanego komunikatu jest xwork.default.invalid.fieldvalue. Aby przetłumaczyć go na język francuski, wystarczy przypisać do tego klucza tekst w języku francuskim. W tym celu dodajemy do pliku [messages.properties] następujący wiersz:


...
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

11.8. Conclusion

Kończymy analizę tej pierwszej aplikacji służącej do walidacji parametrów. Wyjaśnienie jej działania było dość skomplikowane. Teraz zajmiemy się podobnymi aplikacjami. Będziemy więc komentować tylko te zmiany, które się pojawiły.