Skip to content

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

Abordamos agora uma série de exemplos sobre a conversão e a validação dos parâmetros de um formulário. O problema é o seguinte. Para processar um 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 denominado 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 aí definida é 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 se encarrega de inserir na ação os valores valeuri dos parâmetros parami que acompanham o pedido na forma parami=valeuri. Sabe-se que valeuri será injetado 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 é sinalizado qualquer erro.

A cadeia parami=valeuri é uma cadeia de caracteres. Até agora, a inserção de valeuri foi efetuada nos campos parami do tipo String:

private String parami ;

A injeção da cadeia valeuri como valor da cadeia parami não apresentou qualquer problema. Se parami não for do tipo String, então valeuri deve ser convertido do tipo parami para o tipo Ti. Este é o problema da conversão. Por exemplo, se quisermos que uma idade seja um número inteiro, escreveremos na ação:

private int age ;

Por outro lado, podemos querer limitar a idade entre 1 e 150. Temos aqui um problema de validação. O parâmetro parami pode ser convertido para o tipo correto sem, no entanto, ser válido. Há, portanto, duas etapas a seguir. Se voltarmos ao esquema de processamento de um pedido:

Dois interceptores irão encarregar-se, respetivamente, da conversão e da validação dos parâmetros. Se uma das etapas falhar, a solicitação não prossegue o seu percurso até à ação (circuito vermelho acima). O formulário a partir do qual foram enviados os parâmetros errados é exibido novamente com mensagens de erro.

Os interceptores responsáveis pela conversão e validação dos parâmetros são os interceptores conversionError e validation, nas linhas 19 e 20 da lista de interceptores apresentada anteriormente. Note-se, nas linhas 20-22, que o interceptor validation não é aplicado se o método chamado for um dos métodos input, back, cancel, browse. Iremos utilizar esta propriedade mais adiante.

Começamos por estudar a conversão e a validação de números inteiros. Vamos dedicar algum tempo a este primeiro exemplo, uma vez que a validação envolve vários elementos. Depois de os conhecermos, avançaremos mais rapidamente nos exemplos que se seguem.

11.1. O formulário

  • em [1], o formulário de introdução de dados
  • em [2], o resultado quando se valida sem introduzir valores

11.2. O projeto NetBeans

O projeto NetBeans é o seguinte:

  • em [1], as três vistas da aplicação
  • em [2], os códigos-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/Accueil] como ação por predefiniçã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 [Accueil] faz com que seja apresentada a vista [Accueil.JSP]
  • linha 11: a ação [FormInt] executa, por predefinição, o método execute da classe [example.FormInt]. Veremos que serão executados mais dois métodos: os métodos input e cancel. Estes métodos serão então especificados nos parâmetros da consulta.
  • linha 12: a chave input fará com que seja apresentada a vista [FormInt.JSP] (linha 5). Esta vista corresponde ao formulário.
  • linha 13: a chave cancel será devolvida por um método cancel associado ao link [Annuler]. A vista apresentada será, então, a vista [Accueil.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, nomeadamente aqueles que verificam a validade dos parâmetros. O método execute limita-se então a devolver a chave success, que irá apresentar a vista de confirmação [ConfirmationInt.JSP].

11.4. Os 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

Para além deste ficheiro, as vistas 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 segmentar o ficheiro de mensagens caso este seja demasiado grande. As mensagens da ação «Ação» são internacionalizadas no ficheiro [Action.properties].

11.5. As vistas e as ações

Apresentamos agora as vistas e as ações da aplicação. De acordo com a 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>

vemos que existem três vistas [Accueil.JSP, FormInt.JSP, ConfirmationFormInt.JSP] e duas ações [Accueil, FormInt].

11.5.1. Accueil.JSP

A vista [Accueil.JSP] é a seguinte:

 

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 da 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 um link para a ação [FormInt], configurada da seguinte forma 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>

Ao clicar no link, será instanciada a classe [example.FormInt] e executado o método input dessa classe. Como esta não existe, será executado o método input da classe pai ActionSupport. Este método não faz nada além de devolver a chave input. Assim, será apresentada a vista [/example/FormInt.JSP].

Por outro lado, o método input é um dos métodos ignorados pelo interceptor de validação:


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

Por isso, não haverá validação de parâmetros. Isto é importante porque, neste caso, não existem parâmetros e veremos mais adiante que as regras de validação irão exigir a existência 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 {

  // construtor sem parâmetros
  public FormInt() {
  }

  // modelo da ação
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
  }

  public String cancel() {
    // limpar o modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
    return "cancel";
  }

  @SkipValidation
  public String clearModel() {
    // reinicialização do modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
     return INPUT;
  }
  
  // SessionAware
  private Map<String, Object> session;

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

  // validação
  @Override
  public void validate() {
    // entrada int6 válida?
    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 à medida que for necessário. Por enquanto:

  • na linha 9, a classe [FormInt] implementa duas interfaces:
    • ModelDriven, que tem apenas um método, getModel da 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 transferir o modelo de uma vista para uma classe externa, neste caso a seguinte classe [FormIntModel]:

package example;

public class FormIntModel {

  // construtor sem parâmetros
  public FormIntModel() {
  }

  // campos do formulário
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
  private String int6;

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

  // getters e setters
   ...
}

O modelo [FormIntModel] tem seis campos que correspondem aos seis campos de introdução de dados da vista [FormInt.JSP]. São estes seis campos que irão receber os valores enviados. Quatro deles têm o tipo Integer. Para esses campos, coloca-se, portanto, o problema da conversão de String para Integer. O método clearModel permite reinicializar o modelo.

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


  // modelo da ação
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}
  • linhas 3-5: o modelo é procurado na 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 permanecerá na sessão.

Verificamos que a classe não define o método input, mas a classe pai possui um que devolve a chave input. A execução deste método leva à exibição da vista [FormInt.JSP], que apresentamos 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 errados.

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 introdução de dados correspondentes aos seis campos do modelo [FormIntModel] da ação [FormInt]. Ao apresentar a vista, são utilizados os atributos value dos campos de introdução de dados para determinar o valor apresentado por esses campos. Na ausência do atributo value, é 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. É esse o caso aqui. O mesmo se aplica a todos os outros campos.
  • linha 18: o botão [Valider] envia os dados introduzidos para a ação [FormInt] definida na linha 11. Será executado o seu método execute.
  • linhas 21-22: o link [Annuler] executa o método [FormInt.cancel].
  • linhas 24-25: o link [Raz modèle] executa o método [FormInt.clearModel].

11.5.4. A vista [ConfirmationFormInt.JSP]

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

  • no [1], são lançados valores válidos
  • na [2], a página de confirmação

O código da 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, é necessário ter em conta que a vista é apresentada após a instanciação da classe [FormInt]. Os campos desta classe e do seu modelo [FormIntModel] estão, portanto, acessíveis à vista.

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

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

O código específico URL do link indica que o método input da ação [FormInt] deve processar o pedido. Recorde-se 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 input da classe [FormInt] será o da sua classe pai ActionSupport. A execução do método input da classe [FormInt] ocorre após a execução dos interceptores

Sabe-se que a chamada ao método input é ignorada pelo interceptor de validação. Por conseguinte, não haverá validação.

É apresentada a vista [FormInt.JSP]:

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


  // modelo da ação
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}

Vemos que o modelo da ação é recuperado da sessão. Na etapa anterior, esse modelo tinha sido atualizado com os valores enviados. Assim, 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 na ligação [Raz modèle]:

  • em [1], o formulário após uma introdução de dados incorreta
  • em [2], o formulário após um clique no link [Raz modèle].

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


  @SkipValidation
  public String clearModel() {
    // reinicialização do modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
    return INPUT;
}
  • linha 1: não há validação a efetuar. Utiliza-se a notação @SkipValidation para o indicar. O interceptor de validação não efetuará, então, as validações.
  • linha 4: o método [FormIntModel].clearModel é executado. Já o encontrámos anteriormente. Este método reinicializa os seis campos do modelo para null.
  • linha 7: o método devolve a chave input.

Se voltarmos à 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>

vemos que a chave input irá apresentar a vista [FormInt.JSP]. Esta apresenta os seis campos do modelo. Como estes correspondem a null, a vista apresenta seis campos vazios [2].

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

A ação [Formint!cancel] é acionada ao clicar na ligação [Annuler]:

  • em [1], o formulário após uma introdução de dados incorreta
  • em [2], a página inicial após clicar na ligação [Annuler].

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


  public String cancel() {
    // limpa-se o modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
    return "cancel";
}
  • linha 1: note-se que o método não é precedido pela anotação SkipValidation. No entanto, não se pretende efetuar as validações. O método cancel faz parte dos quatro métodos input, back, cancel e browse, que são ignorados pelo interceptor de validação; por isso, a anotação SkipValidation também não é necessária.
  • linha 3: esvazia o modelo
  • linha 5: atribui a chave cancel

Se voltarmos à 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>

vemos que a chave cancel irá apresentar a vista [Accueil.JSP] após um redirecionamento do cliente. É isso que mostra a vista [2].

11.6. O processo de validação

Passamos agora à validação dos seis campos de introdução de dados associados aos seis campos seguintes do modelo:


  // campos do formulário
  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], caso exista na mesma pasta que a classe [FormInt]
  • pelo método [FormInt.validate], caso exista.
  • 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/exemplo-09/exemplo/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 do Tipo de Documento) do ficheiro de validação. Este deve estar acessível; caso contrário, o ficheiro de validação não é utilizado.
  • em [7], o URL do DTD utilizado pela aplicação. Colocámos o ficheiro DTD na pasta [example] do projeto exemple-09 [1] para o termos disponível mesmo que não haja acesso à Internet.
  • linhas 11-20: definem as condições de validação do parâmetro int1 associado ao campo int1 do modelo.

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


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

O campo int1 do modelo é declarado da seguinte forma:


private String int1;
  • linhas 12-14: verificam se o parâmetro int1 existe (não null) e se tem comprimento diferente de zero. Se não for esse o caso, é associada uma mensagem de erro ao campo de introdução de dados. 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 por uma expressão regular.
  • linha 16: a expressão regular, neste caso, 2 algarismos sem nada à frente nem atrás.
  • linha 17: o parâmetro int1, antes de ser comparado com a expressão regular, será limpo dos espaços iniciais e finais.
  • linha 18: a eventual mensagem de erro. É a mesma que para o validador anterior.

Vamos ver como fica:

  • em [1], uma entrada incorreta para o campo int1
  • em [2], a página apresentada:
    • a mensagem de erro da chave int1.error está presente. Está a vermelho.
    • o nome do campo com erro também está a vermelho.
    • A entrada incorreta é exibida novamente. É necessário ter isto em conta, pois não é necessariamente o comportamento por predefinição.

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

  • se a solicitação chegar ao método execute da ação, este devolve a chave success ao controlador, tal como vimos...
  • Se o interceptor de validação interromper a solicitação porque os parâmetros verificados não são vá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, é a vista [FormInt.JSP] que é apresentada, ou seja, o formulário. As tags Struts são concebidas de forma a exibirem as eventuais mensagens de erro que lhes estão associadas. Assim, teremos a vista [FormInt.JSP] com as mensagens de erro associadas aos diferentes campos. É isso que mostra a vista [2].

Analisemos agora 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: verificam se o parâmetro int2 existe.
  • linhas 5-7: verificam se a conversão de String para Integer é possível
  • linhas 3 e 6: a mensagem de erro da chave int2.error é a seguinte:

int2.error=Tapez un nombre entier

A validação dos campos Integer e int3 do 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: verificam se o campo int3 é do tipo inteiro >=-1
  • linhas 3 e 7: a mensagem de erro da chave int3.error é a seguinte:

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

A validação dos campos Integer e int4 do 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: verificam se é do tipo inteiro <=10
  • linhas 3 e 7: a mensagem de erro da chave int4.error é a seguinte:

int4.error=Tapez un nombre entier <=10

A validação do campo Integer int5 do 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: verificam se é do tipo inteiro no intervalo [1, 10].
  • linhas 3 e 8: a mensagem de erro da chave int5.error é a seguinte:

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

A validação do campo String int6 do 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: verificam se int6 é uma cadeia de 2 dígitos.
  • linha 3, 8: a mensagem de erro da 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 é efetuada no método [FormInt].validate, que é executado após o ficheiro [FormInt-validation.xml] ter sido processado. Este método é o seguinte:


  // validação
  @Override
  public void validate() {
    // entrada int6 válida?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
}
  • linha 5: verifica-se se existem erros associados ao campo int6. Se sim, o processo não prossegue.
  • linha 6: se não tiver havido erros, recupera-se o campo String int6 do modelo e transforma-se-o num inteiro.
  • linha 7: verifica-se se o inteiro recuperado está dentro do intervalo [2,20].
  • linha 8: se não for o caso, é anexada uma mensagem de erro ao campo int6. Esta mensagem de erro é procurada no ficheiro de mensagens com a chave int6.error.

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

11.7. Últimos detalhes

Vimos várias formas de introduzir números inteiros. Nem todas são equivalentes. Consideremos, 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 está 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 é complementada pelo método validate da ação [FormInt]:


  public void validate() {
    // entrada int6 válida?
    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 campo introduzido é um número inteiro compreendido num intervalo. No entanto, o comportamento dos campos int5 e int6 é diferente na execução, como mostram as seguintes capturas de ecrã:

  • em [1], a mesma entrada incorreta para ambos os campos
  • no [2], a página de erros apresentada. Os dois campos apresentam mensagens de erro diferentes.
  • em [3], aparece para o campo int5 uma mensagem indesejada, porque está em inglês. Esta mensagem resulta da conversão falhada de String para Integer. Além disso, existe 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) que não encontrou.

A chave da mensagem indesejada é xwork.default.invalid.fieldvalue. Para a traduzir para francês, basta associar um texto em francês a essa chave. Assim, adicionamos ao ficheiro [messages.properties] a seguinte linha:


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

11.8. Conclusion

Conclui-se assim o estudo desta primeira aplicação dedicada à validação de parâmetros. Foi complexo de explicar. Vamos agora estudar aplicações semelhantes. Por isso, apenas comentaremos o que muda.