Validação do Formulário de Ajax Utilizando a Spring e o DWR – Parte 02

 

Eric Spiegelberg

Este artigo apresenta um projeto genérico e reutilizável de validação de formulários em Ajax que, por utilizar componentes open source existentes, pode fácil e rapidamente ser usado para o validar cada formulário de seus aplicativos. Para fornecer um exemplo concreto, o formulário de registro da aplicação exemplo jPetstore, fornecido pelo framework Spring, foi modificado. Este artigo descreve os passos realizados para modificar o jPetstore e discute os detalhes dos níveis de projeto e de execução.

Antes de começarmos, é importante destacarmos que o leitor já possui conhecimentos básicos do framework Spring e do MVC. Caso você não esteja familiarizado com Spring, não se preocupe. Conceitualmente, toda informação apresentada se aplica, igualmente, a qualquer framework MVC e seria trivial modificar os exemplos fornecidos para trabalhar com Struts ou JSF, por exemplo.

Validação do lado do servidor utilizando Spring

O primeiro componente necessário para incorporar a validação de Ajax ao seu aplicativo web é uma estrutura de validação do lado do servidor. O Spring, um framework para desenvolvimento de aplicações JavaEE é bastante popular e sua estrutura de validação são demonstradas através do aplicativo de exemplo do jPetstore. Embora o Spring ofereça um conjunto de funcionalidades bastante abrangente, uma das maiores forças do Spring é sua simplicidade. A validação da entrada de dados é uma área que é particularmente simples. O código apresentado na Listagem 1 representa o código de validação do jPetstore e fornece um exemplo de uso do Validador.

 

Listagem 1. Exemplo de uso do validator.

public class AccountValidator implements Validator {

...

 public void validate(Object obj, Errors errors) {

   ValidationUtils.rejectIfEmpty(errors, "firstName", \\

              "FIRST_NAME_REQUIRED", "First name is required.");

   ValidationUtils.rejectIfEmpty(errors, "lastName", \\

               "LAST_NAME_REQUIRED", "Last name is required.");

   ValidationUtils.rejectIfEmpty(errors, "email", \\

                "EMAIL_REQUIRED", "Email address is required.");

   ValidationUtils.rejectIfEmpty(errors, "phone", \\

               "PHONE_REQUIRED", "Phone number is required.");

   ValidationUtils.rejectIfEmpty(errors, "address1", \\

                "ADDRESS_REQUIRED", "Address (1) is required.");

   ValidationUtils.rejectIfEmpty(errors, "city", \\

               "CITY_REQUIRED", "City is required.");

   ValidationUtils.rejectIfEmpty(errors, "state", \\

               "STATE_REQUIRED", "State is required.");

   ValidationUtils.rejectIfEmpty(errors, "zip", \\

               "ZIP_REQUIRED", "ZIP is required.");

   ValidationUtils.rejectIfEmpty(errors, "country", \\

               "COUNTRY_REQUIRED", "Country is required.");

 }

...

}

Enquanto proporciona um exemplo funcional para uma aplicação exemplo, um grande empecilho desta abordagem é que a lógica é projetada para operar em todo o formulário HTML. Uma conseqüência desta abordagem é que a validação de um campo de formulário individual, requerido para suportar a validação Ajax, não é possível. Com uma pequena refatoração este problema pode ser evitado. O código resultante é apresentado na Listagem 2.

Listagem 2. Exemplo de uso do validator refatorado.

...

public void validate(Object obj, Errors errors) {

 

   Account account = (Account) obj;

 

   validateFirstName(account.getFirstName(), errors);

   validateLastName(account.getLastName(), errors);

   validateEmail(account.getEmail(), errors);

   validatePhone(account.getPhone(), errors);

   validateAddress1(account.getAddress1(), errors);

   validateCity(account.getCity(), errors);

   validateState(account.getState(), errors);

   validateZip(account.getZip(), errors);

   validateCountry(account.getCountry(), errors);

}

...

public void validateFirstName(String firstName, Errors errors) {

   ValidationUtils.rejectIfEmpty(errors, "firstName", \\

              "FIRST_NAME_REQUIRED", "First name is required.");

}

 

public void validateLastName(String lastName, Errors errors) {

   ValidationUtils.rejectIfEmpty(errors, "lastName", \\

              "LAST_NAME_REQUIRED", "Last name is required.");

}

...

Lançando o parâmetro obj em uma instância do objeto do formulário e extraindo a lógica do método validate() original em métodos separados para validar um único campo do formulário, esta refatoração nos traz vários benefícios. Em primeiro lugar, o novo código resulta vários métodos pequenos, altamente coesos e fáceis de ler. A lógica para validar o campo de entrada FristName é fácil e rapidamente localizada dentro do método validateFirstName() ao invés de estar dispersa através do método “validate everything” original. Em segundo lugar, este nível de organização e estrutura será ainda mais interessante caso seus formulários HTML ou sua lógica de validação cresçam em complexidade e/ou tamanho. Em terceiro lugar, e o mais importante, como existem métodos para validar campos individuais de entrada de dados, a validação Ajax pode agora ser utilizada.

Quando uma submissão de formulário é recebida, o processo de manipulação de formulários de Spring trabalha, em primeiro lugar, instanciando um objeto do formulário. Em seguida, um elemento de entrada de dados do formulário é vinculado ao objeto utilizando o reflexão para chamar o método apropriado de setter com o valor da entrada de dados do formulário passado como um argumento. Este processo de vinculação é repetido para cada elemento de entrada de dados. Finalmente, com o objeto passado como um parâmetro, o método de validate() num validador é chamado. Como um pedido de validação de Ajax não é uma submissão normal do formulário e contém somente uma identidade de entrada de formulário e um valor, o processo normal da Spring não pode ser utilizado e a instanciação e a vinculação devem ser realizadas “manualmente”. Entretanto, ao invés de invocar o método Validator.validate( ), que pressupõe um exemplo do objeto de revestimento do formulário, nós invocaremos o método da validação para um campo individual. O seguinte código, situado na classe de AccountValidator e exposto ao cliente, executa estes passos de invocação manuais de instanciação, vinculação e validação:

/**

 * Get the validation message for an individual

 * input field of a model object.

 *

 * @param modelObject The object to validate against.

 * @param formInputId The id attribute of the form input field.

 * @param formInputValue The input value to be validated.

 * @return The validation message.

 */

public String getInputFieldValidationMessage(String formInputId, String formInputValue) {

 

 String validationMessage = "";

 

 try

 {

   Object formBackingObject = new Account();

   Errors errors = new BindException(formBackingObject, "command");

 

   formInputId = formInputId.split("\\.")[1]; // Ignore the preceding "command." portion of the id

   String capitalizedFormInputId = StringUtils.capitalize(formInputId);

 

   // Invoke the set[formInputId] method on the Account instance

   String accountMethodName = "set" + capitalizedFormInputId;

   Class setterArgs[] = new Class[] { String.class };

   Method accountMethod = formBackingObject.getClass().getMethod(accountMethodName, setterArgs);

   accountMethod.invoke(formBackingObject, new Object[] { formInputValue });

 

   // Invoke the validate[formInputId] method of the AccountValidator instance

   String validationMethodName = "validate" + capitalizedFormInputId;

   Class validationArgs[] = new Class[] { String.class, Errors.class };

   Method validationMethod = getClass().getMethod(validationMethodName, validationArgs);

   validationMethod.invoke(this, new Object[] { formInputValue, errors });

 

   validationMessage = getValidationMessage(errors, formInputId);

 }

 catch (Exception e)

 {

   // Handle appropriately for your application

   System.out.println("New code exception: " + e);

 }

 

 return validationMessage;

}

Se o getInputFieldValidaçãoMessage fosse passado a um formInputId do username, o código precedente chamaria o setUsername() para vincular o parâmetro do formInputValue a uma instância de Account. Em seguida, o validateUsername seria invocado na classe AccountValidator. Finalmente, a getValidationMessage() seria utilizada para se beneficiar do subjacente mecanismo de mensagem de validação do formulário da Spring e recuperar o texto real que é retornado ao navegador e apresentado ao usuário.

 

/**

 * Get the FieldError validation message from the underlying MessageSource for the given fieldName.

 *

 * @param errors The validation errors.

 * @param fieldName The fieldName to retrieve the error message from.

 * @return The validation message or an empty String.

 */

protected String getValidationMessage(Errors errors, String fieldName)

{

 String message = "";

 

 FieldError fieldError = errors.getFieldError(fieldName);

 

 if (fieldError != null)

 {

  message = messageSource.getMessage(fieldError.getCode(), null,

                    "This field is invalid", Locale.ENGLISH);

 }

 

 return message;

}


Ajax utilizando DWR

Ajax amadureceu rapidamente para um estado onde existem muitas estruturas de fontes abertas de Ajax. Você estará fazendo um favor a si mesmo utilizando uma destas estruturas ao invés de criar a sua própria execução. Direct Web Remoting (DWR), um projeto java.net criado por Get Ahead, é uma escolha extraordinária. Sendo de fácil utilização, enquanto fornece características avançadas, DWR é bem documentado e direto para integrar em seu aplicativo. Bem projetado, DWR trabalha gerando dinamicamente Javascript baseado em classes de Java. Utilizando um servlet e a infra-estrutura necessária de Javascript, as chamadas de Javascript efetuadas no navegador são enviadas de forma transparente ao servidor, invocado na classe de Java, com os resultados enviados de volta ao navegador e disponíveis no Javascript. Por ser o DWR bem documentado, somente uma breve descrição das etapas realizadas para integrá-lo no aplicativo exemplo é apresentada.

As seguintes etapas foram utilizadas para integrar DWR ao aplicativo exemplo:

 

1.    Efetuar download do dwr.jar na página de download do projeto. Este artigo utiliza a versão 1.1.4.

2.    Adicionar o dwr.jar extraindo ao diretório lib do aplicativo de rede exemplo.

3.    Incluir as seguintes definições de servlet e mapeamento no web.xml:

4.    <servlet>

5.    <servlet-name>dwr-invoker</servlet-name>

6.    <display-name>DWR Servlet</display-name>

7.    <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>

8.    <init-param>

9.    <param-name>debug</param-name>

10. <param-value>true</param-value>

11. </init-param>

12. </servlet>

13. ...

14. <servlet-mapping>

15. <servlet-name>dwr-invoker</servlet-name>

16. <url-pattern>/dwr/*</url-pattern>

17. </servlet-mapping>


Como o aplicativo exemplo utiliza a estrutura de Spring para a lógica de validação, nós necessitamos, de alguma maneira, de dizer a DWR para instanciar as classes de grãos de validação de Spring e as expor com o Javascript. Felizmente, o DWR fornece a integração da Spring fora da caixa através de SpringCreators. O arquivo de configuração de DWR, chamado dwr.xml e situado no mesmo diretório que web.xml, é como segue:

 

18. <!DOCTYPE dwr PUBLIC

19. "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"

20. "http://www.getahead.ltd.uk/dwr/dwr10.dtd">

21. <dwr>

22. <allow>

23. <create creator="spring" javascript="AccountValidatorJS">

24. <param name="beanName" value="accountValidator"/>

25. <include method="getFieldInputValidationMessage"/>

26. </create>

27. </allow>

28. </dwr>



O arquivo instrui DWR para criar um objeto do Javascript chamado AccountValidatorJS. Com o DWR manipulando todos os detalhes, quando o cliente chama uma função do Javascript no objeto AccountValidatorJS, um método Java do lado do servidor de mesmo nome é invocado no exemplo da Spring da classe AccountValidator. Isto é como a lógica do lado do servidor, neste caso, a lógica de validação, é exposta ao cliente através de Ajax.