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















