Criando uma aplicação legada (Spring MVC, JQuery, Sitemesh e JDBC Template)

A idéia aqui é criar uma aplicação simples em Java onde o usuário pode cadastrar pessoas, mas para exemplificar a utilização de várias tecnologias interessantes, transformei aquilo que você faria com 1 JSP, 1 Servlet e 1 ou 2 classes em um monstro robusto/escalável/manutenível/confiável/extensível e tantas outras palavras clichês que você usa pra vender seu software mas não sabe o real significado.

Hoje nós temos na mesa:

  • o Spring! (blá blá blá Padrões blá blá blá J2EE blá blá blá Rod Johnson blá blá blá Injeção de dependência)
  • o JQuery! (blá blá blá Javascript blá blá blá Ctrl+C/Ctrl+V blá blá blá hype blá blá blá moda)
  • o Sitemesh (blá blá blá Melhor que o Tiles/Facelets blá blá blá OpenSymphony blá blá blá Decorator)
  • o JDBC Template (blá blá blá Pior que Hibernate blá blá blá liberdade para queries blá blá blá queria usar algo diferente do Hibernate)

Ok, vamos começar a criar nossa aplicação mesozóica. Vamos precisar de algumas bibliotecas de tags, para não ter que declarar as mesmas em toda página, vamos criar uma jsp para guardar isso:

WEB-INF/jsp/declareTagLibs.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="dec" uri="http://www.opensymphony.com/sitemesh/decorator" %>
<c:set var="contexto" value="${pageContext.request.contextPath}"/>

*Vamos colocar páginas na pasta WEB-INF/jsp  para não permitir o acesso as mesmas através da url diretamente.

Agora vamos criar nossa página inicial que vai apenas redirecionar o usuário para as páginas mapeadas pelo Spring:

index.jsp

<%@ include file="/WEB-INF/jsp/declareTagLibs.jsp" %>
<%-- Redirected because we can't set the welcome page to a virtual URL. --%>
<c:redirect url="index"/>

Vamos compreender como este nome lógico (index) é mapeado ao configurar o componente ‘ViewResolver’. Segue abaixo as configurações necessárias:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="TudoEhLegado" version="2.5">
<!-- Se você não entende esta configuração, você deveria estar assando hambúrgueres-->
<display-name>TudoEhLegado</display-name>
<welcome-file-list>
 <welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Magia. Não toque -->
<servlet>
 <servlet-name>mvc</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
<!-- Ah não! Você está mapeando tudo(/), e os arquivos estáticos (.jpg, .png, .js, etc.)?
Calma porra... -->
<servlet-mapping>
 <servlet-name>mvc</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- Gambiarra do Spring que permite comandos HTTP além do GET e POST passando os mesmos por um campo hidden criado dinamicamente no formulário, desta forma podemos fazer uma aplicação  RESTfull (GET, POST, PUT, DELETE) -->
<filter>
 <filter-name>hiddenHttpMethodFilter</filter-name>
 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
 <filter-name>hiddenHttpMethodFilter</filter-name>
 <servlet-name>mvc</servlet-name>
</filter-mapping>
<!-- Vamos configurar o filtro do Sitemesh para que ele monte nosso layout com header, footer, menus, etc. -->
<filter>
 <filter-name>sitemesh</filter-name>
 <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
</filter>

<filter-mapping>
 <filter-name>sitemesh</filter-name>
 <url-pattern>*</url-pattern>
</filter-mapping>
</web-app>

Ainda no WEB-INF, temos que configurar o arquivo xml que vai coordenar os componentes do Spring, o nome deste arquivo deve ser o nome que atribuímos ao ‘DispatcherServlet’ (front-controller) seguido de ‘-servlet.xml’. Infelizmente, ainda temos mais arquivos xml para configurar depois deste.

xml_sucks_tshirt-p235397949737633150trlf_400

mvc-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Solução para tratamento de requisições de arquivos estáticos -->
<!-- Handles GET requests for /resources/** by efficiently serving static 
 content in the ${webappRoot}/resources dir -->
<mvc:resources mapping="/resources/**" location="/assets/" />
<!-- Auto-scan for controllers -->
<context:component-scan base-package="br.com.temposmodernos.web" />
<!-- auto-scanned daos and services -->
<context:component-scan base-package="br.com.temposmodernos.service.impl" />
<context:component-scan base-package="br.com.temposmodernos.dao.impl" />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basename" value="resources/messages" />
</bean>
<!-- Define o organizador OXM (marshaller) que é usado para converter objetos de/para 
XML. -->
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller" />
<bean id="xmlConverter"
 class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
 <constructor-arg ref="xstreamMarshaller" />
 <property name="supportedMediaTypes" value="application/xml" />
</bean>
<!-- View resolvers -->
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
 <property name="mediaTypes">
 <map>
  <entry key="html" value="text/html" />
  <entry key="xml" value="application/xml"/>
  <entry key="json" value="application/json" />
 </map>
</property>
 <property name="viewResolvers">
  <list>
   <bean id="viewResolver"
    class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:viewClass="org.springframework.web.servlet.view.JstlView"
    p:prefix="/WEB-INF/jsp/"
    p:suffix=".jsp" />
  </list>
 </property>
 <property name="defaultViews">
  <list>
   <bean
    class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
  </list>
 </property>
</bean>
<context:property-placeholder
location="classpath:/resources/application.properties" />
<!-- banco de dados -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
 destroy-method="close">
 <property name="driverClassName" value="${jdbc.driverClassName}" />
 <property name="url" value="${jdbc.url}" />
 <property name="username" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
</bean>
<!-- este cara deve ser declarado por último -->
<mvc:annotation-driven />
</beans>

Segue o arquivo com os dados de acesso a banco configurado pelo <context:property-placeholder>:

/resources/application.properties

################### MySQL Configuration ##########################
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/tudoEhLegado
jdbc.username=root
jdbc.password=root

Agora vamos entender como o Sitemesh funciona, este framework de construção de layouts funciona basicamente através de um servlet filter que mapeia as páginas que devem ser decoradas e insere os trechos de html nos trechos configurados:

sitemesh-architecture

sitemesh.xml

<?xml version="1.0" encoding="UTF-8"?>
<sitemesh>
 <!-- Local do arquivo de configuração de páginas decoradoras -->
 <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
 <excludes file="${decorators-file}"/>

<page-parsers>
 <parser content-type="text/html"
  class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
</page-parsers>

<decorator-mappers>
 <!-- Ao configurar esses parâmetros, é possível definir um decorator via 'meta' tag no header da página. exemplo: <meta name="decorator" content="mydecorator"> -->
 <mapper
  class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
  <param name="property.1" value="meta.decorator" />
  <param name="property.2" value="decorator" />
</mapper>

<!-- Mapeamento para internacionalização, concatena '-en' ao nome do arquivo -->
<mapper
 class="com.opensymphony.module.sitemesh.mapper.LanguageDecoratorMapper">
 <param name="match.en" value="en" />
 <param name="match.zh" value="zh" />
</mapper>

<!-- Mapeamento para compatibilidade de browser, concatena '-ie' ao nome do arquivo -->
<mapper
 class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
 <param name="match.MSIE" value="ie" />
 <param name="match.Mozilla/" value="ns" />
</mapper>

<!-- Define um decorator chamado 'printable' passando parâmetros, para páginas
que devem ter uma versão preparada para impressão -->
<mapper
 class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
 <param name="decorator" value="printable" />
 <param name="parameter.name" value="printable" />
 <param name="parameter.value" value="true" />
</mapper>

<!-- prepara um parâmetro de url que pode ser usado para
especificar o decorator que será usado em alguma URL,
e.g.:  'myurl.jsp?foobar=mydecorator' vai mapear o decorator chamado "mydecorator"-->
<mapper
 class="com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper">
 <param name="decorator.parameter" value="decorator" />
 <param name="parameter.name" value="confirm" />
 <param name="parameter.value" value="true" />
</mapper>

<mapper
 class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
 <param name="config" value="${decorators-file}" />
</mapper>

</decorator-mappers>

</sitemesh>

Finalmente, o último arquivo de configuração:

decorators.xml

<?xml version="1.0" encoding="UTF-8"?>
<decorators>

<!-- urls excluídas nunca serão decoradas pelo Sitemesh -->
<excludes>
 <pattern>**nodecorator**</pattern>
 <pattern>**nodecorator=true**</pattern>
</excludes>

<decorator name="simple" page="/WEB-INF/decorators/base_layout.jsp">
 <pattern>*</pattern>
</decorator>

</decorators>

Segue  nossa página decorator que vai montar o layout da aplicação, veja que as tags dec:title, dec:head e dec:body definem o local onde o conteúdo de escopo de página deve surgir:

/WEB-INF/decorators/base_layout.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file="/WEB-INF/jsp/declareTagLibs.jsp"%>
<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title><dec:title default="Web Page" /></title>
 <link rel="stylesheet" type="text/css"
  href="<c:url value="/resources/css/style.css"/>" />
 <script type="text/javascript"
  src="<c:url value="/resources/js/jquery-1.4.2.min.js"/>"></script>
<script type="text/javascript"
  src="<c:url value="/resources/js/jquery-ui-1.8.14.custom.min.js"/>"></script>
 <dec:head />
</head>

<body>
 <!--body code-->
 <div id="bodyWrapper">
 <div id="header">Spring MVC with Annotations</div>
 <div id="navigation">
  <a href="#">HOME</a> <a href="#">ABOUT</a>
 </div>

 <div id="pageBody">
  <div id="sidebar">
   <a href="#">Link 1</a><br />
   <a href="#">Link 2</a><br />
   <a href="#">Link 3</a><br />
  </div>
 <div id="content">
  <dec:body />
 </div>
</div>

 <div id="footer">Copyright 2011 marcelorjava - All Rights Reserved</div>
</div>

</body>
</html>

Segue o css usado pela página decoradora:

/assets/css/style.css

#bodyWrapper { width:800px; height: 400px; }
#header  { width: 800px; height: 45px; background-color: 99CCFF; font-size: 34px; text-align:           center; font-family: "Times New Roman",Georgia,Serif; } 
#navigation { width: 800px; height: 28px; background-color: CCFFFF; }
#navigation a { font-size: 22px; text-decoration: none; font-family: "Tahoma",Serif; }
#pageBody { width: 800px; height: 335px; font-size: 16px; }
#sidebar { width: 180px; height: 335px; background-color: 99FFFF; float: left; }
#content { width: 620px; background-color: white; float: right; }
#footer { width: 800px; height: 20px; background-color: CCFFFF; font-size: 18px; text-align:           center; }

Para que o jQuery UI fique legal, é interessante inserir estes 2 arquivos css:

http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/base/jquery-ui.css
http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css

Voltando ao fluxo da aplicação, para mapear o nome lógico para a página WEB-INF/jsp/index.jsp, além do ViewResolver, temos uma classe controladora que pode implementar algo na camada de modelo  e retornar o resultado para a view ‘index’ (neste exemplo esta classe não faz nada, apenas retorna o nome lógico da página index.jsp). As classes controladoras do Spring MVC  são anotadas com @Controller para serem escaneadas de acordo com a configuração (<context:component-scan>).  A classe também possui a annotation @RequestMapping para determinar o o formato do recurso que é pedido na requisição (exemplo: @RequestMapping(“/index/**”)), esta mesma anotação é usada em métodos para especificar qual deve tratar a requisição (exemplo:  @RequestMapping(value = “/pessoa/create”, method = RequestMethod.GET)).

IndexController.java

package br.com.temposmodernos.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/index/**")
public class IndexController {
 @RequestMapping(value = "/index", method = RequestMethod.GET)
 public String index() {
  return "index";
 }  
}

A annotation @Autowired é que faz a clássica injeção de dependência, back in the old days usávamos um ‘applicationContext.xml’ e declarávamos as classes de implementação que seriam injetadas, por exemplo:

<bean id="pessoaService" class="br.com.temposmodernos.service.PessoaService">
  <!-- Dependência que será injetada -->
  <property name="pessoaDao" ref="pessoaDaoBean"/>
</bean>
<bean id="pessoaDaoBean" class="br.com.temposmodernos.dao.impl.MySQLDao">

Como a view ‘index’ foi mapeada, encontramos a página WEB-INF/jsp/index.jsp. Nesta página vamos apresentar uma lista de pessoas e fornecer um link para um formulário.

WEB-INF/jsp/index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file="declareTagLibs.jsp"%>
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <style>
   .ui-autocomplete-loading { background: white url('/TudoEhLegado/resources/img/swirl-pessoa.gif') right center no-repeat; }
   #procuraPessoas { width: 25em; }
  </style>
  <title>Insert title here</title>
  <link rel="stylesheet" type="text/css"
   href="<c:url value="/resources/css/jquery-ui.css"/>" />
  <link rel="stylesheet" type="text/css"
   href="<c:url value="/resources/css/ui.css"/>" />
  <script type="text/javascript"
   src="<c:url value="/resources/js/pessoa.js"/>"></script>
</head>
<body>
 <h3>
  <c:out value="${contexto}" />
   Aplicação para POC (proof of concept): JQuery UI
   (autocomplete)+Sitemesh+SpringMVC+JDBCTemplate
 </h3>
 <br />
 <div class="ui-widget">
  <label for="procuraPessoa">Busca: </label>
  <input id="procuraPessoas" />
 </div>
 <h5>
  <a href="<c:url value="/pessoa/create"/>">Novo</a>
 </h5>
 </body>
</html>

Logo no início, declaramos a tag <script> usando a tag JSTL ‘<c:url’ para adicionar o contexto da aplicação, apesar dos arquivos estáticos estarem dentro da pasta ‘assets’ utilizamos o endereço ‘/resources/js/’ pois o mesmo é mapeado de acordo com a nossa configuração do ‘mvc:resources’.

Logo depois vamos criar o campo que será usado pelo componente ‘auto complete’ do JQuery UI. Segue o código javascript que vai ativar este componente:

pessoa.js

$(document).ready(function() {
jQuery("#procuraPessoas").autocomplete({
        minLength: 3,
        delay : 400,
        source: function(request, response) {
            jQuery.ajax(
            {
              url:     "/TudoEhLegado/pessoa/ajaxlist",
              data:    
              {
                        limit : 15,
                        term : request.term
                },
              dataType: "json",
              success: function(data)
              {
              response( jQuery.map( data.pessoaList, function( pessoa ) {
                       return {
                          id: pessoa.id,
                          value: pessoa.nome  
                       };
              }));
              }
            });
      },
   select:  function(e, ui) {
            var nome = ui.item.value;
            var id = ui.item.id;
            alert("O id de "+ nome +" eh: " + id);                        
        }
    });
});

Como funciona: Ao digitar no mínimo 3 caracteres, o parâmetro ‘source’ do componente deverá ser populado dinamicamente por uma função anônima de callback que vai usar uma chamada jQuery.ajax() para o endereço ‘/TudoEhLegado/pessoa/ajaxlist’, este endereço é mapeado pela ‘PessoaController’que vai chamar um método, que recebe as 2 variáveis declaradas em ‘data:’ (limit e term) como parâmetros (ver anotação @RequestParam), para resgatar as pessoas do banco de dados em formato JSON. Exemplo:

{"pessoaList":[{"id":1,"nome":"marcelo","endereco":"rua meh","telefone":"85 99999999"},
{"id":2,"nome":"marcelo2","endereco":"rua meh2","telefone":"85 9999.9999"}]}

Sempre recuperando pessoas de acordo com o que foi digitado no campo de texto (term), como vamos ver mais na frente na implementação do JDBC template (WHERE nome_usuario LIKE ?) . no parâmetro ‘response’ desta função de callback são configurados 2 atributos: ‘id’ e ‘value’ que correspondem aos ids e aos nomes dos objetos ‘pessoa’ da estrutura JSON, estes atributos são usados na função ‘select’ (que é chamada ao selecionar alguma opção do auto-complete). Veja como fica legal (Coloquei um Thread.sleep() na controladora para vocês verem o swirl swirl-pessoa :D ):

autocomplete

O link configurado para criar um novo registro de ‘Pessoa’ vai utilizar outra classe controladora, esta classe é interessante pois mostra como é simples preparar, validar e processar um formulário com o Spring MVC em um estilo RESTfull. Temos um mapeamento de requisição GET para um método invocado antes da renderização do formulário (neste exemplo estou configurando um modelo de número de telefone para a pessoa que será cadastrada), temos um mapeamento de requisição POST para um método invocado depois que o formulário é submetido, podemos implementar até mesmo métodos para tratar requisições DELETE e PUT com ajuda do ‘HiddenHttpMethodFilter‘, REST rlz!

PessoaController.java

package br.com.temposmodernos.web;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import br.com.temposmodernos.model.Pessoa;
import br.com.temposmodernos.service.PessoaService;
import br.com.temposmodernos.service.PessoaValidator;

@Controller
@RequestMapping("/pessoa/**")
public class PessoaController {

	@Autowired
    private PessoaService pessoaService;

	@RequestMapping(value = "/pessoa/list", method = RequestMethod.GET)
	public ModelAndView listaPessoas() {
		System.out.println("listPessoas");
		ModelAndView mav = new ModelAndView();
   	 	List<Pessoa> todasAsPessoasDoBd = pessoaService.list();
   	 	mav.addObject("pessoaList", todasAsPessoasDoBd);
   	 	return mav;
	}

	@RequestMapping(
			value = "/pessoa/ajaxlist",
			method = RequestMethod.GET)
	public ModelAndView populaAutocompletePessoas(
			@RequestParam("limit") int limit,
			@RequestParam("term") String term
		) throws InterruptedException {
		Thread.sleep(5000);
		ModelAndView mav = new ModelAndView();
   	 	List<Pessoa> todasAsPessoasDoBd = pessoaService.ajaxList(term, limit);
   	 	mav.addObject("pessoaList", todasAsPessoasDoBd);
   	 	return mav;
	}

	@RequestMapping(value = "/pessoa/create", method = RequestMethod.GET)
    public ModelAndView novo() {
    	Pessoa pessoa = new Pessoa();
    	pessoa.setTelefone("99 9999999");
        return new ModelAndView("pessoa/create", "pessoa", pessoa);
    }

    @RequestMapping(value = "/pessoa/create", method = RequestMethod.POST)
    public String novo(
    		@ModelAttribute("pessoa") Pessoa pessoa,
    		BindingResult result) {

    	PessoaValidator pessoaValidator = new PessoaValidator();
    	pessoaValidator.validate(pessoa, result);

		if (result.hasErrors()) {
			//if validator failed
			return "/pessoa/create";
		} else {
			pessoaService.save(pessoa);
	        //form success
			return "redirect:../index";
		}
    }
    //TODO permitir edição de pessoa
    /*@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public ModelAndView edit(@PathVariable Long id) {
        Pessoa pessoa = service.findById(id);
        return new ModelAndView("pessoa/edit", "pessoa", pessoa);
	}

    @RequestMapping(value = "/edit", method = RequestMethod.PUT)
    public String editar(Pessoa pessoa) {
        pessoaService.update(pessoa);
        return "redirect:../index";
    }*/
}

Com o Spring 3, o recurso de autowiring aliado ao escaneamento de classes de serviço e repositório (<context:component-scan>), a DI (Dependency Injection) é feita de forma bem mais simples. Para quem não sabe o que é Injeção de dependência, segue uma imagem que explica tudo:

dependencyinversionprinciple

Segue a classe de validação. Apenas para mostrar exemplos, decidi verificar se o campo ‘nome’ está em branco e se o telefone possui código de área 85, não criei restrições fortes para o formato, mas uma expressão regular poderia ser usada para garantir o formato do valor para o campo ‘telefone’. A validação não é nada intrusiva, você tem muita liberdade pra fazer o que quiser e manipular os resultados como bem entender.

PessoaValidator.java

package br.com.temposmodernos.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import br.com.temposmodernos.model.Pessoa;

public class PessoaValidator implements Validator {
 private String allowedAreaCodes = "85";

 /** Logger for this class and subclasses */
 protected final Log logger = LogFactory.getLog(getClass());

 public boolean supports(Class clazz) {
  return Pessoa.class.equals(clazz);
 }

 public void validate(Object obj, Errors errors) {
  Pessoa p = (Pessoa) obj;
  if (p!=null) {
   if (p.getNome().equals("")) {
    logger.info("Validando nome");
    errors.rejectValue("nome", "error.blank", new Object[]{ "nome" } ,"Blank value.");
   } else if ( !(p.getTelefone().substring(0, 2).equals(allowedAreaCodes)) ) {
    logger.info("erro de validação");
    errors.rejectValue("telefone", "error.forbidden",
    new Object[] { allowedAreaCodes }, "Código de área proibido.");
   }
  }
 }
}

Caso a validação falhe, temos tags definidas na página do formulário (form:errors) que configuram onde a mensagem de erro é exibida:

form-pessoa-create

WEB-INF/jsp/pessoa/create.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
 pageEncoding="ISO-8859-1"%>
<%@ include file="/WEB-INF/jsp/declareTagLibs.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <title>Formulário simples</title>
 </head>
<body>
 <h1><fmt:message key="pessoaForm.heading"/></h1>
 <form:form action="${contexto}/pessoa/create" method="POST" modelAttribute="pessoa">
  <table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
   <tr>
    <td align="right" width="20%">Nome:</td>
    <td width="20%">
     <form:input path="nome"/>
    </td>
    <td width="60%">
     <form:errors path="nome" cssClass="error"/>
    </td>
   </tr>
   <tr>
    <td align="right" width="20%">Endereço:</td>
     <td width="20%">
      <form:input path="endereco"/>
     </td>
     <td width="60%">
     </td>
    </tr>
    <tr>
     <td align="right" width="20%">Telefone:</td>
      <td width="20%">
       <form:input path="telefone"/>
      </td>
     <td width="60%">
      <form:errors path="telefone" cssClass="error"/>
     </td>
    </tr>
   </table>
   <br>
   <input type="submit" align="center" value="Execute">
  </form:form>
 </body>
</html>

As tags ‘<fmt:message>’ e mensagens de erro de validação estão armazenadas em um arquivo de propriedades, como definido pelo ‘ResourceBundleMessageSource‘.

/resources/messages.properties

pessoaForm.heading=:: Cadastro de Pessoas ::
error.blank=O campo {0} está em branco!
error.forbidden=Não é permitido inserir um código de área diferente de {0}!

Como todo bom framework J2EE, o formulário já cria pra você uma instância da sua classe de modelo e envia para a controladora (neste caso, através do parâmetro ‘modelAttribute’). Como o método da requisição é POST, vai cair no segundo método da controladora que mapeia a requisição ‘/pessoa/create’, caso não tenha erros na validação, o serviço ‘PessoaService’ vai se encarregar de chamar a nossa classe DAO (Data Access Object) para persistir nosso objeto ‘pessoa’.

PessoaService.java

package br.com.temposmodernos.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.model.Pessoa;

public interface PessoaService {
	public boolean verificaSePessoaExiste(int id);
	public void save(Pessoa p);
	public void remove(Pessoa p);
	public List list();
	public List ajaxList(String pessoaNome, int limit);
	public Pessoa findById(int id);
}

Não vamos implementar realmente nem metade desses métodos, só deixei assim pra ficar mais legal. Segue a classe de implementação marcada com a annotation @Service (annotation que torna a classe elegível para escaneamento automático,  basicamente cumpre o mesmo papel da @component, mas é ideal para serviços).

PessoaServiceImpl.java

package br.com.temposmodernos.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.model.Pessoa;
import br.com.temposmodernos.service.PessoaService;

@Service("pessoaService")
public class PessoaServiceImpl implements PessoaService{
	@Autowired
	private PessoaDao pessoaDao;

	@Autowired
	public PessoaServiceImpl(PessoaDao pessoaDao) {
		this.pessoaDao = pessoaDao;
	}
	public boolean verificaSePessoaExiste(int id){
		return pessoaDao.exists(id);
	}
	public void save(Pessoa p){
		pessoaDao.save(p);
	}
	public void remove(Pessoa p){
		pessoaDao.remove(p);
	}
	public List<Pessoa> list(){
		return pessoaDao.list();
	}
	public List<Pessoa> ajaxList(String nomePessoa, int limit){
		return pessoaDao.ajaxList(nomePessoa, limit);
	}
	public Pessoa findById(int id){
		return pessoaDao.findById(id);
	}
}

Ok, persistência. Abaixo encontra a classe da camada ‘Model’:

Pessoa.java

package br.com.temposmodernos.model;
public class Pessoa {
 private int id;
 private String nome;
 private String endereco;
 private String telefone;
 public Pessoa(String nome, String endereco, String telefone){
  this.nome = nome;
  this.endereco = endereco;
  this.telefone = telefone;
 }

public Pessoa() {
}

 // Getters & Setters
}

Segue a classe da camada DAO:

PessoaDao.java

package br.com.temposmodernos.dao;

import java.util.List;
import br.com.temposmodernos.model.Pessoa;

public interface PessoaDao {
	public boolean exists(int id);
	public void save(Pessoa p);
	public void remove(Pessoa p);
	public List<Pessoa> list();
	public List<Pessoa> ajaxList(String nome, int limit);
	public Pessoa findById(int id);
}

PessoaDaoImpl.java

package br.com.temposmodernos.dao.impl;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.dao.mapper.PessoaRowMapper;
import br.com.temposmodernos.model.Pessoa;

@Repository("pessoaDao")
public class PessoaDaoImpl implements PessoaDao {

	@Autowired
	private DataSource dataSource;

	@Override
	public void save(Pessoa pessoa) {
		JdbcTemplate insert = new JdbcTemplate(dataSource);
		insert.update("INSERT INTO usuarios (nome_usuario, endereco_usuario, telefone_usuario) VALUES(?,?,?)",
				new Object[] { pessoa.getNome(), pessoa.getEndereco(), pessoa.getTelefone() });
	}

	@Override
	public void remove(Pessoa pessoa) {
		JdbcTemplate delete = new JdbcTemplate(dataSource);
		delete.update("DELETE from usuarios where is_usuario= ?",
				new Object[] { pessoa.getId() });
	}

	@Override
	public List<Pessoa> list() {
		JdbcTemplate select = new JdbcTemplate(dataSource);
		return select.query("SELECT id_usuario, nome_usuario, endereco_usuario, telefone_usuario FROM usuarios",
				new PessoaRowMapper());
	}

	@Override
	public List<Pessoa> ajaxList(String nome, int limit) {
		JdbcTemplate select = new JdbcTemplate(dataSource);
		return select.query("SELECT id_usuario, nome_usuario, endereco_usuario, telefone_usuario " +
				"FROM usuarios " +
				"WHERE nome_usuario LIKE ? " +
				"LIMIT 0, ?",
				new Object[] { nome+"%", new Integer(limit) },
				new PessoaRowMapper());
	}

	@Override
	public boolean exists(int id) {
		JdbcTemplate exists = new JdbcTemplate(dataSource);
		return (exists.queryForInt("SELECT COUNT(*) FROM pessoas where ID= ?",
				new Object[] { id }) != 0);
	}

	@Override
	public Pessoa findById(int id) {
		JdbcTemplate select = new JdbcTemplate(dataSource);
		return (Pessoa) select.queryForObject("select ID, NOME, ENDERECO, TELEFONE from pessoas WHERE ID= ?",
				new Object[] { id }, new PessoaRowMapper());
	}
}

O único objeto injetado que é configurado através de xml é o ‘dataSource’ declarado nesta classe, ele é utilizado para conexão com o MySQL e criar o template JDBC que vai executar as instruções SQL e retornar objetos/listas prontos para uso.

Classes utilitárias do JDBC Template:

PessoaResultSetExtractor.java

package br.com.temposmodernos.dao.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.ResultSetExtractor;
import br.com.temposmodernos.model.Pessoa;

public class PessoaResultSetExtractor implements ResultSetExtractor {

	public Object extractData(ResultSet rs) throws SQLException {
		Pessoa pessoa = new Pessoa();
		pessoa.setId(rs.getInt(1));
		pessoa.setNome(rs.getString(2));
		pessoa.setEndereco(rs.getString(3));
		pessoa.setTelefone(rs.getString(4));
		return pessoa;
	}
}

PessoaRowMapper.java

package br.com.temposmodernos.dao.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class PessoaRowMapper implements RowMapper {
	@Override
	public Object mapRow(ResultSet rs, int line) throws SQLException {
		PessoaResultSetExtractor extractor = new PessoaResultSetExtractor();
		return extractor.extractData(rs);
	}
}

Segue o SQL para criar a tabela ‘usuarios’ em seu banco de dados:

CREATE TABLE `usuarios` (
  `id_usuario` int(10) NOT NULL AUTO_INCREMENT,
  `nome_usuario` varchar(300) DEFAULT NULL,
  `endereco_usuario` varchar(500) DEFAULT NULL,
  `telefone_usuario` varchar(15) DEFAULT NULL,
  PRIMARY KEY (`id_usuario`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=latin1

Depois de criar todas esses arquivos, seu projeto deve ficar mais ou menos assim:

project-TudoEhLegado

Segue a lista de JARs que foram utilizados:

jstl.jar
junit-3.8.2.jar
mysql-connector-java-5.1.14.jar
commons-logging.jar
commons-dbcp-1.4.jar
commons-pool-1.5.6.jar
jackson-core-asl-1.6.0.jar
jackson-mapper-asl-1.6.0.jar
sitemesh-2.4.2.jar
spring-webmvc.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-orm.jar
spring-oxm.jar
spring-web.jar
spring-jdbc.jar
spring-dao.jar
spring-asm.jar
spring-expression.jar
standard.jar
xstream.jar

PS: Para quem usava Spring 2.5 e não está encontrando o ‘spring-webmvc.jar’ e o ‘spring-dao.jar’, os mesmos foram renomeados para ‘org.springframework.web.servlet-3.0.5.RELEASE.jar’e ‘org.springframework.transaction-3.0.5.RELEASE.jar’ respectivamente.

UPDATE:

Atenção: uma instrução jQuery.ajax não entra na função de ‘success’ (mesmo se você constatar via Firebug que o resultado foi um HTTP 200) se o json estiver mal-formatado.

jQuery.ajax({

 url: ‘/contexto/jsp/RetornaUmJSON.jsp’,
dataType: ‘json’,
data: requestData,
success: function(data)
 {
   listPeople = convertListPeople(data.searchResults);
   listPeople = listPeople.sort(sortAlphabetical);
   defineTabActivit(a,id)
 }
});

Sempre é interessante validar os dados (http://jsonlint.com/), uma simples quebra de linha pode destruir toda a sua aplicação. exemplo:

{"pessoaList":[{"id":1,"nome":"marcelo \n
Rodrigues","endereco":"rua meh","telefone":"85 99999999"},

Para corrigir você deve ter certeza que o código que cria o json está tratando esses dados incorretos:

--RetornaUmJSON.jsp
...
jsonOutput.append(objectNameValue.replaceAll("[\n\r]"," ")); 
...

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s