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 à 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 pelo banco de dados PostgreSQL foi devido à maior
facilidade de trabalhar justamente com os dados geográficos, ao
contrário do suporte oferecido pelo MySQL, que é bom também, mas 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 do mecanismo de persistência
utilizado, esta estratégia utiliza o Hibernate 3.6, que por sua vez
possui uma implementação de referência da JPA 2; isso é muito bom,
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 só se quisermos, porque podemos declarar um
interceptador para gerenciar isso…mas daqui a pouco eu explico melhor.
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 própria 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 à 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.
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.
No item ‘Configuration’ do nosso projeto, vamos configurar o suporte
do Eclipse ao JPA através do botão ‘Modify’. O suporte JPA nos traz
diversas vantagens, como a validação na codificação das anotações do
JPA e geração das classes a partir de um banco de dados. Para isso
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.
Feito isso, 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 à ilustrada na Figura 4:
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. Caso desejem utilizar o Tomcat 7, por exemplo, isso 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 desse artigo:as dependências de
bibliotecas e frameworks. É 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. Vamos criar um arquivo ‘pom.xml’
na raiz do nosso projeto e adicionar a ele 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:
<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>
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:
<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>
Bom, já temos o Maven configurado, agora falta apenas colocarmos ele para trabalhar:
mvn install clean
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.
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:
<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>
Agora já temos um log funcional para a nossa aplicação.
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:
<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>
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.
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:
<context-param>
<param-name>br.com.caelum.vraptor.packages</param-name>
<param-value>br.com.caelum.vraptor.util.jpa</param-value>
</context-param>
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, nós 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:
<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>
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…
String query = "SELECT foo FROM geo.tabela_mapeada ORDER BY champz";
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 no meu 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, 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:
@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;
}
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, se vocês repararem o nosso Java Bean irão perceber que eu
adicionei dois 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’:
<%@ 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>
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:
@Post("/propriedades/salvar")
public void algumMetodoMatoro(Propriedade propriedade){...}
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:
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(){
}
}
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:
<form method="post" action="<c:url value='/propriedades/salvar/'/>">
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:
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();
}
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.
Bom, 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:
@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);
}
Um detalhe legal: 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.
<table border="1" style="width: 60%;">
<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>
Mais um detalhe aqui: 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.
Deixei no GitHub o exemplo para download: https://github.com/carlosjrcabello/imastersgeo.git
Referências
Separei algumas referências que são legais para quem está começando no geo:
***
Artigo de
Carlos Alberto Junior