Skip to content

14. Exemplo 11 – Conversão e validação de datas

A nova aplicação inclui a introdução de datas:

  • em [1], o formulário de entrada
  • em [2], a resposta devolvida

A aplicação funciona de forma semelhante à entrada de números inteiros, pelo que apenas comentaremos os pontos que diferem.

14.1. O projeto NetBeans

O projeto NetBeans é o seguinte:

  • em [1], as vistas da aplicação
  • [Home.jsp]: a página inicial
  • [FormDate.jsp]: o formulário de entrada
  • [ConfirmationFormDate.jsp]: a página de confirmação
  • em [2], o ficheiro de mensagens [messages.properties] e o ficheiro de configuração principal do Struts
  • em [3]:
  • [FormDate.java]: a ação que apresenta e processa o formulário
  • [FormDate-validation.xml]: as regras de validação para a ação [FormDate]. Este ficheiro delega estas validações ao modelo.
  • [FormDateModel]: o modelo para a ação [FormDate]
  • [FormDateModel-validation.xml]: as regras de validação para o modelo
  • [FormDateModel.properties]: o ficheiro de mensagens do modelo
  • [example.xml]: ficheiro de configuração secundária do Struts

14.2. Configuração do projeto

O projeto é configurado principalmente pelo seguinte ficheiro [example.xml]:


<?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="FormDate" class="example.FormDate">
      <result name="input">/example/FormDate.jsp</result>
      <result name="cancel" type="redirect">/example/Accueil.jsp</result>
      <result name="success">/example/ConfirmationFormDate.jsp</result>
    </action>
  </package>
</struts>

É semelhante ao discutido para a introdução de números inteiros.

14.3. Ficheiros de mensagens

O ficheiro [messages.properties] é o seguinte:


Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormDate=Saisie de dates
Form.titre=Conversions et validations
FormDate.message=Struts 2 - Conversion et validation de dates
FormDate.conseil=Tapez les dates au format JJ/MM/AAAA comme dans 12/10/2008
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
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

O ficheiro [FormDateModel.properties] é o seguinte:


date.format={0,date,dd/MM/yyyy}
date1.prompt=1-Tapez une date au format JJ/MM/AAAA
date1.error=Date erron\u00E9e
date2.prompt=2-Tapez une date au format JJ/MM/AAAA
date2.error=Date erron\u00E9e
date3.prompt=3-Tapez une date >=18/05/2000
date3.error=Date erron\u00E9e
date4.prompt=4-Tapez une date <=20/06/2001
date4.error=Date erron\u00E9e
date5.prompt=5-Tapez une date dans l''intervalle [18/05/2000,20/06/2001]
date5.error=Date errron\u00E9e
date6.prompt=6-Tapez une date dans l''intervalle [18/05/2000,20/06/2001]
date6.error=Date errron\u00E9e

A linha 1 desempenha um papel importante. Voltaremos a este assunto.

14.4. O formulário de entrada

A vista [FormDate.jsp] é a 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="FormDate.message"/></h2>
    <h4><s:text name="FormDate.conseil"/></h4>
    <s:form name="formulaire" action="FormDate">
      <s:textfield name="date1" key="date1.prompt"/>
      <s:textfield name="date2" key="date2.prompt" value="%{#parameters['date2']!=null ? #parameters['date2'] : date2==null ? '' :getText('date.format',{date2})}"/>
      <s:textfield name="date3" key="date3.prompt" value="%{#parameters['date3']!=null ? #parameters['date3'] : date3==null ? '' :getText('date.format',{date3})}"/>
      <s:textfield name="date4" key="date4.prompt" value="%{#parameters['date4']!=null ? #parameters['date4'] : date4==null ? '' :getText('date.format',{date4})}"/>
      <s:textfield name="date5" key="date5.prompt" value="%{#parameters['date5']!=null ? #parameters['date5'] : date5==null ? '' :getText('date.format',{date5})}"/>
      <s:textfield name="date6" key="date6.prompt"/>
      <s:submit key="Form.submitText" method="execute"/>
    </s:form>
    <br/>
    <s:url id="url" action="FormDate" method="cancel"/>
    <s:a href="%{url}"><s:text name="Form.cancelText"/></s:a>
      <br/>
    <s:url id="url" action="FormDate" method="clearModel"/>
    <s:a href="%{url}"><s:text name="Form.clearModel"/></s:a>
  </body>
</html>

As linhas 13 a 18 correspondem aos seis campos de introdução de data. Os campos date2 a date5 têm um atributo de valor complexo, tal como acontece com a introdução de números reais. Uma vez que se depararam com as mesmas dificuldades, estas foram resolvidas da mesma forma.

14.5. A vista de confirmação

A vista de confirmação [ConfirmationFormDate.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="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="date1.prompt"/></td>
        <td><s:text name="date1"/></td>
      </tr>
      <tr>
        <td><s:text name="date2.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date2"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date3.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date3"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date4.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date4"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date5.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date5"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date6.prompt"/></td>
        <td><s:text name="date6"/></td>
      </tr>
    </table>
    <br/>
    <s:url id="url" action="FormDate!input"/>
    <s:a href="%{url}"><s:text name="Confirmation.lien"/></s:a>
  </body>
</html>

A vista [ConfirmationFormDate.jsp] simplesmente apresenta o [FormDateModel]. As linhas 47–49 mostram como uma data é apresentada. Pretendemos que esta apresentação siga um formato de data específico. Para tal, utilizamos a tag <s:text>, que já utilizámos anteriormente para a internacionalização da aplicação.

Aqui, a chave de mensagem utilizada é *date.format*. Esta chave encontra-se no ficheiro FormDateModel.properties:


date.format={0,date,dd/MM/yyyy}

O valor associado à chave não é uma mensagem, mas sim um formato de exibição:

  • 0 é um parâmetro que representa o valor a ser exibido. Aqui, será o campo date5 do modelo.
  • date representa uma data. Anteriormente, utilizávamos number para um número.
  • dd/MM/yyyy representa o formato da data. Aqui, queremos que seja na forma dd/mm/yyyy. O formato Java apropriado é dd/MM/yyyy (d: dia, M: mês, y: ano)

A tag


<s:text name="date.format">
            <s:param value="date5"/>
</s:text>

exibe a mensagem {0, data, dd/MM/aaaa}, em que o parâmetro date5 (linha 2) substitui o parâmetro 0 no formato. Assim, o modelo date5 será exibido no formato dd/mm/aaaa.

14.6. O modelo [FormDateModel]

Os campos date1 a date6 do formulário [FormDate.jsp] são inseridos no seguinte modelo [FormDateModel]:


package example;
 
import java.util.Date;
 
public class FormDateModel {
 
  // constructor without parameters
  public FormDateModel() {
  }
  // fields
  private String date1;
  private Date date2 = new Date();
  private Date date3 = new Date();
  private Date date4;
  private Date date5;
  private String date6;
 
  // raz model
  public void clearModel() {
    date1 = null;
    date2 = null;
    date3 = null;
    date4 = null;
    date5 = null;
    date6 = null;
  }
 
  // getters and setters
...
}
  • Os campos date1 e date6 são do tipo String
  • os outros campos são do tipo Date

14.7. Validação do modelo

A validação do modelo é controlada por dois ficheiros: [FormDate-validation.xml] e [FormDateModel-validation.xml].

O ficheiro [FormDate-validation.xml] delega as validações ao ficheiro [FormDateModel-validation.xml]:


<!--
<!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-10/example/xwork-validator-1.0.2.dtd">
 
<validators>
  <field name="model" >
    <field-validator type="visitor">
      <param name="appendPrefix">false</param>
      <message/>
    </field-validator>
  </field>
</validators>

Já nos deparámos com este ficheiro.

O ficheiro [FormDateModel-validation.xml] contém as seguintes regras de validação:


<!--
<!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-11/example/xwork-validator-1.0.2.dtd">
 
<validators>
 
  <field name="date1" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="date1.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{2}/\d{2}/\d{4}$</param>
      <param name="trim">true</param>
      <message key="date1.error"/>
    </field-validator>
  </field>
 
  <field name="date2" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
   </field>
 
  <field name="date3" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date3.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="min">18/05/2000</param>
      <message key="date3.error"/>
    </field-validator>
  </field>
 
  <field name="date4" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date4.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="max">20/06/2001</param>
      <message key="date4.error"/>
    </field-validator>
  </field>
 
 <field name="date5" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date5.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="min">18/05/2000</param>
      <param name="max">20/06/2001</param>
      <message key="date5.error"/>
    </field-validator>
  </field>
 
  <field name="date6" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="date6.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{2}/\d{2}/\d{4}$</param>
      <param name="trim">true</param>
      <message key="date6.error"/>
    </field-validator>
  </field>
 
</validators>

  • A regra nas linhas 11–20 verifica se o campo de entrada date1 segue o padrão de uma expressão regular que representa uma data no formato dd/mm/aaaa. Note que verificamos se a cadeia de caracteres tem o formato de uma data, mas não verificamos se é uma data válida. Assim, 29/02/2011 tem o formato de uma data, mas não é uma data válida.
  • A regra nas linhas 22–29 verifica se o campo de entrada date2 é uma data válida.
  • A regra nas linhas 31–42 verifica se o campo de entrada date3 é uma data válida >= 18/05/2000
  • A regra nas linhas 44–55 verifica se o campo de entrada date4 é uma data válida igual ou anterior a 20/06/2001.
  • A regra nas linhas 57–69 verifica se o campo de entrada date5 é uma data válida <= 20 de junho de 2001 e >= 18 de maio de 2000.
  • A regra nas linhas 71–80 verifica se o campo date6 está no formato dd/mm/aaaa.

Assim que o ficheiro [FormDateModel-validation.xml] for processado pelo interceptor de validação, este executa o método validate da ação [FormDate], caso exista. Apresentá-lo-emos juntamente com a ação completa.

14.8. A ação [FormDate]

A ação [FormDate] é a seguinte:


package example;
 
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;
 
public class FormDate extends ActionSupport implements ModelDriven, SessionAware {
 
  // constructor without parameters
  public FormDate() {
  }
 
  // action model
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormDateModel());
    }
    return session.get("model");
  }
 
  @SkipValidation
  public String clearModel() {
    // close to the model
    ((FormDateModel) getModel()).clearModel();
    // result
    return INPUT;
  }
 
  public String cancel() {
    // cleaning the model
    ((FormDateModel) getModel()).clearModel();
    // result
    return "cancel";
  }
  // SessionAware
  Map<String, Object> session;
 
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // validation
  @Override
  public void validate() {
    // date formatting
    SimpleDateFormat formateurDate = new SimpleDateFormat("dd/MM/yyyy");
    formateurDate.setLenient(false);
    // enter valid date1 ?
    if (getFieldErrors().get("date1") == null) {
      // check validity date
      try {
        formateurDate.parse(((FormDateModel) getModel()).getDate1());
      } catch (Exception e) {
        addFieldError("date1", getText("date1.error"));
      }
    }
    // valid date6 entry?
    if (getFieldErrors().get("date6") == null) {
      Date d = null;
      try {
        // check validity date
        d = formateurDate.parse(((FormDateModel) getModel()).getDate6());
        // terminal check
        if (d.after(formateurDate.parse("20/06/2001")) || d.before(formateurDate.parse("18/05/2000"))) {
          addFieldError("date6", getText("date6.error"));
          return;
        }
      } catch (Exception e) {
        addFieldError("date6", getText("date6.error"));
        return;
      }
    }
  }
}

A ação [FormDate] é construída com base no mesmo modelo que a ação [FormInt]. Iremos apenas comentar o método validate. Recorde-se que o método validate é executado após o ficheiro de validação [FormDateModel-validation.xml] ser processado e antes do método execute ser executado.

  • O método validate conclui a validação dos campos date1 e date6. O ficheiro de validação [FormDateModel-validation.xml] já tinha verificado que as cadeias de caracteres introduzidas estavam no formato de data. Verificamos agora se são, de facto, datas válidas. Note-se também que date1 e date6 eram os únicos dois campos no modelo do tipo String.
  • Linha 50: Criamos um formato de data dd/mm/aaaa para verificar se as cadeias de caracteres deste tipo são, de facto, datas válidas.
  • Linha 51: setLenient(false) impede que a data inválida 32/01/2012 seja interpretada como a data válida 01/02/2012.
  • Linha 53: Validamos date1 apenas se as validações anteriores não tiverem encontrado erros neste campo.
  • Linha 56: Verificamos se date1 é uma data válida no formato dd/mm/aaaa.
  • Linha 58: Se não for esse o caso, é apresentada uma mensagem de erro associada ao campo date1.
  • Linha 62: date6 só é validada se este campo tiver passado nas validações anteriores.
  • Linha 66: Verificamos se date6 é uma data válida
  • Linha 68: Se data6 for <18/05/2000 ou data6 > 20/06/2001, então data6 está incorreta.

14.9. Detalhes finais

O formulário anterior apresenta falhas, como se pode ver no exemplo seguinte:

  • Em [1], foi introduzida uma data inválida
  • em [2], foi aceite. Apenas os primeiros caracteres dd/mm/aaaa foram tidos em conta.

Os campos date2 a date5, que estão ligados a modelos do tipo Date, apresentam todos este problema. Mais uma vez, talvez me tenha escapado algo na documentação. Os campos date1 e date6, que estão ligados a modelos do tipo String, não apresentam este problema.