Skip to content

11. Ejemplo 09: Conversión y validación de números enteros

Ahora abordamos una serie de ejemplos sobre la conversión y la validación de los parámetros de un formulario. El problema es el siguiente. Para procesar una URL del tipo [http://machine:port/.../Action], el controlador [FilterDispatcher] instancia la clase que implementa la acción solicitada y ejecuta uno de sus métodos, por defecto el método denominado execute. La llamada a este método execute pasa por una serie de interceptores:

La lista de interceptores se define en el archivo [struts-default.xml], situado en la raíz del archivo [struts2-core.jar]. La lista de interceptores definida en él es la siguiente:


             <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 los interceptores, hay uno que se encarga de inyectar en la acción los valores valeuri de los parámetros parami que acompañan a la solicitud en forma de parami=valeuri. Se sabe que valeuri se inyectará en el campo parami de la acción mediante el método setParami si este existe. De lo contrario, no se produce ninguna inyección y no se señala ningún error.

La cadena parami=valeuri es una cadena de caracteres. Hasta ahora, la inserción de valeuri se ha realizado en campos parami de tipo String:

private String parami ;

La inyección de la cadena valeuri como valor de la cadena parami no planteaba ningún problema. Si parami no es de tipo String, entonces valeuri debe someterse a una conversión al tipo Ti de parami. Este es el problema de la conversión. Por ejemplo, querremos que una edad sea un número entero y escribiremos en la acción:

private int age ;

Por otra parte, es posible que queramos limitar la edad entre 1 y 150. Aquí tenemos un problema de validación. El parámetro parami puede convertirse al tipo correcto sin que por ello sea válido. Por lo tanto, hay que pasar por dos etapas. Si volvemos al esquema del procesamiento de una solicitud:

Dos interceptores se encargarán, respectivamente, de la conversión y la validación de los parámetros. Si falla uno de los pasos, la solicitud no continúa su recorrido hacia la acción (circuito rojo arriba). El formulario desde el que se enviaron los parámetros erróneos se vuelve a mostrar con mensajes de error.

Los interceptores implicados en la conversión y la validación de los parámetros son los interceptores conversionError y validation de las líneas 19 y 20 de la lista de interceptores presentada anteriormente. Cabe señalar en las líneas 20-22 que el interceptor validation no se aplica si el método llamado es uno de los métodos input, back, cancel o browse. Utilizaremos esta propiedad más adelante.

Comenzamos por estudiar la conversión y la validación de números enteros. Dedicaremos tiempo a este primer ejemplo, ya que la validación implica numerosos elementos. Una vez que los conozcamos, avanzaremos más rápido en los ejemplos siguientes.

11.1. El formulario

  • en [1], el formulario de introducción de datos
  • en [2], el resultado al validar sin introducir valores

11.2. El proyecto NetBeans

El proyecto NetBeans es el siguiente:

  • en [1], las tres vistas de la aplicación
  • en [2], los códigos fuente, los archivos de mensajes internacionalizados y los archivos de configuración de Struts.

11.3. Configuración de Struts

La aplicación se configura mediante los archivos [struts.xml] y [example.xml].

El archivo [struts.xml] es el siguiente:


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

Las líneas 12-18 definen la acción [/example/Accueil] como acción por defecto cuando el usuario no especifica ninguna.

El archivo [example.xml] es el siguiente:


<?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>
  • líneas 8-10: la acción [Accueil] muestra la vista [Accueil.jsp]
  • línea 11: la acción [FormInt] ejecuta por defecto el método execute de la clase [example.FormInt]. Veremos que se ejecutarán otros dos métodos, los métodos input y cancel. Estos métodos se especificarán entonces en los parámetros de la consulta.
  • línea 12: la clave input mostrará la vista [FormInt.jsp] (línea 5). Esta vista es la del formulario.
  • línea 13: la clave cancel será devuelta por un método cancel asociado al enlace [Annuler]. La vista mostrada será entonces la vista [Accueil.jsp] tras una redirección (type=redirect).
  • Línea 14: la clave success es devuelta por el método execute de la acción [FormInt]. Si la solicitud llega hasta el método execute, es porque ha superado con éxito todos los interceptores, en particular los que verifican la validez de los parámetros. El método execute se limita entonces a devolver la clave success, que mostrará la vista de confirmación [ConfirmationInt.jsp].

11.4. Los archivos de mensajes

El archivo [messages.properties] es el siguiente:


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

Además de este archivo, las vistas utilizan el siguiente archivo [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]

El archivo [FormInt.properties] solo se utiliza cuando la acción que ha generado la vista es la acción [FormInt]. Es una forma de segmentar el archivo de mensajes si este es demasiado grande. Los mensajes de la acción se internacionalizan en el archivo [Action.properties].

11.5. Las vistas y las acciones

A continuación, presentamos las vistas y las acciones de la aplicación. Según la configuración de la aplicación:


<?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 hay tres vistas [Accueil.jsp, FormInt.jsp, ConfirmationFormInt.jsp] y dos acciones [Accueil, FormInt].

11.5.1. Accueil.jsp

La vista [Accueil.jsp] es la siguiente:

Image

Su código es el siguiente:


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

El enlace de la línea 14 genera el siguiente 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>

Por lo tanto, se trata de un enlace a la acción [FormInt], configurada de la siguiente manera en [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>

Al hacer clic en el enlace, se instanciará la clase [example.FormInt] y se ejecutará su método input. Como esta no existe, se ejecutará el método input de la clase padre ActionSupport. Este no hace nada más que devolver la clave input. Por lo tanto, se mostrará la vista [/example/FormInt.jsp].

Por otra parte, el método input es uno de los métodos ignorados por el interceptor de validación:


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

Por lo tanto, no habrá validación de parámetros. Esto es importante porque aquí no hay parámetros y veremos más adelante que las reglas de validación exigirán la existencia de seis parámetros.

11.5.2. La acción [FormInt]

La acción [FormInt] está asociada a la siguiente clase [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 sin parámetros
  public FormInt() {
  }

  // modelo de la acción
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
  }

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

  @SkipValidation
  public String clearModel() {
    // borrado del modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
     return INPUT;
  }
  
  // SessionAware
  private Map<String, Object> session;

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

  // validación
  @Override
  public void validate() {
    // ¿Es válida la entrada int6?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
  }
}

Comentaremos este código según sea necesario. Por ahora:

  • línea 9, la clase [FormInt] implementa dos interfaces:
  • ModelDriven, que solo tiene un método, getModel de la línea 16
  • SessionAware, que solo tiene un método, setSession de la línea 41
  • líneas 16-21: implementación de la interfaz ModelDriven. Recordemos que esta interfaz permite trasladar el modelo de una vista a una clase externa, en este caso la siguiente clase [FormIntModel]:

package example;

public class FormIntModel {

  // constructor sin parámetros
  public FormIntModel() {
  }

  // campos del formulario
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
  private String int6;

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

  // getters y setters
   ...
}

El modelo [FormIntModel] tiene seis campos que se corresponden con los seis campos de entrada de la vista [FormInt.jsp]. Son estos seis campos los que recibirán los valores enviados. Cuatro de ellos son de tipo Integer. Por lo tanto, para ellos se planteará el problema de la conversión String --> Integer. El método clearModel permite reiniciar el modelo.

Volvamos al método getModel de la acción [FormInt]:


  // modelo de la acción
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}
  • líneas 3-5: se busca el modelo en la sesión. Si no está allí, se crea una instancia del modelo y se coloca en la sesión.
  • línea 6: aunque se crea una instancia de la acción con cada nueva solicitud realizada a la acción, su modelo permanecerá en la sesión.

Vemos que la clase no define el método input, pero la clase padre tiene uno que devuelve la clave input. La ejecución de este método da lugar a la visualización de la vista [FormInt.jsp] que presentamos a continuación.

11.5.3. La vista [FormInt.jsp]

La vista [FormInt.jsp] es la siguiente:

  • en [1], el formulario en blanco
  • en [2], el formulario tras la validación de parámetros erróneos.

Su código es el siguiente:


<%@ 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>
  • líneas 12-17: los seis campos de entrada que corresponden a los seis campos del modelo [FormIntModel] de la acción [FormInt]. Al visualizar la vista, se utilizan los atributos value de los campos de entrada para el valor que muestran dichos campos. En ausencia del atributo value, se utiliza el atributo name.
  • línea 12: el campo de entrada se asocia (name) al campo int1 de la acción o de su modelo si la acción implementa la interfaz ModelDriven. Este es el caso aquí. Lo mismo ocurre con todos los demás campos.
  • línea 18: el botón [Valider] envía las entradas a la acción [FormInt] definida en la línea 11. Se ejecutará su método execute.
  • Líneas 21-22: el enlace [Annuler] ejecuta el método [FormInt.cancel].
  • líneas 24-25: el enlace [Raz modèle] ejecuta el método [FormInt.clearModel].

11.5.4. La vista [ConfirmationFormInt.jsp]

Se muestra cuando todas las entradas del formulario [FormInt.jsp] son válidas.

  • En [1], se contabilizan los valores válidos
  • en [2], la página de confirmación

El código de la vista [ConfirmationInt.jsp] es el siguiente:


<%@ 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 entender este código, hay que recordar que la vista se muestra tras la instanciación de la clase [FormInt]. Por lo tanto, los campos de esta y de su modelo [FormIntModel] son accesibles desde la vista.

  • líneas 16-38: se muestran los valores de los seis campos
  • líneas 42-43: un enlace a la acción [FormInt]. El código generado para este enlace es el siguiente:

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

La URL específica del enlace indica que el método input de la acción [FormInt] debe procesar la solicitud. Recordemos la configuración de la acción [FormInt] en [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>

El método input de la clase [FormInt] será el de su clase padre ActionSupport. La ejecución del método input de la clase [FormInt] se realiza tras la ejecución de los interceptores

Sabemos que la llamada al método input es ignorada por el interceptor de validación. Por lo tanto, no habrá validación.

Se muestra la vista [FormInt.jsp]:

En [2], los campos de entrada recuperan sus valores de entrada. Esto puede parecer normal, pero no lo es. Al haberse llamado a la acción [FormInt], se ha instanciado la clase asociada [FormInt]. Dado que esta clase implementa la interfaz ModelDriven, se ha llamado a su método getModel:


  // modelo de la acción
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormIntModel());
    }
    return session.get("model");
}

Se observa que el modelo de la acción se recupera de la sesión. En el paso anterior, este modelo se había actualizado con los valores enviados. Por lo tanto, se recuperan estos valores. Si no se hubiera incluido el modelo en la sesión, se habrían tenido seis campos vacíos en la vista [FormInt.jsp].

11.5.5. La acción [FormInt!clearModel]

La acción [Formint!clearModel] se activa al hacer clic en el enlace [Raz modèle]:

  • en [1], el formulario tras una entrada errónea
  • en [2], el formulario tras hacer clic en el enlace [Raz modèle].

El método [FormInt.clearModel] es el siguiente:


  @SkipValidation
  public String clearModel() {
    // borrado del modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
    return INPUT;
}
  • línea 1: no hay que realizar ninguna validación. Se utiliza la notación @SkipValidation para indicarlo. El interceptor de validación no realizará entonces las validaciones.
  • línea 4: se ejecuta el método [FormIntModel].clearModel. Ya lo hemos visto antes. Reinicia a null los seis campos del modelo.
  • línea 7: el método devuelve la clave input.

Si volvemos a la configuración de la acción [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 la clave input mostrará la vista [FormInt.jsp]. Esta muestra los seis campos del modelo. Al estar estos en null, la vista muestra seis campos vacíos [2].

11.5.6. La acción [FormInt!cancel]

La acción [Formint!cancel] se activa al hacer clic en el enlace [Annuler]:

  • en [1], el formulario tras una entrada errónea
  • en [2], la página de inicio tras hacer clic en el enlace [Annuler].

El método [FormInt.cancel] es el siguiente:


  public String cancel() {
    // limpiamos el modelo
    ((FormIntModel) getModel()).clearModel();
    // resultado
    return "cancel";
}
  • línea 1: cabe señalar que el método no va precedido de la anotación SkipValidation. Sin embargo, no queremos realizar las validaciones. El método cancel forma parte de los cuatro métodos input, back, cancel y browse que el interceptor de validación ignora, por lo que la anotación SkipValidation tampoco es necesaria.
  • línea 3: vacía la plantilla
  • línea 5: devuelve la clave cancel

Si volvemos a la configuración de la acción [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 la clave cancel mostrará la vista [Accueil.jsp] tras una redirección del cliente. Esto es lo que muestra la vista [2].

11.6. El proceso de validación

Ahora abordamos la validación de los seis campos de entrada asociados a los seis campos siguientes del modelo:


  // campos del formulario
  private String int1;
  private Integer int2;
  private Integer int3;
  private Integer int4;
  private Integer int5;
private String int6;

Esta validación tiene lugar cada vez que se instancia la clase [FormInt] y el método ejecutado no es ignorado por el interceptor de validación. Está controlada por:

  • el archivo [FormInt-validation.xml], si existe en la misma carpeta que la clase [FormInt]
  • el método [FormInt.validate], si existe.
  • en [1]: el archivo [xwork-validator-1.0.2.dtd] necesario para el proceso de validación
  • en [2]: el archivo [FormInt-validation.xml] en la misma carpeta que la clase [FormInt]

El archivo [FormInt-validation.xml] es el siguiente:


<!--
<!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/ejemplo-09/ejemplo/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>
  • en [3], la URL de DTD (Document Type Definition) del archivo de validación. Esta debe ser accesible; de lo contrario, el archivo de validación no se utilizará.
  • en [7], la URL de DTD utilizada por la aplicación. Hemos colocado el archivo DTD en la carpeta [example] del proyecto exemple-09 [1] para disponer de él aunque no se tenga acceso a Internet.
  • Líneas 11-20: establecen las condiciones de validación del parámetro int1 asociado al campo int1 del modelo.

La etiqueta denominada int1 en el formulario es la siguiente:


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

El campo int1 del modelo se declara de la siguiente manera:


private String int1;
  • líneas 12-14: comprueban que el parámetro int1 existe (no null) y que su longitud no es nula. Si no es así, se asocia un mensaje de error al campo de entrada. Se define en [FormInt.properties] de la siguiente manera:

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

Si hay un error, el proceso de validación del parámetro int1 se detiene (short-circuit=true).

  • líneas 15-19: la validez del parámetro int1 se comprueba mediante una expresión regular.
  • línea 16: la expresión regular, en este caso 2 dígitos sin nada delante ni detrás.
  • línea 17: antes de compararse con la expresión regular, al parámetro int1 se le eliminarán los espacios iniciales y finales.
  • línea 18: el posible mensaje de error. Es el mismo que para el validador anterior.

Veamos qué resultado da:

  • en [1], una entrada errónea para el campo int1
  • en [2], la página devuelta:
  • aparece el mensaje de error de la clave int1.error. Está en rojo.
  • El texto del campo erróneo también aparece en rojo.
  • Se vuelve a mostrar la entrada errónea. Hay que tenerlo en cuenta, ya que no es necesariamente el comportamiento por defecto.

Hemos visto que la validación del formulario provoca la ejecución del método [FormInt].execute si la solicitud logra pasar todos los interceptores, en particular el de validación:

  • si la solicitud llega hasta el método execute de la acción, este devuelve la clave success al controlador, tal y como hemos visto.
  • Si el interceptor de validación detiene la solicitud porque los parámetros comprobados no son válidos, se devuelve la clave input al controlador.

Dado que la acción [FormInt] está configurada de la siguiente manera:


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

cuando se produce un error de validación, se muestra la vista [FormInt.jsp], es decir, el formulario. Las etiquetas Struts están diseñadas de tal manera que muestran los posibles mensajes de error que se les asocian. Por lo tanto, tendremos la vista [FormInt.jsp] con los mensajes de error asociados a los distintos campos. Esto es lo que muestra la vista [2].

Examinemos ahora la validación del campo int2, declarado de la siguiente manera en el modelo:


private Integer int2;

La validación del campo int2 en [FormInt-validation.xml] es la siguiente:


<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>
  • líneas 2-4: comprueban que el parámetro int2 existe.
  • líneas 5-7: comprueban que la conversión de cadena a entero es posible
  • líneas 3 y 6: el mensaje de error de la clave int2.error es el siguiente:

int2.error=Tapez un nombre entier

La validación del campo Integer int3 del modelo en [FormInt-validation.xml] es la siguiente:


<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>
  • líneas 8-11: comprueban que el campo int3 es de tipo entero >=-1
  • líneas 3 y 7: el mensaje de error de la clave int3.error es el siguiente:

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

La validación del campo Integer int4 del modelo en [FormInt-validation.xml] es la siguiente:


<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>
  • líneas 8-11: comprueban que sea de tipo entero <=10
  • líneas 3 y 7: el mensaje de error de la clave int4.error es el siguiente:

int4.error=Tapez un nombre entier <=10

La validación del campo Integer int5 del modelo en [FormInt-validation.xml] es la siguiente:


<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>
  • líneas 5-9: comprueban que sea de tipo entero en el intervalo [1, 10].
  • Líneas 3 y 8: el mensaje de error de la clave int5.error es el siguiente:

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

La validación del campo String int6 del modelo en [FormInt-validation.xml] es la siguiente:


<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>
  • líneas 5-9: comprueban que int6 es una cadena de 2 dígitos.
  • Líneas 3 y 8: el mensaje de error de la clave int6.error es el siguiente:

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

La validación anterior no comprueba que el parámetro int6 sea un entero dentro del intervalo [2,20]. Esta comprobación se realiza en el método [FormInt].validate, que se ejecuta después de que se haya procesado el archivo [FormInt-validation.xml]. Este método es el siguiente:


  // validación
  @Override
  public void validate() {
    // ¿Es válida la entrada int6?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
    }
}
  • línea 5: se comprueba si hay errores asociados al campo int6. Si los hay, no se continúa.
  • línea 6: si no ha habido ningún error, se recupera el campo String int6 del modelo y se transforma en un entero.
  • línea 7: se comprueba que el entero recuperado esté dentro del intervalo [2,20].
  • línea 8: si no es así, se adjunta un mensaje de error al campo int6. Este mensaje de error se busca en el archivo de mensajes con la clave int6.error.

Si al final de este proceso de validación hay errores, se interrumpe la llamada al método [FormInt].execute y se devuelve la clave input al controlador Struts.

11.7. Últimos detalles

Hemos visto varias formas de introducir números enteros. No todas son equivalentes. Consideremos, por ejemplo, los campos de entrada int5 y int6:

En la vista [FormInt.jsp], se declaran de la siguiente manera:


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

Su modelo se declara en [FormIntModel.java]:


  private Integer int5;
private String int6;

El campo int5 es de tipo Integer, mientras que el campo int6 es de tipo String. Sus reglas de validación son 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>

La validación del campo int6 se completa mediante el método validate de la acción [FormInt]:


  public void validate() {
    // ¿Es válida la entrada int6?
    if (getFieldErrors().get("int6") == null) {
      int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
      if (int6 < 2 || int6 > 20) {
        addFieldError("int6", getText("int6.error"));
      }
}

Aunque las reglas de validación se expresan de forma diferente, ambas tienen como objetivo verificar que el campo introducido sea un número entero comprendido en un intervalo. Sin embargo, el comportamiento de los campos int5 y int6 es diferente en la ejecución, como muestran las siguientes capturas de pantalla:

  • en [1], la misma entrada incorrecta para ambos campos
  • en [2], la página de errores devuelta. Los dos campos tienen mensajes de error diferentes.
  • en [3] aparece para el campo int5 un mensaje no deseado porque está en inglés. Proviene de la conversión fallida de String a Integer. Además, hay una excepción en los registros de 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, Struts ha buscado un método FormIntModel.setInt5(String value) que no ha encontrado.

La clave del mensaje no deseado es xwork.default.invalid.fieldvalue. Para traducirlo al francés, basta con asociar un texto en francés a esta clave. Así, añadimos al archivo [messages.properties] la siguiente línea:


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

11.8. Conclusion

Con esto concluye el estudio de esta primera aplicación dedicada a la validación de parámetros. Ha sido complejo de explicar. Ahora vamos a estudiar aplicaciones similares. Por lo tanto, solo comentaremos lo que cambia.