Iniciando bem no geoprocessamento com VRaptor 3 e Postgis

Com a constante e rápida evolução tecnológica, novas demandas e usuários mais exigentes por natureza, as aplicações voltadas a geoprocessamento assim como as demais também estão saindo dos Desktops e indo para os navegadores. Os usuários de aplicações ‘gis’ desejam ‘por direito’ que as suas aplicações WEB também façam desenho de geometrias, manipulações de projeções, sobreposições e centenas de outras operações ali à um clique de distância assim como nos seus aplicativos Desktop conhecidos, como o Quantum GIS por exemplo.

Assim como em qualquer projeto precisamos saber por onde seguir, ou seja, saber quais tecnologias iremos utilizar, a melhor solução de arquitetura para garantir uma boa e segura expansividade, quais as equivalências com outras tecnologias, etc. Enfim, são diversas coisas que devemos ter em mente ao começar com geoprocessamento para a WEB.

Para quem tem experiência com geoprocessamento já deve estar familiarizado com o famoso OpenLayers que se encaixa com o JSF (geralmente o 1.2), mas hoje apresento um outro nome bem conhecido, mas talvez nem tanto para o mundo gis, o VRaptor 3, um ótimo framework para desenvolvimento WEB ágil que é mantido pela Caelum e também por uma comunidade de desenvolvedores.

Objetivo

Ambientar o leitor que deseja iniciar no desenvolvimento de soluções de geoprocessamento para WEB utilizando o framework VRaptor 3 em conjunto com outras tecnologias envolvidas tais como o Hibernate 3.6 com as suas extensões gis.

Estratégia de arquitetura

Quando estamos construindo projetos Java é comum termos um toró de frameworks e bibliotecas, aqui não muito diferente, mas sempre devemos calcular bem o que vamos embutir nos projetos, em diversas consultorias vi projetos problemáticos devido a falta de gestão nas dependências, coisa que um simples Maven poderia ter resolvido, por isso vamos ficar atentos. Bom, a solução que vamos empregar hoje está baseada na estratégia de utilizarmos:

  • VRaptor 3 como framework MVC e também provedor CDI naturalmente;
  • Hibernate 3.6 como mecanismo de persistência abstraído pela especificação JPA 2;
  • Hibernate Spatial como extensão para manipulação de dados geográficos;
  • OpenLayers como framework para visualização de mapas (segunda parte);
  • PostgreSQL 8.4 ou superior com a sua extensão para dados geográficos (Postgis);

Persistência

A escolha neste caso pelo banco de dados PostgreSQL foi devido a maior facilidade de trabalhar justamente com os dados geográficos, ao contrário do suporte oferecido pelo MySQL que por sinal é bom também, ele ainda deixa a desejar em algumas praticidades. Para quem desejar um artigo explicando como instalar e configurar o banco de dados eu publiquei um neste endereço.

Para garantirmos a portabilidade quando ao mecanismo de persistência utilizado, esta estratégia utiliza o Hibernate 3.6, este que por sua vez possui uma implementação de referência da JPA 2, isto é muito bom logo porque o VRaptor 3 carrega as configurações dele e injeta no EntityManager por CDI nas nossas lógicas. Neste ponto ganhamos muito, porque não precisamos mexer em nada, só precisamos controlar as transações com o banco, e se quisermos, porque podemos declarar um interceptador para gerenciar isto…mas daqui a pouco eu explico melhor isto.

Framework MVC para WEB

Aqui vem o pulo do gato que dá nome ao artigo, a versão 3 do VRaptor (atualmente a 3.4) evoluiu muito, sem comparação com a morosa versão 2…bom é preciso levar em conta as limitações e principalmente as ideias e conceitos na época. O VRaptor 3 tornou o desenvolvimento WEB muito simples e como efeito colateral ágil (que pode ser relativo). A ideia de utilizar convenção sobre configuração, CDI, restfull e outras boas práticas o tornou muito prático e claro na hora de codificar.

O grande problema (no meu ponto de vista) é que a maioria dos desenvolvedores acham desvantagem não ter os componentes prontos como do JSF, do AJAX transparente, etc., só que por experiência minha em desenvolvimento, quanto mais detalhada e complexa for a lógica mais os componentes prontos do JSF deixarão de atender…mas este não é o foco deste artigo. Apenas ressalto que utilizando o VRaptor trabalharemos com JSTL, HTML, CSS, Javascript, etc.

Mãos a massa

Vamos começar criando a nossa estrutura básica de um Dynamic WEB Project do Eclipse conforme as Figuras 1 e 2 abaixo:

Criando um DWP no Eclipse.
Criando um DWP no Eclipse.

Avançando no wizard  do Eclipse iremos definir o nome e as configurações da versão do módulo WEB e também do  contêiner WEB (opcional neste ponto).

Configuração do WEB module e contêiner.
Configuração do WEB module e contêiner.

No item ‘Configuration’ do nosso projeto, vamos configurar o suporte do Eclipse ao JPA através do botão ‘Modify’. O suporte JPA nos trás diversas vantagens como por exemplo a validação na codificação das anotações do JPA e geração das classes a partir de um banco de dados, para isto precisamos apenas marcar a opção ‘JPA’ na versão ‘2.0’ conforme o apresentado na Figura 3.

Configuração do suporte ao JPA 2.0.
Configuração do suporte ao JPA 2.0.

Feito isto clicamos em ‘OK’ e finalizamos a criação da estrutura básica do nosso projeto, se tudo estiver correto neste ponto você terá uma estrutura semelhante a ilustrada na Figura 4:

Estrutura do Dynamic WEB Project no Eclipse.
Estrutura do Dynamic WEB Project no Eclipse.

Como eu tinha dito antes no ponto da configuração do contêiner WEB, eu gosto de utilizar o Jetty 7.6, e por isto, vou utilizá-lo neste tutorial. Mas caso desejem utilizar o Tomcat 7 por exemplo, isto não impactará em nenhuma modificação na estrutura geral da nossa aplicação.

Lembrando que para rodar o Jetty de modo embarcado no Eclipse necessitamos de um plugin adicional, o update site está aqui.

Pronto pessoal, já temos e esqueleto da nossa aplicação WEB pronta.

Dependências

Este é um item importantíssimo deste artigo, as dependências de bibliotecas e frameworks. Isto é algo comum nos projetos Java e quando vamos para a parte de geoprocessamento não é muito diferente, basicamente temos o Hibernate Spatial que depende do Hibernate Core e de alguns jars do Geotools (que é gigante), mais especificamente o módulo de geometrias do Geotools.

Para resolver esse impasse vamos utilizar o Maven para gerenciar as nossas dependências, para isto vamos criar um arquivo ‘pom.xml’ na raiz do nosso projeto, e adicionar ao mesmo as nossas respectivas dependências, no caso:

  • VRaptor 3;
  • Driver do PostgreSQL 9.1;
  • Hibernate Core e Hibernate Spatial (esta última já inclui o core);
  • JPA 2.0;
  • Outras libs requeridas para projetos Web;

Logo no nosso ‘pom.xml’ iremos ter as seguintes dependências declaradas:

[xml]
<dependencies>
<dependency>
<groupId>br.com.caelum</groupId>
<artifactId>vraptor</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernatespatial</groupId>
<artifactId>hibernate-spatial-postgis</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
[/xml]

Temos um detalhe que é para a dependência do Hibernate Spatial, necessitamos adicionar um repositório extra para o Maven,  e um para o Hibernate devido à um bug com o seu provider. Para isto vamos adicionar estes repositórios:

[xml]
<repositories>
<repository>
<id>jboss</id>
<name>JBoss repository</name>
<url>http://repository.jboss.org/maven2</url>
</repository>
<repository>
<id>OSGEO GeoTools repo</id>
<url>http://download.osgeo.org/webdav/geotools</url>
</repository>
<repository>
<id>Hibernate Spatial repo</id>
<url>http://www.hibernatespatial.org/repository</url>
</repository>
</repositories>
[/xml]

Bom, já temos o Maven configurado, agora falta apenas colocarmos ele para trabalhar:

[bash]
mvn install clean
[/bash]

Pronto! Agora já temos o nosso projeto com todas as dependências resolvidas.

Configurações no projeto

A partir de agora iremos fazer as configurações básicas no projeto, no caso o Log4J, JPA 2 e o arquivo descritor web.xml com as configurações do VRaptor 3.

Log4j

O Log4j necessita de um arquivo de configuração para capturar as mensagens de log da aplicação  (que eu acho que teria como fazer pelo Maven mas não sei), por isso vamos criar o arquivo ‘log4j.xml’ na raiz da pasta ‘src’ com a seguinte configuração:

[xml]
<appender name=”stdout”>
<layout>
<param name=”ConversionPattern” value=”%d{HH:mm:ss,SSS} %5p [%-20c{1}] %m%n”/>
</layout>
</appender>
<category name=”org.hibernate.ejb”>
<priority value=”INFO” />
<appender-ref ref=”stdout” />
</category>
<category name=”org.springframework”>
<priority value=”INFO” />
<appender-ref ref=”stdout” />
</category>
<root>
<priority value =”info” />
<appender-ref ref=”stdout” />
</root>
[/xml]

Agora já temos um log funcional para a nossa aplicação.

VRaptor 3

A configuração do VRaptor 3 é muito simples, basicamente precisaremos configurar a Servlet padrão dele no web.xml do projeto. Já que iremos mexer nesta configuração iremos aproveitar também para configurar a codificação do nosso projeto, o i18n e também o pacote para gerenciar as sessões para o EntityManager da JPA.

Assim teremos as seguintes configurações:

[xml]
<context-param>
<param-name>br.com.caelum.vraptor.encoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>messages</param-value>
</context-param>
<filter>
<filter-name>vraptor</filter-name>
<filter-class>br.com.caelum.vraptor.VRaptor</filter-class>
</filter>
<filter-mapping>
<filter-name>vraptor</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<context-param>
<param-name>br.com.caelum.vraptor.packages</param-name>
<param-value>br.com.caelum.vraptor.util.jpa</param-value>
</context-param>
[/xml]

Se formos iniciar o nosso servidor, veremos que o VRaptor 3 já está inicializando através do resultado do log conforme o ilustrado na Figura 5.

Start inicial do VRaptor 3.
Start inicial do VRaptor 3.

JPA  2

A configuração do nosso persistence.xml é bem tranquila, mas primeiro devemos lembrar de um recurso incrível que o VRaptor 3 nos fornece que é a possibilidade de delegarmos a ele o gerenciamento da abertura e fechamento das sessões tanto para o Hibernate puro como sobre a JPA, como eu citei no começo deste artigo que iríamos abstrair a camada de persistência, vamos dar uma olhada numa declaração que fizemos no web.xml, mais especificamente esta:

[xml]
<context-param>
<param-name>br.com.caelum.vraptor.packages</param-name>
<param-value>br.com.caelum.vraptor.util.jpa</param-value>
</context-param>
[/xml]

Esta configuração habilita que o VRaptor 3 ative os seus interceptadores para controlar as  transações. Este modo de controle de transações é o famoso Open Session in View. Além de transações, ele também cria as sessões com o banco e injeta nas nossas lógicas o EntityManager pronto para utilizarmos.

Só enfatizando mais uma vez, que estamos ganhando em tempo abstraindo as complexidades utilizando parte das boas práticas que o VRaptor 3 adotou nesta versão.

Bom, para que o VRaptor 3 consiga fazer estas marotagens nos precisamos seguir algumas convenções, iniciando pelo nome da nossa unidade de persistência, precisamos chamá-la de ‘default’. Pronto! :)

Agora devemos apenas nos preocupar com as demais configurações do arquivo, assim temos:

[xml]
<class>com.wp.carlos4web.geo.beans.Propriedade</class>
<properties>
<property name=”hibernate.dialect” value=”org.hibernatespatial.postgis.PostgisDialect” />
<property name=”hibernate.connection.url” value=”jdbc:postgresql://localhost:5432/imastersgeo” />
<property name=”hibernate.connection.driver_class” value=”org.postgresql.Driver” />
<property name=”hibernate.connection.username” value=”postgres” />
<property name=”hibernate.connection.password” value=”postgres” />
<property name=”hibernate.default_schema” value=”geo” />

<property name=”hibernate.show_sql” value=”true”/>
<property name=”hibernate.format_sql” value=”true”/>
<property name=”hibernate.generate_statistics” value=”true”/>
<property name=”hibernate.hbm2ddl.auto” value=”update” />
</properties>
[/xml]

Uma atenção ao tipo do dialeto que iremos utilizar, neste caso é o PostgisDialect e também ao esquema padrão do banco para não precisarmos colocar sempre o nome dele antes de uma tabela…

[java]
String query = “SELECT foo FROM geo.tabela_mapeada ORDER BY champz”;
[/java]

Pessoal um detalhe quanto ao criar o banco, para quem estiver começando com Postgis, tem que lembrar que quando criamos um banco devemos estender o mesmo a partir do template do postgis, para saber mais tem um outro post aqui no blog também. Então vou deixar o banco criado e o Hibernate apenas irá criar ou atualizar o esquema das tabelas.

Hard coding….

Bom pessoal, agora vamos codificar e usar na prática toda esta história…iremos começar definindo uma entidade que irá representar uma simples propriedade que possui um nome e uma localização geográfica (latitude e longitude), assim fazendo uma análise altamente ‘complexa’ teremos:

[java]
@Entity(name=”propriedade”)
public class Propriedade implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;

@Column(nullable=false)
private String nome;

@Column(nullable=false)
@Type(type=”org.hibernatespatial.GeometryUserType”)
private Point localizacao;

@Transient
private Double x, y;
// Getters e setters omitidos…
}
[/java]

Todas as anotações são da própria especificação da JPA, com exceção da @Type, que é do Hibernate e que pra ela passamos um tipo específico para geometrias, no caso o GeometryUserType. Este é um ponto de amarração da nossa aplicação com um determinado mecanismo de persistência :(, se necessitarmos trocar precisaremos achar algum outro que de suporte a isto.

Bom pessoal, se vocês reparem o nosso Java Bean irão perceber que eu adicionei dos atributos nesta classe, mas pra isto? O VRaptor faz a conversão e população automática dos valores submitados  ao nosso método que está mapeado na URL de submit, mas ele não sabe como fazer para instanciar um objeto do tipo Point, basicamente neste ponto nós poderíamos criar um Converter para realizar este trabalho, mas preferi manter desta forma para vocês possam visualizar melhor.

Antes de eu terminar a história dos campos transientes da listagem acima iremos fazer um formulário HTML simples para podermos inserir propriedades na nossa aplicação. Assim vamos lá criar o nosso formulário em um arquivo JSP localizado na pasta ‘WebContent/WEB-INF/jsp/propriedade/formulario.jsp’:

[java]
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″%>
<%@ taglib prefix=”fmt” uri=”http://java.sun.com/jsp/jstl/fmt”%>
<%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core”%>
<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″>
<title>Cadastro de propriedades</title>
</head>
<body>
<form method=”post” action=”<c:url value=’/propriedades/salvar/’/>”>
Nome: <input type=”text” name=”propriedade.nome”/>
<br/>
X: <input type=”text” name=”propriedade.x”/>
<br/>
Y: <input type=”text” name=”propriedade.y”/>
<br/>
<br/>
<input type=”submit” value=”Salvar”/>
</form>
</body>
</html>
[/java]

Bom, continuando aquela explicação de antes pessoal, reparem que a convenção adotada pelo VRaptor é usar o name dos elementos HTML seguidos do atributo alvo da nossa classe, ou seja “propriedade.nome”, “propriedade.id”, etc. Este nome tem que ser o mesmo nome do parâmetro do nosso método que irá receber, ou seja, como definimos “propriedade.*” o VRaptor irá jogar o objeto convertido em:

[java]
@Post(“/propriedades/salvar”)
public void algumMetodoMatoro(Propriedade propriedade){…}
[/java]

Ainda explicando lá de antes, o VRaptor não entende o construtor do objeto Point e por isto eu não uso ele no formulário, assim o VRaptor não instanciará ele, cabendo a nós fazermos isto. Basicamente eu apenas necessito de um ponto X e Y para instanciar este objeto Point (mostrei daqui a pouco).

O nosso formulário está pronto, mas precisamos agora fazer que ele fique disponível na Web, então vamos fazer que o VRaptor crie um caminho para o formulário, assim iremos criar uma classe controller:

[java]
package com.wp.carlos4web.geo.controllers;
import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Resource;

@Resource
public class PropriedadeController {

@Get(“/propriedades/cadastrar/”)
public void formulario(){
// nadinha por aqui… ;)
}
}
[/java]

Simples não?!?

Quando anotamos uma classe Java com a anotação @Resource ela ficará visível na Web, ou seja, automaticamente todos os seus métodos públicos ficarão visíveis, mesmo se eu não colocar anotações para definir a URI de acesso. Para o nosso formulário de cadastro eu criei o método ‘formulario()’ que não possui nenhum código, e de acordo com a convenção do VRaptor ele irá carregar o arquivo do diretório WebContent/WEB-INF/jsp/propriedade/formulario.jsp. Reparem que eu defini uma URI através da anotação @Get colocada sobre o método, logo podemos mapear todas as URIs que desejarmos para acessar um mesmo método. Bem vindos ao Rest :)

Bom pessoal, sem muito esforço já temos o nosso JavaBean, formulário e uma classe controladora criados, agora nós precisamos fazer uma lógica para salvar isto. Então vamos utilizar aquela URI no atributo action do formulário HTML:

[java]
<form method=”post” action=”<c:url value=’/propriedades/salvar/’/>”>
[/java]

Ela contem uma URI que deverá estar mapeada via anotação em uma classe controladora, como já temos uma classe controladora para coisas das propriedades, iremos usar ela mesma:

[java]
private final Result result;
private final EntityManager manager;

public PropriedadeController(Result result, EntityManager manager) {
super();
this.result = result;
this.manager = manager;
}
@Post(“/propriedades/salvar/”)
public void salvar(Propriedade propriedade){
if(propriedade == null){
throw new IllegalArgumentException(“Nenhuma propriedade informada”);
}
Point localizacao = new GeometryUtils().from(propriedade.getX(), propriedade.getY()).convertTo(Point.class);
propriedade.setLocalizacao(localizacao);
manager.persist(propriedade);
result.redirectTo(this.getClass()).listaPropriedades();
}
[/java]

Pra quem aí já utilizou a JPA vai se familiarizar com o código, mas irá ser perguntar porque raios eu não abri e fechei a sessão na hora de executar o persist. Se derem uma olhada no início do nosso artigo, na parte de configurações da aplicação, irão ver que eu deleguei isso ao VRaptor…economizo assim 2 linhas de código e mais umas 3 ou 4 para tratar alguma exceção que venha a acontecer :)

Quase iria esquecer de comentar, reparem que adicionei os objetos Result e EntityManager no construtor padrão do nosso controller, o próprio VRaptor com uso do CDI irá prover estes caras pra nós.

Bom, voltando na história do XY, eles foram instanciados pelo VRaptor porque eram objetos Double simples, agora eu uso uma biblioteca marota para reduzir a complexidade de se mexer diretamente com o Geotools, a GeometryUtils. Com apenas uma linha um consigo fazer a instanciação de geometrias a partir de pontos ou então de Strings WKT.

Boa pessoal, então quando terminarmos de salvar a nossa propriedade, vamos redirecionar para uma página de listagem, para isso usamos o objeto Result, que tem milhões de funcionalidades além de redirecionar páginas, com ele também incluímos valores nas páginas JSP que ficam acessíveis pela EL, alteramos o tipo do resultado (HTML, XML, JSON, etc.), reuso de outras lógicas e muito mais. Finalmente então, vamos criar mais um método no nosso controller:

[java]
@Get(“/propriedades/listar/”)
public void listaPropriedades(){
String query = “SELECT p FROM propriedade p ORDER BY p.nome”;

Collection<Propriedade> propriedades = manager.createQuery(query, Propriedade.class).getResultList();
result.include(“propriedades”, propriedades);
}
[/java]

Um detalhe legal galera, quando invocamos o Result.redirectTo(), ele irá fazer um redirect para a URI bonitinha que mapeamos, assim a nossa aplicação sempre ficará com as ‘friendly urls‘ em funcionamento e também será muito útil para os mecanismos de busca realizarem a indexação da nossa aplicação.

Já que temos a lógica que realiza a consulta, agora vamos ao nosso HTML básico. Pessoal, desta vez irei suprimir a declaração do HTML pois é o mesmo usado no formulário de cadastro.

[java]
<table border=&amp;quot;1&amp;quot; style=&amp;quot;width: 60%;&amp;quot;&amp;gt;
<tr>
<th>ID</th>
<th>Nome</th>
<th>Localização</th>
</tr>
<c:forEach var=”p” items=”${propriedades}”>
<tr>
<td>${p.id}</td>
<td>${p.nome}</td>
<td>${p.localizacao.x} / ${p.localizacao.y}</td>
</tr>
</c:forEach>
</table>
[/java]

Mais um detalhe aqui pessoal, o objeto Point possui dois atributos X e Y, logo não são aqueles da classe propriedade, somente utilizo eles na hora de submitar o objeto.

Conclusão

Esse foi o primeiro artigo da série que pretendo fazer, no próximo iremos ver como usar o OpenLayers e também como fazemos para persistir polígonos e também linhas.

Já participei de vários projetos Web voltados a geoprocessamento, tanto com JSF 1.2 e 2.0 como também em VRaptor 3, não faço propaganda e nem nenhum tipo de influência, mas para os próximos exemplos que veremos ficará bem clara a vantagem que temos ao usar um framework action based como o VRaptor aplicado neste tipo de aplicação.

Este artigo foi de grande valia pra mim porque consegui utilizar o Maven com sucesso…sempre vinha patinando com ele.

Referências

Separei algumas referências que são legais para quem está começando no geo.

http://vraptor.caelum.com.br/documentacao/

http://vraptor.caelum.com.br/cookbook/

https://github.com/carlosjrcabello/geometry-utils

http://carlos4web.wordpress.com/category/java/geotools-java/

Download do exemplo

Deixei disponível lá no Github através do endereço abaixo:

https://github.com/carlosjrcabello/imastersgeo.git