Skip to content

11. Exemplo 09 - Conversão e validação de números inteiros

Vamos agora analisar uma série de exemplos sobre a conversão e validação de parâmetros de formulário. O problema é o seguinte. Para processar uma URL do tipo [http://machine:port/.../Action], o controlador [FilterDispatcher] instancia a classe que implementa a ação solicitada e executa um dos seus métodos — por predefinição, o método chamado execute. A chamada a este método execute passa por uma série de interceptores:

A lista de interceptores está definida no ficheiro [struts-default.xml] na raiz do arquivo [struts2-core.jar]. A lista de interceptores definida nesse ficheiro é a seguinte:


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

Entre os interceptores, há um que é responsável por injetar os valores dos parâmetros que acompanham a solicitação na ação, na forma parami=valores. Sabemos que os valores serão injetados no campo parami da ação através do método setParami, caso este exista. Caso contrário, não ocorre qualquer injeção e não é reportado qualquer erro.

A string parami=values é uma cadeia de caracteres. Até agora, a injeção de valores tem sido realizada em campos parami do tipo String:

private String parami ;

Injetar a string valeuri como valor da string parami não apresentou qualquer problema. Se parami não for do tipo String, então valeuri deve ser convertida para o tipo Ti de parami. Este é o problema da conversão. Por exemplo, podemos querer que a idade seja um inteiro e escrever na ação:

private int age ;

Além disso, podemos querer restringir a idade a um intervalo entre 1 e 150. Isto apresenta um problema de validação. O parâmetro parami pode ser convertido para o tipo correto sem necessariamente ser válido. Há, portanto, dois passos a completar. Voltando ao diagrama de processamento de pedidos:

Dois interceptores irão tratar da conversão e da validação dos parâmetros, respetivamente. Se qualquer uma das etapas falhar, a solicitação não prossegue para a ação (caminho vermelho acima). O formulário a partir do qual os parâmetros incorretos foram enviados é exibido novamente com mensagens de erro.

Os interceptores envolvidos na conversão e validação de parâmetros são os interceptores conversionError e validation nas linhas 19 e 20 da lista de interceptores apresentada anteriormente. Observe nas linhas 20–22 que o interceptor de validação não é aplicado se o método chamado for um dos métodos input, back, cancel ou browse. Utilizaremos esta propriedade mais adiante.

Começaremos por examinar a conversão e a validação de inteiros. Iremos dedicar algum tempo a este primeiro exemplo, uma vez que a validação envolve muitos elementos. Assim que compreendermos estes, avançaremos mais rapidamente pelos exemplos que se seguem.

11.1. O formulário

  • em [1], o formulário de entrada
  • em [2], o resultado ao validar sem introduzir quaisquer valores

11.2. O projeto NetBeans

O projeto NetBeans é o seguinte:

  • em [1], as três vistas da aplicação
  • em [2], o código-fonte, os ficheiros de mensagens internacionalizadas e os ficheiros de configuração do Struts.

11.3. Configuração do Struts

A aplicação é configurada pelos ficheiros [struts.xml] e [example.xml].

O ficheiro [struts.xml] é o seguinte:


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

As linhas 12–18 definem a ação [/example/Home] como a ação padrão quando o utilizador não especifica nenhuma.

O ficheiro [example.xml] é o seguinte:


<?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>
  • Linhas 8–10: A ação [Home] apresenta a vista [Home.jsp]
  • linha 11: a ação [FormInt] executa, por predefinição, o método execute da classe [example.FormInt]. Veremos que serão executados outros dois métodos: os métodos input e cancel. Estes métodos serão então especificados nos parâmetros da solicitação.
  • linha 12: a chave input exibirá a vista [FormInt.jsp] (linha 5). Esta vista é a vista do formulário.
  • linha 13: a chave cancel será devolvida por um método cancel associado ao link [Cancel]. A vista renderizada será então a vista [Home.jsp] após um redirecionamento (type=redirect).
  • Linha 14: A chave `success` é devolvida pelo método `execute` da ação [FormInt]. Se a solicitação chegar ao método `execute`, significa que passou com sucesso por todos os interceptores, particularmente aqueles que verificam a validade dos parâmetros. O método `execute` devolve então simplesmente a chave `success`, que irá apresentar a vista de confirmação [ConfirmationInt.jsp].

11.4. Ficheiros de mensagens

O ficheiro [messages.properties] é o seguinte:


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

Além deste ficheiro, as visualizações utilizam o seguinte ficheiro [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]

O ficheiro [FormInt.properties] só é utilizado quando a ação que gerou a vista é a ação [FormInt]. Esta é uma forma de dividir o ficheiro de mensagens caso este se torne demasiado grande. As mensagens para a ação Action estão localizadas no ficheiro [Action.properties].

11.5. Visualizações e Ações

Vamos agora apresentar as vistas e as ações da aplicação. Com base na configuração da aplicação:


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

Podemos ver que existem três vistas [Home.jsp, FormInt.jsp, ConfirmationFormInt.jsp] e duas ações [Home, FormInt].

11.5.1. Home.jsp

A vista [Home.jsp] é a seguinte:

Image

O seu código é o seguinte:


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

O link na linha 14 gera o seguinte código 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>

Trata-se, portanto, de uma ligação para a ação [FormInt] configurada da seguinte forma no ficheiro [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>

Ao clicar na ligação, a classe [example.FormInt] será instanciada e o seu método de entrada será executado. Uma vez que este não existe, será executado o método de entrada da classe pai ActionSupport. Este método não faz nada além de devolver a chave de entrada. Por conseguinte, a vista [/example/FormInt.jsp] será apresentada.

Além disso, o método de entrada é um dos métodos ignorados pelo interceptor de validação:


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

Portanto, não haverá validação de parâmetros. Isto é importante porque não existem parâmetros aqui, e veremos mais tarde que as regras de validação exigem a presença de seis parâmetros.

11.5.2. A ação [FormInt]

A ação [FormInt] está associada à seguinte classe [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 without parameters
  public FormInt() {
  }
 
  // action model
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
  }
 
  public String cancel() {
    // cleaning the model
    ((FormIntModel) getModel()).clearModel();
    // result
    return "cancel";
  }
 
  @SkipValidation
  public String clearModel() {
    // close to the model
    ((FormIntModel) getModel()).clearModel();
    // result
     return INPUT;
  }
 
  // SessionAware
  private Map<String, Object> session;
 
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // validation
  @Override
  public void validate() {
    // valid int6 input?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
  }
}

Iremos comentar este código conforme necessário. Por enquanto:

  • na linha 9, a classe [FormInt] implementa duas interfaces:
  • ModelDriven, que possui apenas um método, getModel na linha 16
  • SessionAware, que tem apenas um método, setSession na linha 41
  • linhas 16–21: implementação da interface ModelDriven. Recorde-se que esta interface permite que o modelo de uma vista seja delegado a uma classe externa, neste caso a seguinte classe [FormIntModel]:

package example;
 
public class FormIntModel {
 
  // constructor without parameters
  public FormIntModel() {
  }
 
  // form fields
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
  private String int6;
 
  // raz model
  public void clearModel(){
    int1=null;
    int2=null;
    int3=null;
    int4=null;
    int5=null;
    int6=null;
  }
 
  // getters and setters
   ...
}

O modelo [FormIntModel] tem seis campos correspondentes aos seis campos de entrada na vista [FormInt.jsp]. Estes seis campos receberão os valores enviados. Quatro deles são do tipo Integer. Por conseguinte, surgirá a questão da conversão de String para Integer para esses campos. O método clearModel permite-lhe reiniciar o modelo.

Voltemos ao método getModel da ação [FormInt]:


  // action model
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}
  • Linhas 3–5: O modelo é recuperado da sessão. Se não estiver presente, é criada uma instância do modelo e colocada na sessão.
  • Linha 6: Embora seja criada uma instância da ação a cada nova solicitação feita à ação, o seu modelo permanece na sessão.

Vemos que a classe não define um método de entrada, mas a classe pai possui um que retorna a chave de entrada. A execução deste método exibe a vista [FormInt.jsp], que apresentaremos agora.

11.5.3. A vista [FormInt.jsp]

A vista [FormInt.jsp] é a seguinte:

  • em [1], o formulário em branco
  • em [2], o formulário após a validação de parâmetros incorretos.

O seu código é o seguinte:


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

  • linhas 12–17: os seis campos de entrada correspondentes aos seis campos do modelo [FormIntModel] para a ação [FormInt]. Quando a vista é apresentada, os atributos value dos campos de entrada são utilizados para os valores apresentados por esses campos. Se o atributo value estiver em falta, então é utilizado o atributo name.
  • linha 12: o campo de entrada está associado (name) ao campo int1 da ação ou do seu modelo, caso a ação implemente a interface ModelDriven. É o que acontece aqui. O mesmo se aplica a todos os outros campos.
  • Linha 18: O botão [Submit] envia as entradas para a ação [FormInt] definida na linha 11. O seu método execute será executado.
  • Linhas 21–22: O link [Cancel] executa o método [FormInt.cancel].
  • Linhas 24–25: O link [Clear Model] executa o método [FormInt.clearModel].

11.5.4. A vista [ConfirmationFormInt.jsp]

É apresentada quando todas as entradas no formulário [FormInt.jsp] são válidas.

  • Em [1], são enviados valores válidos
  • Em [2], a página de confirmação

O código para a vista [ConfirmationInt.jsp] é o seguinte:


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

Para compreender este código, lembre-se de que a vista é apresentada após a instância da classe [FormInt]. Os campos desta classe e do seu modelo [FormIntModel] estão, portanto, acessíveis à vista.

  • linhas 16–38: os valores dos seis campos são apresentados
  • linhas 42-43: um link para a ação [FormInt]. O código gerado para este link é o seguinte:


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

O URL específico do link indica que o método de entrada da ação [FormInt] deve processar o pedido. Recorde a configuração da ação [FormInt] em [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>

O método de entrada da classe [FormInt] será o da sua classe pai, ActionSupport. O método de entrada da classe [FormInt] é executado após a execução dos interceptores

Sabemos que a chamada ao método de entrada é ignorada pelo interceptor de validação. Portanto, não haverá validação.

A vista [FormInt.jsp] é apresentada:

Em [2], os campos de entrada revertem para os seus valores predefinidos. Isto pode parecer normal, mas não é. Uma vez que a ação [FormInt] foi chamada, a classe [FormInt] associada foi instanciada. Como esta classe implementa a interface ModelDriven, o seu método getModel foi chamado:


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

Podemos ver que o modelo da ação é recuperado da sessão. No passo anterior, este modelo foi atualizado com os valores enviados. Por isso, recuperamos esses valores. Se não tivéssemos colocado o modelo na sessão, teríamos seis campos vazios na vista [FormInt.jsp].

11.5.5. A ação [FormInt!clearModel]

A ação [FormInt!clearModel] é acionada ao clicar no link [Limpar Modelo]:

  • em [1], o formulário após uma entrada incorreta
  • em [2], o formulário após clicar no link [Limpar modelo].

O método [FormInt.clearModel] é o seguinte:


  @SkipValidation
  public String clearModel() {
    // close to the model
    ((FormIntModel) getModel()).clearModel();
    // result
    return INPUT;
}

  • linha 1: não há validação a realizar. Utilizamos a anotação @SkipValidation para indicar isso. O interceptor de validação não realizará, portanto, nenhuma validação.
  • linha 4: o método [FormIntModel].clearModel é executado. Já o encontrámos anteriormente. Este método redefine os seis campos do modelo para null.
  • linha 7: o método devolve a chave de entrada.

Voltando à configuração da ação [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>

Podemos ver que a tecla de entrada exibirá a vista [FormInt.jsp]. Esta vista apresenta os seis campos do modelo. Uma vez que estes estão nulos, a vista exibe seis campos vazios [2].

11.5.6. A ação [FormInt!cancel]

A ação [FormInt!cancel] é acionada ao clicar no link [Cancelar]:

  • em [1], o formulário após uma entrada incorreta
  • em [2], a página inicial após clicar no link [Cancelar].

O método [FormInt.cancel] é o seguinte:


  public String cancel() {
    // cleaning the model
    ((FormIntModel) getModel()).clearModel();
    // result
    return "cancel";
}

  • linha 1: Note que o método não é precedido pela anotação SkipValidation. No entanto, não queremos realizar validações. O método cancel é um dos quatro métodos (input, back, cancel, browse) ignorados pelo interceptor de validação, pelo que a anotação SkipValidation não é necessária.
  • linha 3: isto limpa o modelo
  • linha 5: devolve a chave cancel

Voltando à configuração da ação [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>

Podemos ver que a chave "cancel" irá apresentar a vista [Home.jsp] após um redirecionamento do lado do cliente. Isto é mostrado na vista [2].

11.6. O processo de validação

Vamos agora abordar a validação dos seis campos de entrada associados aos seguintes seis campos no modelo:


  // champs du formulaire
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
private String int6;

Esta validação ocorre sempre que a classe [FormInt] é instanciada e o método executado não é ignorado pelo interceptor de validação. É controlada pelo:

  • o ficheiro [FormInt-validation.xml], se existir na mesma pasta que a classe [FormInt]
  • o método [FormInt.validate], se existir.

  • em [1]: o ficheiro [xwork-validator-1.0.2.dtd] necessário para o processo de validação
  • em [2]: o ficheiro [FormInt-validation.xml] na mesma pasta que a classe [FormInt]

O ficheiro [FormInt-validation.xml] é o seguinte:


<!--
<!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/exemple-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>
  • em [3], o URL da DTD (Definição de Tipo de Documento) para o ficheiro de validação. Este deve estar acessível; caso contrário, o ficheiro de validação não será utilizado.
  • em [7], o URL da DTD utilizada pela aplicação. Colocámos o ficheiro DTD na pasta [example] do projeto example-09 [1] para que esteja disponível mesmo que não haja acesso à Internet.
  • Linhas 11–20: Defina as condições de validação para o parâmetro int1 associado ao campo int1 no modelo.

A tag denominada int1 no formulário é a seguinte:


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

O campo int1 no modelo é declarado da seguinte forma:


private String int1;
  • Linhas 12–14: verifique se o parâmetro int1 existe (não é nulo) e tem um comprimento diferente de zero. Se não for esse o caso, é exibida uma mensagem de erro associada ao campo de entrada. Está definido em [FormInt.properties] da seguinte forma:

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

Se houver um erro, o processo de validação do parâmetro int1 é interrompido (short-circuit=true).

  • Linhas 15–19: A validade do parâmetro int1 é verificada utilizando uma expressão regular.
  • Linha 16: A expressão regular, neste caso dois dígitos sem nada antes ou depois.
  • Linha 17: Antes de ser comparado com a expressão regular, o parâmetro int1 terá os espaços à esquerda e à direita removidos.
  • Linha 18: A mensagem de erro, se houver. É a mesma que para o validador anterior.

Vamos ver como isto funciona:

  • em [1], uma entrada incorreta para o campo int1
  • em [2], a página apresentou:
  • A mensagem de erro para a chave int1.error é exibida. Está a vermelho.
  • O rótulo do campo incorreto também está a vermelho.
  • A entrada incorreta é exibida novamente. Isto deve ser antecipado, pois não é necessariamente o comportamento padrão.

Vimos que a validação do formulário aciona a execução do método [FormInt].execute se a solicitação conseguir passar por todos os interceptores, particularmente o interceptor de validação:

  • Se o pedido chegar ao método `execute` da ação, este devolve a chave `success` ao controlador, como já vimos.
  • Se o interceptor de validação interromper a solicitação porque os parâmetros testados são inválidos, então a chave `input` é devolvida ao controlador.

Uma vez que a ação [FormInt] está configurada da seguinte forma:


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

Quando ocorre um erro de validação, é apresentada a vista [FormInt.jsp], ou seja, o formulário. As tags Struts foram concebidas para apresentar quaisquer mensagens de erro associadas às mesmas. Veremos, portanto, a vista [FormInt.jsp] com mensagens de erro associadas aos vários campos. Isto é mostrado na vista [2].

Vamos agora examinar a validação do campo int2, declarado da seguinte forma no modelo:


private Integer int2;

A validação do campo int2 em [FormInt-validation.xml] é a seguinte:


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

  • Linhas 2–4: verifique se o parâmetro int2 existe.
  • linhas 5-7: verifique se a conversão de String para Inteiro é possível
  • Linhas 3, 6: A mensagem de erro para a chave int2.error é a seguinte:


int2.error=Tapez un nombre entier

A validação para o campo inteiro int3 no modelo em [FormInt-validation.xml] é a seguinte:


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

  • Linhas 8–11: verifique se o campo int3 é um inteiro >= -1
  • Linhas 3, 7: A mensagem de erro para a chave int3.error é a seguinte:

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

A validação do campo inteiro int4 no modelo em [FormInt-validation.xml] é a seguinte:


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

  • linhas 8-11: verifique se é um número inteiro <=10
  • Linhas 3, 7: A mensagem de erro para a chave int4.error é a seguinte:


int4.error=Tapez un nombre entier <=10

A validação para o campo inteiro int5 no modelo em [FormInt-validation.xml] é a seguinte:


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

  • Linhas 5–9: verifique se o valor é um número inteiro no intervalo [1, 10].
  • Linhas 3, 8: A mensagem de erro para a chave int5.error é a seguinte:


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

A validação para o campo String int6 no modelo em [FormInt-validation.xml] é a seguinte:


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

  • Linhas 5–9: verifique se int6 é uma cadeia de caracteres de 2 dígitos.
  • Linhas 3, 8: A mensagem de erro para a chave int6.error é a seguinte:


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

A validação anterior não verifica se o parâmetro int6 é um número inteiro no intervalo [2,20]. Esta verificação é realizada no método [FormInt].validate, que é executado após o ficheiro [FormInt-validation.xml] ter sido processado. Este método é o seguinte:


  // validation
  @Override
  public void validate() {
    // valid int6 input?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
}
  • Linha 5: Verificamos se existem erros associados ao campo int6. Se houver, paramos aqui.
  • linha 6: se não houver erros, recuperamos o campo String int6 do modelo e convertemo-lo num inteiro.
  • linha 7: Verificamos se o inteiro recuperado está no intervalo [2,20].
  • linha 8: se não for esse o caso, é anexada uma mensagem de erro ao campo int6. Esta mensagem de erro é recuperada do ficheiro de mensagens utilizando a chave int6.error.

Se forem encontrados erros no final deste processo de validação, a chamada ao método [FormInt].execute é interrompida e a chave de entrada é devolvida ao controlador Struts.

11.7. Detalhes finais

Vimos várias formas de introduzir números inteiros. Nem todas são equivalentes. Considere, por exemplo, os campos de introdução int5 e int6:

Na vista [FormInt.jsp], estão declarados da seguinte forma:


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

O seu modelo é declarado em [FormIntModel.java]:


  private Integer int5;
private String int6;

O campo int5 é do tipo Integer, enquanto o campo int6 é do tipo String. As suas regras de validação são diferentes:


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

A validação do campo int6 é realizada pelo método validate da ação [FormInt]:


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

Embora expressas de forma diferente, ambas as regras de validação têm como objetivo verificar se o valor introduzido é um número inteiro dentro de um intervalo especificado. No entanto, o comportamento dos campos int5 e int6 difere em tempo de execução, conforme mostrado nas seguintes capturas de ecrã:

  • em [1], a mesma entrada incorreta para ambos os campos
  • em [2], a página de erro apresentada. Os dois campos apresentam mensagens de erro diferentes.
  • Em [3], aparece uma mensagem indesejada para o campo int5 porque está em inglês. Isso resulta da conversão falhada de String para Inteiro. Há também uma exceção nos registos do Apache:
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;)]

Curiosamente, o Struts procurou um método FormIntModel.setInt5(String value), mas não o encontrou.

A chave para a mensagem de erro é xwork.default.invalid.fieldvalue. Para traduzi-la para francês, basta associar um texto em francês a esta chave. Por isso, adicionamos a seguinte linha ao ficheiro [messages.properties]:


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

11.8. Conclusão

Isto conclui o nosso estudo desta primeira aplicação dedicada à validação de parâmetros. Foi complexo de explicar. Vamos agora examinar aplicações semelhantes. Por isso, iremos apenas comentar o que mudou.