Skip to content

11. Voorbeeld 09 - Conversie en validatie van gehele getallen

We behandelen nu een reeks voorbeelden over de conversie en validatie van formulierparameters. Het probleem is als volgt. Om een URL van het type [http://machine:port/.../Action] te verwerken, maakt de controller [FilterDispatcher] een instantie aan van de klasse die de gevraagde actie implementeert en voert een van de methoden daarvan uit, standaard de methode met de naam execute. De aanroep van deze methode execute doorloopt een reeks interceptors:

De lijst met interceptors is gedefinieerd in het bestand [struts-default.xml] in de hoofdmap van het archief [struts2-core.jar]. De lijst met interceptors die daarin is gedefinieerd, is als volgt:


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

Een van de interceptors zorgt ervoor dat de waarden valeuri van de parameters parami, die bij het verzoek horen in de vorm parami=valeuri, in de actie worden ingevoegd. Het is bekend dat valeuri via de methode setParami in het veld parami van de actie wordt geïnjecteerd, indien deze bestaat. Zo niet, dan vindt er geen injectie plaats en wordt er geen fout gemeld.

De tekenreeks parami=valeuri is een tekenreeks. Tot nu toe is valeuri geïnjecteerd in velden parami van het type String:

private String parami ;

Het invoegen van de tekenreeks valeuri als waarde van de tekenreeks parami leverde geen problemen op. Als parami niet van het type String is, dan moet valeuri worden geconverteerd naar het type Ti vanuit parami. Dit is het probleem bij de conversie. Stel bijvoorbeeld dat we willen dat een leeftijd een geheel getal is, dan schrijven we in de actie:

private int age ;

Daarnaast kan men de leeftijd willen beperken tot tussen 1 en 150. Hier ontstaat een validatieprobleem. De parameter parami kan weliswaar naar het juiste type worden geconverteerd, maar is daarom nog niet per se geldig. Er moeten dus twee stappen worden doorlopen. Als we terugkeren naar het schema voor de verwerking van een verzoek:

Twee interceptors zorgen respectievelijk voor de conversie en de validatie van de parameters. Als een van de stappen mislukt, wordt de aanvraag niet doorgestuurd naar de actie (rode route hierboven). Het formulier van waaruit de foutieve parameters zijn verzonden, wordt opnieuw weergegeven met foutmeldingen.

De interceptors die betrokken zijn bij de conversie en validatie van de parameters zijn de interceptors conversionError en validation op de regels 19 en 20 van de eerder gepresenteerde lijst met interceptors. Opmerking: in de regels 20-22 wordt de interceptor validation niet toegepast als de aangeroepen methode een van de volgende methoden is: input, back, cancel, browse. We zullen deze eigenschap later gebruiken.

We beginnen met het bestuderen van de conversie en validatie van gehele getallen. We zullen wat tijd besteden aan dit eerste voorbeeld, omdat bij de validatie veel elementen een rol spelen. Zodra we deze onder de knie hebben, zullen we de volgende voorbeelden sneller kunnen doorlopen.

11.1. Het formulier

  • naar [1], het invoerformulier
  • naar [2], het resultaat wanneer je op ‘Valideren’ klikt zonder waarden in te voeren

11.2. Het NetBeans-project

Het NetBeans-project ziet er als volgt uit:

  • in [1], de drie weergaven van de applicatie
  • in [2]: de broncodes, de bestanden met geïnternationaliseerde berichten en de Struts-configuratiebestanden.

11.3. Configuratie van Struts

De applicatie wordt geconfigureerd via de bestanden [struts.xml] en [example.xml].

Het bestand [struts.xml] ziet er als volgt uit:


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

De regels 12-18 definiëren de actie [/example/Accueil] als standaardactie wanneer de gebruiker er geen opgeeft.

Het bestand [example.xml] ziet er als volgt uit:


<?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>
  • regels 8-10: de actie [Accueil] zorgt ervoor dat de weergave [Accueil.JSP] wordt weergegeven
  • regel 11: de actie [FormInt] voert standaard de methode execute van de klasse [example.FormInt] uit. We zullen zien dat er nog twee andere methoden worden uitgevoerd, namelijk de methoden input en cancel. Deze methoden worden vervolgens gespecificeerd in de parameters van de aanvraag.
  • regel 12: de sleutel input zorgt ervoor dat de weergave [FormInt.JSP] (regel 5) wordt weergegeven. Dit is de weergave van het formulier.
  • regel 13: de sleutel cancel wordt geretourneerd door een methode cancel die is gekoppeld aan de link [Annuler]. De weergegeven weergave is dan de weergave [Accueil.JSP] na een omleiding (type=redirect).
  • regel 14: de sleutel success wordt geretourneerd door de methode execute van de actie [FormInt]. Als het verzoek de methode execute bereikt, betekent dit dat het alle interceptors met succes heeft doorlopen, met name die welke de geldigheid van de parameters controleren. De methode execute geeft vervolgens alleen de sleutel success terug, die de bevestigingsweergave [ConfirmationInt.JSP] weergeeft.

11.4. De berichtbestanden

Het bestand [messages.properties] ziet er als volgt uit:


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

Naast dit bestand maken de weergaven gebruik van het volgende bestand [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]

Het bestand [FormInt.properties] wordt alleen gebruikt wanneer de actie die de weergave heeft gegenereerd, de actie [FormInt] is. Dit is een manier om het berichtenbestand op te splitsen als het te groot is. De berichten van de actie Action worden geïnternationaliseerd in het bestand [Action.properties].

11.5. De weergaven en acties

We presenteren nu de weergaven en acties van de applicatie. Volgens de configuratie van de applicatie:


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

zien we dat er drie weergaven [Accueil.JSP, FormInt.JSP, ConfirmationFormInt.JSP] en twee acties [Accueil, FormInt] zijn.

11.5.1. Accueil.JSP

De weergave [Accueil.JSP] ziet er als volgt uit:

 

De code ervan is als volgt:


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

De link op regel 14 genereert de volgende HTML-code:

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

Het is dus een link naar de actie [FormInt], die als volgt is geconfigureerd in [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>

Als je op de link klikt, wordt de klasse [example.FormInt] geïnstantieerd en wordt de methode input daarvan uitgevoerd. Aangezien deze niet bestaat, wordt de methode input van de bovenliggende klasse ActionSupport uitgevoerd. Deze doet niets anders dan de sleutel input retourneren. Daarom wordt de weergave [/example/FormInt.JSP] weergegeven.

Overigens is de methode input een van de methoden die door de validatie-interceptor worden genegeerd:


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

Er vindt dus geen validatie van parameters plaats. Dit is belangrijk omdat er hier geen parameters zijn en we later zullen zien dat de validatieregels het bestaan van zes parameters voorschrijven.

11.5.2. De actie [FormInt]

De actie [FormInt] is gekoppeld aan de volgende klasse [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 {

  // constructor zonder parameters
  public FormInt() {
  }

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

  public String cancel() {
    // het model opschonen
    ((FormIntModel) getModel()).clearModel();
    // resultaat
    return "cancel";
  }

  @SkipValidation
  public String clearModel() {
    // model op nul zetten
    ((FormIntModel) getModel()).clearModel();
    // resultaat
     return INPUT;
  }
  
  // SessionAware
  private Map<String, Object> session;

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

  // validatie
  @Override
  public void validate() {
    // int6-invoer geldig?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
  }
}

We zullen deze code gaandeweg toelichten waar nodig. Voorlopig geldt:

  • regel 9: de klasse [FormInt] implementeert twee interfaces:
    • ModelDriven, die slechts één methode heeft, en getModel op regel 16
    • SessionAware, die slechts één methode heeft, setSession op regel 41
  • regels 16-21: implementatie van de interface ModelDriven. Ter herinnering: met deze interface kan het model van een weergave worden ondergebracht in een externe klasse, in dit geval de volgende klasse [FormIntModel]:

package example;

public class FormIntModel {

  // constructor zonder parameters
  public FormIntModel() {
  }

  // formuliervelden
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
  private String int6;

  // sjabloon leeg
  public void clearModel(){
    int1=null;
    int2=null;
    int3=null;
    int4=null;
    int5=null;
    int6=null;
  }

  // getters en setters
   ...
}

Het model [FormIntModel] heeft zes velden die overeenkomen met de zes invoervelden van de weergave [FormInt.JSP]. Deze zes velden zullen de verzonden waarden ontvangen. Vier daarvan hebben het type Integer. Voor deze velden doet zich dus het probleem voor van de conversie van String naar Integer. Met de methode clearModel kan het model worden gereset.

Laten we teruggaan naar de methode getModel van de actie [FormInt]:


  // actiemodel
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}
  • regels 3-5: er wordt in de sessie naar het model gezocht. Als het daar niet is, wordt er een instantie van het model aangemaakt en in de sessie geplaatst.
  • regel 6: hoewel bij elk nieuw verzoek aan de actie een instantie van de actie wordt aangemaakt, blijft het bijbehorende model in de sessie staan.

We zien dat de klasse geen methode input definieert, maar dat de bovenliggende klasse er een heeft die de sleutel input retourneert. Het uitvoeren van deze methode leidt tot de weergave van de weergave [FormInt.JSP], die we nu presenteren.

11.5.3. De weergave [FormInt.JSP]

De weergave [FormInt.JSP] ziet er als volgt uit:

  • in [1], het lege formulier
  • in [2], het formulier na validatie van onjuiste parameters.

De code is als volgt:


<%@ 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>
  • regels 12-17: de zes invoervelden die overeenkomen met de zes velden van het sjabloon [FormIntModel] van de actie [FormInt]. Bij het weergeven van de weergave worden de attributen value van de invoervelden gebruikt voor de waarde die door deze velden wordt weergegeven. Als het attribuut value ontbreekt, wordt het attribuut name gebruikt.
  • regel 12: het invoerveld is gekoppeld (name) aan het veld int1 van de actie of het bijbehorende model, indien de actie de interface ModelDriven implementeert. Dat is hier het geval. Dit geldt ook voor alle andere velden.
  • regel 18: de knop [Valider] verzendt de invoer naar de actie [FormInt] die op regel 11 is gedefinieerd. De bijbehorende methode execute wordt uitgevoerd.
  • regels 21-22: de koppeling [Annuler] voert de methode [FormInt.cancel] uit.
  • regels 24-25: de koppeling [Raz modèle] voert de methode [FormInt.clearModel] uit.

11.5.4. De weergave [ConfirmationFormInt.JSP]

Deze wordt weergegeven wanneer alle invoergegevens in het formulier [FormInt.JSP] geldig zijn.

  • In [1] worden geldige waarden geboekt
  • in [2] wordt de bevestigingspagina weergegeven

De code van de weergave [ConfirmationInt.JSP] is als volgt:


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

Om deze code te begrijpen, moet men bedenken dat de weergave wordt getoond na het instantiëren van de klasse [FormInt]. De velden van deze klasse en van het bijbehorende model [FormIntModel] zijn dus toegankelijk voor de weergave.

  • regels 16-38: de waarden van de zes velden worden weergegeven
  • regels 42-43: een link naar de actie [FormInt]. De gegenereerde code voor deze link is als volgt:

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

Het specifieke URL van de link geeft aan dat de methode input van de actie [FormInt] het verzoek moet verwerken. Laten we nog eens kijken naar de configuratie van de actie [FormInt] in [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>

De methode input van de klasse [FormInt] is dezelfde als die van de bovenliggende klasse ActionSupport. De methode input van de klasse [FormInt] wordt uitgevoerd nadat de interceptors zijn uitgevoerd

We weten dat de aanroep van de methode input door de validatie-interceptor wordt genegeerd. Er vindt dus geen validatie plaats.

De weergave [FormInt.JSP] wordt weergegeven:

In [2] krijgen de invoervelden hun invoerwaarden terug. Dit lijkt misschien normaal, maar dat is het niet. Omdat de actie [FormInt] is aangeroepen, is de bijbehorende klasse [FormInt] geïnstantieerd. Omdat deze klasse de interface ModelDriven implementeert, is de methode getModel aangeroepen:


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

We zien dat het model van de actie uit de sessie wordt opgehaald. In de vorige stap was dit model bijgewerkt met de verzonden waarden. We vinden deze waarden dus terug. Als we het model niet in de sessie hadden opgeslagen, zouden we zes lege velden hebben gehad in de weergave [FormInt.JSP].

11.5.5. De actie [FormInt!clearModel]

De actie [Formint!clearModel] wordt geactiveerd door op de link [Raz modèle] te klikken:

  • naar [1], het formulier na een foutieve invoer
  • in [2], het formulier na een klik op de link [Raz modèle].

De methode [FormInt.clearModel] is als volgt:


  @SkipValidation
  public String clearModel() {
    // model resetten
    ((FormIntModel) getModel()).clearModel();
    // resultaat
    return INPUT;
}
  • regel 1: er hoeft geen validatie plaats te vinden. Dit wordt aangegeven met de notatie @SkipValidation. De validatie-interceptor voert dan geen validaties uit.
  • regel 4: de methode [FormIntModel].clearModel wordt uitgevoerd. Deze hebben we al eerder gezien. Deze methode reset de zes velden van het model naar null.
  • regel 7: de methode retourneert de sleutel input.

Als we teruggaan naar de configuratie van de actie [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>

zien we dat de sleutel input ervoor zorgt dat de weergave [FormInt.JSP] wordt weergegeven. Deze toont de zes velden van het model. Aangezien deze velden bij null horen, toont de weergave zes lege velden [2].

11.5.6. De actie [FormInt!cancel]

De actie [Formint!cancel] wordt geactiveerd door op de link [Annuler] te klikken:

  • in [1], het formulier na een foutieve invoer
  • in [2], de startpagina na een klik op de link [Annuler].

De methode [FormInt.cancel] is als volgt:


  public String cancel() {
    // het model opschonen
    ((FormIntModel) getModel()).clearModel();
    // resultaat
    return "cancel";
}
  • regel 1: merk op dat de methode niet wordt voorafgegaan door de aantekening SkipValidation. We willen echter geen validaties uitvoeren. De methode cancel maakt deel uit van de vier methoden input, back, cancel en browse die door de validatie-interceptor worden genegeerd, waardoor de annotatie SkipValidation ook niet nodig is.
  • regel 3: deze maakt het sjabloon leeg
  • regel 5: deze stelt de sleutel cancel in

Als we teruggaan naar de configuratie van de actie [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>

zien we dat de sleutel cancel ervoor zorgt dat de weergave [Accueil.JSP] wordt weergegeven na een omleiding van de client. Dit wordt weergegeven in de weergave [2].

11.6. Het validatieproces

We gaan nu in op de validatie van de zes invoervelden die gekoppeld zijn aan de volgende zes velden van het model:


  // formuliervelden
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
private String int6;

Deze validatie vindt plaats telkens wanneer de klasse [FormInt] wordt geïnstantieerd en de uitgevoerde methode niet wordt genegeerd door de validatie-interceptor. Ze wordt aangestuurd door:

  • het bestand [FormInt-validation.xml], indien dit in dezelfde map staat als de klasse [FormInt]
  • de methode [FormInt.validate], indien deze bestaat.
  • in [1]: het bestand [xwork-validator-1.0.2.dtd] dat nodig is voor het validatieproces
  • naar [2]: het bestand [FormInt-validation.xml] in dezelfde map als de klasse [FormInt]

Het bestand [FormInt-validation.xml] is als volgt:


<!--
<!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/voorbeeld-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>
  • in [3], het URL van het DTD (Document Type Definition) van het validatiebestand. Dit moet toegankelijk zijn, anders wordt het validatiebestand niet gebruikt.
  • in [7], de URL van de DTD die door de applicatie wordt gebruikt. We hebben het bestand DTD in de map [example] van het project exemple-09 [1] geplaatst, zodat we erover beschikken, zelfs als we geen toegang tot internet hebben.
  • regels 11-20: hierin worden de validatievoorwaarden vastgelegd voor de parameter int1 die gekoppeld is aan het veld int1 van het model.

De tag met de naam int1 in het formulier is als volgt:


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

Het veld int1 van het sjabloon wordt als volgt gedeclareerd:


private String int1;
  • regels 12-14: controleren of de parameter int1 bestaat (niet null) en een lengte heeft die niet nul is. Als dit niet het geval is, wordt er een foutmelding aan het invoerveld gekoppeld. Deze is in [FormInt.properties] als volgt gedefinieerd:

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

Als er een fout optreedt, wordt het validatieproces van de parameter int1 gestopt (short-circuit=true).

  • regels 15-19: de geldigheid van de parameter int1 wordt gecontroleerd met behulp van een reguliere expressie.
  • regel 16: de reguliere expressie, in dit geval 2 cijfers zonder iets ervoor of erachter.
  • regel 17: de parameter int1 wordt, voordat deze met de reguliere uitdrukking wordt vergeleken, ontdaan van de spaties aan het begin en het einde.
  • regel 18: de eventuele foutmelding. Deze is hetzelfde als bij de vorige validator.

Laten we eens kijken wat het resultaat is:

  • wordt omgezet in [1], een onjuiste invoer voor het veld int1
  • in [2], de teruggestuurde pagina:
    • de foutmelding voor sleutel int1.error is aanwezig. Deze is rood.
    • de tekst van het foutieve veld is ook rood.
    • De foutieve invoer wordt opnieuw weergegeven. Hier moet rekening mee worden gehouden, want dit is niet noodzakelijkerwijs het standaardgedrag.

We hebben gezien dat het valideren van het formulier ervoor zorgt dat de methode [FormInt].execute wordt uitgevoerd als het verzoek alle interceptors weet te passeren, met name die voor validatie:

  • als het verzoek de methode execute van de actie bereikt, stuurt deze de sleutel success terug naar de controller, zoals we hebben gezien.
  • als de validatie-interceptor het verzoek tegenhoudt omdat de geteste parameters ongeldig zijn, wordt de sleutel input teruggestuurd naar de controller.

Aangezien de actie [FormInt] als volgt is geconfigureerd:


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

wordt bij een validatiefout de weergave [FormInt.JSP] weergegeven, dus het formulier. De Struts-tags zijn zo ontworpen dat ze eventuele foutmeldingen weergeven die eraan zijn gekoppeld. We krijgen dus de weergave [FormInt.JSP] te zien met de foutmeldingen die aan de verschillende velden zijn gekoppeld. Dit is wat de weergave [2] laat zien.

Laten we nu eens kijken naar de validatie van het veld int2, dat als volgt in het model is gedefinieerd:


private Integer int2;

De validatie van het veld int2 in [FormInt-validation.xml] is als volgt:


<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>
  • regels 2-4: controleren of de parameter int2 bestaat.
  • regels 5-7: controleren of de conversie van String naar Integer mogelijk is
  • regels 3 en 6: de foutmelding van sleutel int2.error is als volgt:

int2.error=Tapez un nombre entier

De validatie van de velden Integer en int3 van het model in [FormInt-validation.xml] is als volgt:


<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>
  • regels 8-11: controleren of het veld int3 van het type geheel getal >=-1 is
  • regels 3 en 7: de foutmelding voor sleutel int3.error is als volgt:

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

De validatie van de velden Integer en int4 van het sjabloon in [FormInt-validation.xml] is als volgt:


<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>
  • regels 8-11: controleren of het een geheel getal <=10 is
  • regels 3 en 7: de foutmelding voor sleutel int4.error is als volgt:

int4.error=Tapez un nombre entier <=10

De validatie van het veld Integer int5 van het model in [FormInt-validation.xml] is als volgt:


<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>
  • regels 5-9: controleren of het een geheel getal is binnen het bereik [1, 10].
  • regel 3, 8: de foutmelding voor sleutel int5.error is als volgt:

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

De validatie van het veld String int6 van het model in [FormInt-validation.xml] is als volgt:


<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>
  • regels 5-9: controleren of int6 een tekenreeks van 2 cijfers is.
  • regel 3, 8: de foutmelding voor sleutel int6.error luidt als volgt:

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

De voorgaande validatie controleert niet of de parameter int6 een geheel getal is binnen het interval [2,20]. Deze controle wordt uitgevoerd in de methode [FormInt].validate, die wordt uitgevoerd nadat het bestand [FormInt-validation.xml] is verwerkt. Deze methode is als volgt:


  // validatie
  @Override
  public void validate() {
    // is de int6-invoer geldig?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
}
  • regel 5: er wordt gecontroleerd of er fouten zijn gekoppeld aan het veld int6. Zo ja, dan wordt de verwerking niet voortgezet.
  • regel 6: als er geen fout is opgetreden, wordt het veld String int6 uit het model opgehaald en omgezet in een geheel getal.
  • regel 7: er wordt gecontroleerd of het opgehaalde gehele getal binnen het bereik [2,20] valt.
  • regel 8: als dat niet het geval is, wordt er een foutmelding gekoppeld aan het veld int6. Deze foutmelding wordt opgezocht in het berichtenbestand met de sleutel int6.error.

Als er na afloop van dit validatieproces fouten zijn, wordt de aanroep van de methode [FormInt].execute afgebroken en wordt de sleutel input teruggegeven aan de Struts-controller.

11.7. Laatste details

We hebben verschillende manieren gezien om gehele getallen in te voeren. Deze zijn niet allemaal gelijkwaardig. Laten we bijvoorbeeld eens kijken naar de invoervelden int5 en int6:

In de weergave [FormInt.JSP] zijn ze als volgt gedefinieerd:


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

Hun sjabloon is gedefinieerd in [FormIntModel.java]:


  private Integer int5;
private String int6;

Het veld int5 is van het type Integer, terwijl het veld int6 van het type String is. Hun validatieregels zijn verschillend:


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

De validatie van het veld int6 wordt aangevuld met de methode validate van de actie [FormInt]:


  public void validate() {
    // is de int6-invoer geldig?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
}

Hoewel de validatieregels anders zijn geformuleerd, zijn ze beide bedoeld om te controleren of het ingevoerde veld een geheel getal is dat binnen een bepaald bereik valt. Het gedrag van de velden int5 en int6 verschilt echter bij de uitvoering, zoals te zien is in de volgende schermafbeeldingen:

  • in [1], dezelfde onjuiste invoer voor beide velden
  • in [2]: de weergegeven foutpagina. De twee velden hebben verschillende foutmeldingen.
  • in [3] verschijnt voor het veld int5 een ongewenste melding omdat deze in het Engels is. Deze is het gevolg van de mislukte conversie van String naar Integer. Daarnaast is er een uitzondering in de Apache-logs:
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;)]

Vreemd genoeg heeft Struts gezocht naar een methode FormIntModel.setInt5(String value) die het niet heeft gevonden.

De sleutel van de ongewenste melding is xwork.default.invalid.fieldvalue. Om deze in het Frans weer te geven, volstaat het om een Franse tekst aan deze sleutel te koppelen. We voegen dus aan het bestand [messages.properties] de volgende regel toe:


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

11.8. Conclusion

Hiermee is de bespreking van deze eerste toepassing voor het valideren van parameters afgerond. Het was een ingewikkeld onderwerp om uit te leggen. We gaan nu soortgelijke toepassingen bekijken. We zullen daarom alleen ingaan op wat er verandert.