Integrando IIS ao Jetty, Apache URL Redirect e Filtro ISAPI

Bom galera, muito tempo sem postar, né? Peço desculpas pro pessoal que acompanha o blog mas alguns projetos pessoais acabaram por tomar todo o tempo livre. Resolvi reservar um tempo para me dedicar a um post que acredito ser interessante pro pessoal da comunidade Java, uma solução simples para algo que acredito ser comum.

Na empresa em que trabalho temos aplicações disponibilizadas tanto pelo IIS da Microsoft quanto pelo Jetty, tínhamos então os sites estáticos e algumas aplicações em .net hospedados no IIS e os WebServices desenvolvidos em Java e sites em Grails hospedados pelo Jetty.

Isso sempre foi um problema pois além de ser algo feio e nada transparente para nossos clientes pois precisavam acessar dois caminhos distintos (um na porta 80 do IIS e outro na porta 8080 do Jetty), era impossível fazer a comunicação entre uma aplicação em Flex(IIS) e com um WebService(Jetty) via HTTPService graças a política de acesso a diferentes domínios do Flex. Estava realmente na hora de conseguir resolver este problema e integrar o Jetty ao IIS, mas como?

Pesquisando um pouco na internet encontrei alguns artigos mostrando como integrar o Apache Tomcat ao IIS, porém nada especifico sobre o Jetty, eu sabia que era possível pois o Jetty tem suporte implementado para o protocolo AJP13 mas uma documentação simples e atualizada era praticamente impossível de ser encontrada, imagine em português. Foi um pouco na tentativa e erro que consegui o resultado esperado, mas no final se tornou algo transparente, performático e simples de entender.

Bom, primeiramente gostaria de explicar porque escolhemos o Jetty e não o Apache Tomcat para fazer o deploy de nossas aplicações? O Tomcat já foi nosso antigo servidor de aplicações e tivemos problemas de performance e vazamento de memória e o simples ato de migrar para o Jetty resolveu um problema que estava nos dando muita dor de cabeça. O pessoal da Caelum já chegou a relatar um problema parecido e documentado aqui com o deploy do GUJ e recomendo que leiam. Vamos então mostrar como configurar o IIS com o IAPI Redirect da Apache para redirecionar as chamadas para o nosso Jetty.

Para começar faça download da ultima versão estável do Jetty 6.1 aqui: http://dist.codehaus.org/jetty/jetty-6.1.21/, faça download também da DLL para o IIS, ISAPI Url Redirect da Apache aqui: http://www.apache.org/dist/tomcat/tomcat-connectors/jk/binaries/win32/jk-1.2.28/isapi_redirect-1.2.28.dll.

Precisamos configurar o Jetty para disponibilizar uma porta para o protocolo AJP13 que será utilizado pelo nosso filtro ISAPI, para isso, extraia os arquivos do Jetty e edite o arquivo etc/jetty.xml adicionando as seguintes linhas:

  <Call name="addConnector">
    <Arg>
       <New class="org.mortbay.jetty.ajp.Ajp13SocketConnector">
         <Set name="port">8009</Set>
       </New>
    </Arg>
  </Call>

Com isso você tem o Jetty configurado para prover conexão entre os dois servidores. Configurações mais avançadas do Jetty podem ser encontradas aqui porém tomem cuidado pois este tutorial é antigo e a classe de conexão com Ajp13 já mudou de nome. Com o Jetty configurado vamos as configurações do IIS.

Para configurar o IIS siga os passos:

  1. Crie uma pasta, por exemplo C:\Jakarta e copie a dll isapi_redirect para dentro dela.
  2. Coloque na pasta também os arquivos workers.properties e uriworkermap.properties, siga as instruções abaixo para entender como criá-los.
  3. No console de configuração do IIS adicione uma pasta virtual com o nome Jakarta e aponte para a pasta criada acima, ao criar a pasta permita que a mesma execute processos.
  4. Adicione um filtro ISAPI na página default do IIS apontando pra o caminho da DLL.
  5. Crie um registro no Windows com o caminho HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector\1.0
  6. Crie os seguintes valores com suas respectivas chaves dentro deste caminho como descrito na imagem abaixo.
  7. Reinicie o IIS.

Para mais informações sobre como configurar o seu arquivo worker.properties veja este link.

workers.properties:
#Aqui podemos definir uma lista de workers, podemos então redirecionar as chamadas para mais de um servidor
worker.list=jetty
#Porta configurada dentro do jetty.xml acima
worker.jetty.port=8009
#Dominio em que se encontra o servidor Jetty
worker.jetty.host=localhost
#Tipo de protocolo disponibilizado no Jetty
worker.jetty.type=ajp13

uriworkermap.properites
#Aqui estamos dizendo ao filtro ISAPI para redirecionar o programa e todas suas chamadas para o worker Jetty.
/programa/*=jetty

Configure o seu registro do windows da seguinte forma:
Registro Windows

Tente agora acessar um aplicativo disponibilizado pelo Jetty na porta 8080 diretamente na porta 80 do IIS, se você configurou tudo corretamente você vai conseguir acessar de forma transparente o Jetty pelo IIS.

Bom galera, é isso. O post será rápido mesmo pois o tempo livre ainda está curto, essa configuração é a mais básica possível, recomendo ler a documentação do Apache a fundo para entender como configurar algo mais parrudo e realmente para produção, espero que o post seja útil e esteja claro o suficiente para ajudar alguém que venha a ter o mesmo problema que tive e que essa seja a solução esperada. Qualquer dúvida, sugestão ou crítica deixe um comentário ou envie um email. Um abraço.


Many-to-many com colunas extras, JPA e Hibernate

Bom galera, estou um pouco sem tempo pois estou trabalhando em um projeto pessoal portanto pretendo fazer um tópico rápido sobre um problema que enfrentei essa semana tentando mapear uma tabela ManytoMany com dados extras na terceira tabela.

Encontrei dificuldades em mapear as entidades devido ao loading dos objetos para serializa-los, o que fazer quando um objeto fosse deletado ou atualizado entre outros problemas comuns na hora de se desenvolver aplicações que acessam dados em banco.

Procurando na internet e encontrei diversas pessoas utilizando Eager Loading nas entidades com relacionamento devido ao fato dos atributos da entidade já virem carregador assim que procuramos ele no banco. E isso é um problema, imagine retornar um objeto que faz referência a outros objetos que por final faz referência ao mesmo objeto e temos ai um Stackoverflow (percebeu o loop infinito?). É importante saber quando e porque utilizar Eager Loading pois pode ser um problema que você acaba levando tempo para descobrir(aconteceu comigo). Para solucionar o Eager Loading temos a forma mais preguiçosa de acessar dados em banco, Lazy Loading, onde buscamos pela referência ao objeto apenas quando for necessário e não sempre como acontece com Eager. Outro problema que econtrei foi como fazer para atualizar ou remover as referências às entidades relacionadas entre si quando uma ou outra for atualizada/deletada? Precisamos então ter uma regra bem definida de cascade para evitar que dados fiquem perdidos.

Imagine uma entidade de Alunos que podem assistir diversas Aulas e claro aulas que contém diversos Alunos, até ai tudo bem, porém além das informações básicas é preciso que o Aluno consiga deixar um comentário sobre a Aula que ele assistiu e que seja amarzenado também a nota que aquele determinado aluno obteve. Sempre que descrevemos um relacionamento N:M entre duas tabelas em banco de dados é necessário criar uma terceira tabela contendo os Ids de cada tabela relacionada e caso necessário adicionar campos nessa terceira tabela e é necessa terceira tabela que devemos colocar o comentário e a nota do aluno que fez determinada aula, devemos então mapear além das tabelas Aluno e Aula, também a tabela de AulasParaAlunos para especificar os dados extras que temos nessa terceira tabela.

Vamos então às nossas entidades:

import java.util.LinkedList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

@Entity
@Table(name = "tb_aluno")
public class Aluno {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "idPessoa")
 private Long idAluno;

 @Column(name = "nome")
 private String nome;

 @Column(name = "endereco")
 private String endereco;

 @Column(name = "nascimento")
 @Temporal(TemporalType.TIMESTAMP)
 private String dataNascimento;

 //Aqui estamos dizendo como a entidade deve ser carregada
 //e o que deve acontecer caso alguma coisa seja deletada ou atualizada.
 @OneToMany(fetch = FetchType.LAZY,
 mappedBy = "pk.aluno",
 cascade = { CascadeType.PERSIST, CascadeType.MERGE })
 @Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE,
 org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
 private List<AulasParaAlunos> aulasParaAlunos = new LinkedList<>();

}
import java.util.LinkedList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

@Entity
@Table(name = "tb_aula")
public class Aula {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "idAula")
 private Long idAula;
 @Column(name = "nome")
 private String nome;
 @Column(name = "entrada")
 private String entrada;
 @Column(name = "correcao")
 private String correcao;
 @Column(name = "tempoNecessariO")
 private Integer tempoNecessario;

 @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.aula",
 cascade = { CascadeType.PERSIST, CascadeType.MERGE })
 @Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE,
 org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
 private List<AulasParaAlunos> aulasParaAlunos = new LinkedList<AulasParaAlunos>();
}

Veja que o mapeamento das duas entidades é bem parecido, estamos fazendo referencia a uma terceira entidade que é AulaParaAlunos e é nela que dizemos os campos adicionais, a Id da tabela(que nesse caso é composta) e algumas regras adicionais para ter o relacioamento correto entre elas.

import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name = "aulas_para_alunos")
@AssociationOverrides(
 { @AssociationOverride(name = "pk.aluno", joinColumns = @JoinColumn(name = "idAluno")),
 @AssociationOverride(name = "pk.aula", joinColumns = @JoinColumn(name = "idAula")) })
public class AulasParaAlunos {

 @EmbeddedId
 private AulasParaAlunosPK pk = new AulasParaAlunosPK();
 @Column(name = "comentario")
 private String comentario;
 @Column(name = "nota")
 private Integer nota;

 public boolean equals(Object o) {
 if (this == o)
 return true;
 if (o == null || getClass() != o.getClass())
 return false;

 AulasParaAlunos that = (AulasParaAlunos) o;

 if (getPk() != null ? !getPk().equals(that.getPk()) : that.getPk() != null)
 return false;

 return true;
 }

 @Transient
 public Aluno getAluno() {
 return this.getPk().getAluno();
 }

 @Transient
 public Aula getAula() {
 return this.getPk().getAula();
 }

 public int hashCode() {
 return (getPk() != null ? getPk().hashCode() : 0);
 }
}

Essa tabela possui uma Id composta pelo Id do Aluno e uma Id da Aula, descrita pela classe AulasParaAlunosPK:

import java.io.Serializable;

import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;

@Embeddable
public class AulasParaAlunosPK implements Serializable {

 private static final long serialVersionUID = -5869094934725857817L;
 @ManyToOne(fetch = FetchType.LAZY, optional = false)
 private Aluno aluno;
 @ManyToOne(fetch = FetchType.LAZY, optional = false)
 private Aula aula;

 // Precisamos implementar corretamente o equals() na classe de Id composta.
 public boolean equals(Object o) {
 if (this == o)
 return true;
 if (o == null || getClass() != o.getClass())
 return false;
 if (!(o instanceof AulasParaAlunosPK))
 return false;

 AulasParaAlunosPK that = (AulasParaAlunosPK) o;

 if (this.aluno != null ? !this.aluno.equals(that.aluno) : that.aluno != null)
 return false;
 if (this.aula != null ? !this.aula.equals(that.aula) : that.aula != null)
 return false;

 return true;
 }

 public int hashCode() {
 int result;
 result = (this.aluno != null ? this.aluno.hashCode() : 0);
 result = 31 * result + (this.aula != null ? this.aula.hashCode() : 0);
 return result;
 }
}

Agora temos o mapeamento correto com lazy loading e cascading respeitando regras de negocio. Todos os objetos serão carregados apenas quando dermos um GET em cada variável e caso alguma coisa seja deletada ou atualizada o Hibernate vai saber o que fazer. Bom, tutorial rápido pois estou sem tempo, semana que vem tento parar para escrever algo mais bacana, espero conseguir abordar o SpringSecurity. Dúvidas por favor deixem recados.


Integrando Hibernate3 ao Spring e DAO Generico

Bom, galera como prometido neste post irei continuar o tópico anterior adicionando o suporte ao Hibernate em nosso WebService. O Hibernate é um Framework podereso de mapeamento objeto-relacional muito conhecido e utilizado, não pretendo entrar em detalhes sobre como configurar o Hibernate nem como mapear seus objetos para acessar o banco, porém, caso tenha dúvidas acesse este tutorial que mostra como tirar proveito deste freamework e caso tenha alguma dúvida pode deixe um comentário ou enviar um email que tentarei ajudar.

O Spring possui um módulo para acesso a banco de dados extremamente robusto, podemos  fazer controle de transações via AOP, cache, gerenciar pool de conexões diretamente no contexto do Spring sem se preocupar com qual tecnologia que está implementada e caso seja necessário podemos trocar o modo de acesso sem precisar modificar todo o projeto, nos dando assim maior mobilidade e escalabilidade. O Hibernate porém não é o único framework que o Spring provê integração, o Spring se integra facilmente a diversos frameworks de persistência como JPA, iBATIS, JDBC e proprio Hibernate que iremos demonstrar neste tutorial. Mostrarei também como desenvolver um DAO generico para o Hibernate que nos da acesso as principais funcões básicas de um CRUD em apenas uma interface, evitando repetição de código. Modificaremos um pouco o projeto anterior para poder demonstrar todas as funcionalidades dessa integração. Primeiramente precisamos modificar nosso POM para baixar todas as dependências necessárias:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.dchohfi</groupId>
	<artifactId>ServiceSample</artifactId>
	<packaging>war</packaging>
	<version>2.0</version>
	<developers>
		<developer>
			<id>dchohfi</id>
			<name>Diego Chohfi</name>
			<email>diegochohfi@hotmail.com</email>
			<url>http://dchohfi.wordpress.com</url>
		</developer>
	</developers>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<configuration>
					<scanIntervalSeconds>10</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>

	</build>
	<dependencies>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-bundle</artifactId>
			<version>2.2.3</version>
			<exclusions>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-beans</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-context</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-core</artifactId>
				</exclusion>
				<exclusion>
					<artifactId>asm</artifactId>
					<groupId>asm</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>xerces</groupId>
			<artifactId>xercesImpl</artifactId>
			<version>2.9.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring</artifactId>
			<version>2.5.6</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate</artifactId>
			<version>3.2.6.ga</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-annotations</artifactId>
			<version>3.3.0.ga</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-commons-annotations
			</artifactId>
			<version>3.3.0.ga</version>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.8</version>
		</dependency>
	</dependencies>
</project>

Verifique que além das dependências do Spring e do Apache CXF, temos também as dependências do Hibernate. Note que adicionei como dependência o commons-dbcp da Apache, iremos utiliza-lo para gerenciar nosso pool de conexões ao banco de dados, o Hibernate por default vem com o c3p0 para gerenciar o pool de conexões, porém eu prefiro o commons-dbcp pois já tive problemas com o c3p0 portanto recomendo a vocês utilizarem. O bacana de se utilizar o commons-dbcp é que podemos ter maior controle sobre como as conexões são abertas e fechadas no Hibernate e também evitar que conexões fiquem abertas sem serem utilizadas. Vamos ao arquivo de configuração do Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
	xsi:schemaLocation="

http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-2.5.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-2.5.xsd


http://cxf.apache.org/core


http://cxf.apache.org/schemas/core.xsd


http://cxf.apache.org/jaxws


http://cxf.apache.org/schemas/jaxws.xsd


http://cxf.apache.org/jaxrs


http://cxf.apache.org/schemas/jaxrs.xsd">

	<!-- Carrega as configurações presentes nos jars do Apache CXF -->
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<!-- Endpoint WSDL para o Apache CXF -->
	<!-- Dizemos o endereço, o ID do serviço, em qual bean ele depende -->
	<jaxws:endpoint id="service" depends-on="serviceImpl"
		implementor="#serviceImpl" address="/service">
	</jaxws:endpoint>

	<!-- Mapeamento do REST para o Apache CXF -->
	<jaxrs:server id="myService" address="/rest">
		<jaxrs:serviceBeans>
			<ref bean="serviceImpl" />
		</jaxrs:serviceBeans>
		<!-- Aqui declaramos o nosso Mapper para a Exception -->
		<jaxrs:providers>
			<ref bean="ClienteExceptionMapper" />
		</jaxrs:providers>
	</jaxrs:server>

	<!-- Referencia ao bean Mapper da nossa Exception -->
	<bean id="ClienteExceptionMapper" class="com.wordpress.dchohfi.exception.ClienteExceptionMapper" />

	<!-- Bean que implementa o endpoint do nosso webservice -->
	<bean id="serviceImpl" class="com.wordpress.dchohfi.service.ServiceImpl">
		<property name="clienteDAO" ref="clienteHibernateDao" />
	</bean>

	<!-- Bean abstrato para evitar repetição de código -->
	<bean id="baseSessionFactory" abstract="true">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<!-- Implementacao da interface ClienteDAO -->
	<bean id="clienteHibernateDao"
		class="com.wordpress.dchohfi.model.dao.hibernate.ClienteHibernateDAO"
		parent="baseSessionFactory" />

	<!-- Carregar arquivos de configuracao -->
	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:/jdbc.properties</value>
			</list>
		</property>
	</bean>

	<!--
		Bean com as configuracoes de conexao com o banco, estamos utilizando o
		CommonsDBCP da Apache para gerenciar nosso pool de conexão
	-->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="${database.driver}" />
		<property name="url" value="${database.url}" />
		<property name="username" value="${database.user}" />
		<property name="password" value="${database.pass}" />
		<property name="initialSize" value="${database.initConnections}" />
		<property name="maxActive" value="${database.maxActive}" />
		<property name="maxIdle" value="${database.maxIdle}" />
		<property name="removeAbandoned" value="${database.removeAbandoned}" />
	</bean>

	<!-- Configuracoes de uma fabrica de sessões -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource">
			<ref local="dataSource" />
		</property>
		<property name="annotatedClasses">
			<list>
				<value>com.wordpress.dchohfi.model.entity.Cliente
				</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLInnoDBDialect
				</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">false</prop>
			</props>
		</property>
	</bean>
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory" />
		</property>
	</bean>
	<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

Leia atentamente os comentários feitos no decorrer do arquivo pois desta vez temos uma configuração mais avançada do Spring, estamos utilizando diversos recursos disponíveis no framework, um deles é o PropertyPlaceholderConfigurer onde podemos montar um arquivo .properties fora do nosso contexto do Spring e colocar diversas configurações importantes lá, gosto deste método pois podemos colocar em uma pasta separada, por exemplo, os dados de conexão com o banco, assim qualquer pessoa pode modificar os dados de configuração se for necessário, sem precisar entender o arquivo de configuração do Spring.

jdbc.properties:
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/test
database.user=root
database.pass=serviceSample
database.initConnections=1
database.maxActive=10
database.maxIdle=5
database.removeAbandoned=true

A integração do Spring ao Hibernate3 começa quando configuramos nosso sessionFactory, observe que utilizamos a classe AnnotationSessionFactoryBean, aqui utilizamos um dataSource configurado previamente, dizemos também ao Spring quais são as entidades anotadas com @Entity entre outras diversas configurações disponíveis. Nós criamos então um Bean abstrato dentro do contexto do Spring para evitar repetição de código, nosso Bean clienteHibernateDao precisa então apenas herdar nosso bean abstrato para obter a sessionFactory, simples não? No final do arquivo temos também um bean de controle de transações, não pretendo aprofundar nesse assunto pois criarei outro tutorial abordando apenas controle de transações no Spring pois é um assunto muito bacana. Vamos ao nosso DAO generico integrado ao Spring.

Precisamos primeiramente de uma interface contendo as operações basicas de um CRUD(criar, ler, atualizar e deletar):

package com.wordpress.dchohfi.model.dao;

public interface GenericDAO<T, ID extends Serializable> {
    T findById(ID id);
    List<T> listAll();
    T save(T entity);
    void delete(T entity);
}

Agora vem a “mágica” da integração do Hibernate ao Spring e o DAO generico:

package com.wordpress.dchohfi.model.dao.hibernate;

//Adicionando herança ao HibernateDaoSupport temos diversas funcionalidades do Spring
//junto ao Hibernate, implementamos também nosso DAO generico para ter mobilidade.
public class HibernateDAOGenerico<T, ID extends Serializable> extends
	HibernateDaoSupport implements GenericDAO<T, ID> {

    private static Log LOG = LogFactory.getLog(HibernateDAOGenerico.class);

    // Nosso construtor vai setar automaticamente via Reflection qual classe
    // estamos tratando.
    @SuppressWarnings("unchecked")
    public HibernateDAOGenerico() {
	this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
    }
    
    // Classe que será persistida.
    private Class<T> persistentClass;
    public Class<T> getPersistentClass() {
	return this.persistentClass;
    }
    
    @Override
    public void delete(T entity) {
	try {
	    this.getHibernateTemplate().delete(entity);
	} catch (final HibernateException ex) {
	    HibernateDAOGenerico.LOG.error(ex);
	    throw convertHibernateAccessException(ex);
	}
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public T findById(ID id) {
	try {
	    return (T) this.getHibernateTemplate().get(getPersistentClass(), id);
	} catch (final HibernateException ex) {
	    HibernateDAOGenerico.LOG.error(ex);
	    throw convertHibernateAccessException(ex);
	}
    }
    
    @Override
    public List<T> listAll() {
	try {
	    return this.getHibernateTemplate().loadAll(getPersistentClass());
	} catch (final HibernateException ex) {
	    HibernateDAOGenerico.LOG.error(ex);
	    throw convertHibernateAccessException(ex);
	}
    }
    
    @Override
    public T save(T entity) {
	try {
	    this.getHibernateTemplate().save(entity);
	    return entity;
	} catch (final HibernateException ex) {
	    HibernateDAOGenerico.LOG.error(ex);
	    throw convertHibernateAccessException(ex);
	}
    }
    
    protected List<T> findByCriteria(Criterion... criterion) {
	try {
	    Criteria crit = this.getHibernateTemplate()
	    			.getSessionFactory()
	    			.getCurrentSession()
	    			.createCriteria(getPersistentClass());
	    for (Criterion c : criterion) {
		crit.add(c);
	    }
	    return crit.list();
	} catch (final HibernateException ex) {
	    HibernateDAOGenerico.LOG.error(ex);
	    throw convertHibernateAccessException(ex);
	}
    }
}

O nosso DAO generico não precisa saber qual classe ele está relacionando, ele precisa apenas fazer as operações básicas de um CRUD. Porém com o suporte ao Hibernate temos algo a mais além das operações básicas. Verifique que o método findByCriteria pode receber ou não um Array de Criterion onde podemos fazer buscas mais específicas no banco de dados.

package com.wordpress.dchohfi.model.dao.hibernate;

public class ClienteHibernateDAO extends HibernateDAOGenerico<Cliente, Long>
	implements ClienteDAO {

    @Override
    public List<Cliente> findClietsWithPhone() {
	return findByCriteria(Expression.isNotNull("telefone"), Expression.ne("telefone", ""));
    }
}

Nossa implementação de ClienteHibernateDao consegue consultar por exemplo todos os Clientes que possuem telefone, dessa forma você consegue ter um DAO generico para as operações básicas, mas também tem a opção de fazer buscas mais avançadas via Criteria. Agora você pode ter diversos DAO’s utilizando apenas uma classe, acredite em mim, você da pra economizar muito tempo com isso. O Spring também nos da suporte para tratar as excecões lançadas pelo Hibernate, fica mais fácil saber o que aconteceu e porque aconteceu e com o controle de transações você não precisa se preocupar em dar rollback caso algum problema aconteça

Veja também que nossa interface de acesso ao serivço não mudou muito:

package com.wordpress.dchohfi.service;

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@WebService(endpointInterface = "com.wordpress.dchohfi.service.Service")
public class ServiceImpl implements Service {

    private ClienteDAO clienteDao;

    @Override
    public Cliente getCliente(Long id) throws ClienteException {
	Cliente cliente = this.clienteDao.findById(id);
	if (cliente == null)
	    throw new ClienteException("Nenhum usuario com id" + id + " encontrado!");
	return cliente;
    }

    @Override
    public Collection<Cliente> getClientes() {
	return this.clienteDao.listAll();
    }

    @Override
    public Collection<Cliente> getClientesWithPhone() {
	return this.clienteDao.findClietsWithPhone();
    }

    @Override
    public Cliente saveCliente(Cliente cliente) {
	return this.clienteDao.save(cliente);
    }

    public void setClienteDAO(ClienteDAO clienteDao) {
	this.clienteDao = clienteDao;
    }
}

Note como temos todas as operações de acesso a dados a partir da assunatura da interface, nós não sabemos nem precisamos saber o que acontece. Não se preocupe com a anotação de @Transactional pois irei explicar em outro post, mas aguarde que o Spring vai se tornar ainda mais interessante.

É importante entender que o nosso projeto ainda esta funcionando a partir de interfaces, você por exemplo não é obrigado ao utilizar o Hibernate como framework de acesso a dados, utilizando a interface generica você pode ter acesso via JDBC normal caso você ache necessário, modificando apenas a parte de acesso ao dado.

Acho que isso é tudo, temos agora mais uma demonstração de quanto o Spring Framework é poderoso, integrando ele a outros frameworks podersos temos uma forma fácil e rápida de se implementar  aplicações robustas. Utilizei o MySQL para banco de dados e caso você tenha problema com a lib da Sun você precisa fazer o download dela diretamente no site para aceitar os termos. Qualquer outra dúvida deixe um comentário ou envie um email.

Obrigado e espero que seja util!


WebService WSDL e RESTful com Java

Neste post demonstrarei como criar um WebService WSDL e RESTful na mesma aplicação de modo simplificado, utilizando Spring e Apache CXF, que na nova versão traz suporte total para JSR 311 de forma clara e via annotations. Usaremos também o Maven2 para gerenciar nosso projeto, assumo que você já tem prévio conhecimento, recomendo este tutorial para entender o Maven2 e também como configura-lo. Também presumo que você tenha conhecimento sobre os dois protocolos, porém, caso queira aprender mais sobre leia: WSDL e RESTful.

Bom, cabe a você também decidir qual tipo de serviço sua aplicação deve prover, tanto o WSDL como o RESTful tem suas vantágens e desvantágens e é necessário saber qual delas prover para seu cliente e se preciso prover as duas soluções, com o Apache CXF isso é possível, cheguei a essa conclusão pois a pouco tempo, participei de um projeto onde inicialmente províamos acesso a um WebService WSDL para o cliente, porém foi necessário disponibilizar também o acesso ao RESTful e como utilizávamos o Apache CXF não tivemos que mudar nada no projeto e nenhúm cliente foi afetado, pois a interface do projeto continuava a mesma.

Bom, vamos ao nosso projeto.

Primeiro precisamos mapear nossas dependências no Maven2, segue o pom do nosso projeto:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wordpress.dchohfi</groupId>
	<artifactId>ServiceSample</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<developers>
		<developer>
			<id>dchohfi</id>
			<name>Diego Chohfi</name>
			<email>diegochohfi@hotmail.com</email>
			<url>http://dchohfi.wordpress.com</url>
		</developer>
	</developers>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<configuration>
					<scanIntervalSeconds>10</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-bundle</artifactId>
			<version>2.2.3</version>
			<exclusions>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-beans</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-context</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>2.5.6</version>
		</dependency>
	</dependencies>
</project>

O Apache CXF utiliza dentro dele o Spring como dependência, removemos o mesmo para utilizar uma versão atualizada do SpringFramework. Adicionei ao nosso POM também o Plugin do Jetty que iremos utilizar para debugar nossa aplicação dentro do Eclipse. Utilizei também um plugin para compilar nosso projeto o Maven Compiler Plugin dizendo qual versão do Java estamos utilizando e qual o encoding do nosso projeto (não esqueça de mudar o encoding no seu projeto do Eclipse para UTF-8). Agora nossa dependências estão prontas e nosso projeto configurado.

Nosso projeto possui uma interface seguindo o padrão de projeto Façade, essa interface será exposta como nosso WebService, de maneira simplificada, o Façade torna o projeto mais fácil de entender e usar.

Interface:

package com.wordpress.dchohfi.service;

import com.wordpress.dchohfi.exception.ClienteException;
import com.wordpress.dchohfi.model.entity.Cliente;

@Path("/")//caminho onde o serviço REST fica disponibilizado, seguido pelo @Path de cada parametro
@Produces({"application/xml", "application/json"})//tipos de retorno que o nosso REST pode produzir
@WebService//definimos aqui que essa interface é um WebService WSDL
public interface Service {

    @GET//tipo do metodo REST
    @Path("/cliente")//caminho do método
    @WebMethod(operationName="getClientes")//nome do metodo no WSDL
    public Collection<Cliente> getClientes();
    
    @GET
    @Path("/cliente/{id}/")//aqui passamos uma ID para obter um Cliente especifico
    @WebMethod(operationName="getCliente")
    //o ID do cliente é mapeado pelo PathParam para o REST e recebe o nome dado pelo WebParam para o WSDL.
    public Cliente getCliente(@PathParam("id") @WebParam(name="id")int id) throws ClienteException;
}

Verifique como é simples desenvolver uma interface para disponibilizar acesso tanto para RESTful como para WSDL, utilizamos annotations para dizer ao CXF qual o tipo de retorno que o nosso WebService vai prover tanto para o RESTful quanto para o WSDL, no mesmo código! Na implementação da nossa interface não iremos precisar de praticamente nenhuma configuração adicional para o Apache CXF saber o que deve fazer.

Implementação:

package com.wordpress.dchohfi.service;

@WebService(endpointInterface = "com.wordpress.dchohfi.service.Service")
public class ServiceImpl implements Service {

	private ClienteDAO clienteDao;
	
	@Override
	public Cliente getCliente(int id) throws ClienteException {
		return clienteDao.getCliente(id);
	}

	@Override
	public Collection<Cliente> getClientes() {
		return clienteDao.getClientes();
	}

	public void setClienteDAO(ClienteDAO clienteDao) {
		this.clienteDao = clienteDao;
	}

	public ClienteDAO getClienteDao() {
		return clienteDao;
	}
}

Veja que tivemos apenas uma anotação indicando ao Apache CXF qual o Endpoint que ele deve procurar para fazer o mapeamento do XML para o WSDL. Criei também uma interface de ClienteDAO, faremos uma implementação simples do DAO, mas você pode integrá-lo ao Hibernate, por exemplo, ou qualquer outra forma para obter acesso ao seus dados, até mesmo outro WebService. É sempre bom trabalhar com interfaces para garantir o baixo acoplamento das suas classes garantindo uma escalabilidade maior, você não precisa saber o que a classe faz em si, deste que você tenha a assinatura dela.

Implementação Simples do ClienteDAO:

package com.wordpress.dchohfi.model.dao.simple;
//você pode implementar a classe ClienteDAO da forma que desejar
public class ClienteSimpleDAO implements ClienteDAO {

	private Collection<Cliente> clientes;
	
	@Override
	public Cliente getCliente(int id) throws ClienteException {
		for (Cliente cliente : clientes) {
			if(cliente.getId() == id)
				return cliente;
		}
		throw new ClienteException("Cliente com id "+id+" não encontrado!");
	}

	@Override
	public Collection<Cliente> getClientes() {
		return this.clientes;
	}

	public void setClientes(Collection<Cliente> clientes) {
		this.clientes = clientes;
	}
}

Verifique que na implementação lançamos uma Exception do tipo ClienteException caso nenhúm usuário seja encontrado com aquela ID, o Apache CXF ainda não consegue mapear para o REST a exception lançada e responder adequadamente a requisição ao usuário, precisamos então ter um ExceptionMapper para dizer ao Apache CXF o que fazer caso aquela Exception seja lançada.

ClienteExceptionMapper:

package com.wordpress.dchohfi.exception;

public class ClienteExceptionMapper implements ExceptionMapper<ClienteException>{

	@Override
	public Response toResponse(ClienteException arg0) {
		return Response.status(Status.BAD_REQUEST).build();
	}
}

Verifique como é simples, você pode dizer qual o Status do Response gerado pelo método, ainda é possível colocar uma mensagem para mostrar para o usuário o que aconteceu com a requisição que ele fez. Agora precisamos configurar o Spring juntando tudo o que fizemos, considere isso como um pequeno tutorial desse incrivel framework. Leia os comentários dentro do XML e qualquer dúvida poste um comentário e caso seja necessário farei um breve tutorial sobre Spring.

Configuração do Spring:

<?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:context="http://www.springframework.org/schema/context"
	xmlns:cxf="http://cxf.apache.org/core" 
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-2.5.xsd


http://cxf.apache.org/core


http://cxf.apache.org/schemas/core.xsd


http://cxf.apache.org/jaxws


http://cxf.apache.org/schemas/jaxws.xsd


http://cxf.apache.org/jaxrs


http://cxf.apache.org/schemas/jaxrs.xsd">

	<!-- Carrega as configurações presentes nos jars do Apache CXF -->
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
	
	<!-- Endpoint WSDL para o Apache CXF -->
	<!-- Dizemos o endereço, o ID do serviço, em qual bean ele depende -->
	<jaxws:endpoint id="service" depends-on="serviceImpl"
		implementor="#serviceImpl" address="/service" />

	<!-- Mapeamento do REST para o Apache CXF -->
	<jaxrs:server id="myService" address="/rest">
		<jaxrs:serviceBeans>
			<ref bean="serviceImpl" />
		</jaxrs:serviceBeans>
		<!-- Aqui declaramos o nosso Mapper para a Exception -->
		<jaxrs:providers>
			<ref bean="ClienteExceptionMapper" />
		</jaxrs:providers>
	</jaxrs:server>
	
	<!-- Referencia ao bean Mapper da nossa Exception -->
    <bean id="ClienteExceptionMapper" class="com.wordpress.dchohfi.exception.ClienteExceptionMapper"/>

	<!-- Bean que implementa o endpoint do nosso webservice -->
	<bean id="serviceImpl" class="com.wordpress.dchohfi.service.ServiceImpl">
		<property name="clienteDao" ref="clienteDAO" />
	</bean>
	
	<!-- Bean criado apenas para demonstrar um simples DAO -->
	<bean id="clienteDAO"
		class="com.wordpress.dchohfi.model.dao.simple.ClienteSimpleDAO">
		<property name="clientes">
			<list>
				<ref bean="cliente1" />
				<ref bean="cliente2" />
				<ref bean="cliente3" />
			</list>
		</property>
	</bean>
	
	<!-- Cria 3 clientes simples com algumas informações para demonstração -->
	<bean id="cliente1" class="com.wordpress.dchohfi.model.entity.Cliente">
		<property name="id" value="1" />
		<property name="nome" value="Diego Chohfi" />
		<property name="endereco" value="Rua Fagundes Varela" />
	</bean>
	<bean id="cliente2" class="com.wordpress.dchohfi.model.entity.Cliente">
		<property name="id" value="2" />
		<property name="nome" value="Cliente 2" />
		<property name="endereco" value="Rua Cliente 2" />
	</bean>
	<bean id="cliente3" class="com.wordpress.dchohfi.model.entity.Cliente">
		<property name="id" value="3" />
		<property name="nome" value="Cliente 3" />
		<property name="endereco" value="Rua Cliente 3" />
	</bean>
</beans>

Considero que a configuração feita em XML é pequena visto o tamanho da aplicação que temos em tão pouco tempo, normalmente a configuração do Spring é bem extensa em projetos de grande porte, então quem já trabalha com Spring não terá problema para entender o contexto. Agora precisamos configurar o servlet dentro do web.xml para nossa aplicação.

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="services" version="2.5">

	<context-param>
		<param-name>webAppRootKey</param-name>
		<param-value>cxf.rest.example.root</param-value>
	</context-param>
	
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/applicationContext.xml</param-value>
	</context-param>
	
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<servlet>
		<servlet-name>CXFServlet</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

Bom, aqui estamos dizendo ao Spring o que fazer com a nossa aplicação e estamos carregando o Apache CXF para rodar dentro de um servidor de aplicações escolhido por você, aqui você pode ficar tranquilo pois a configuração é padrão.

Neste momento temos nossa aplicação funcionando e pronta para ser colocada para rodar.  Lembra do plugin do Jetty dentro do POM do Maven2? Iremos utilizá-lo agora. Demonstrarei rapidamente como fazer as configurações para debugar seu projeto dentro do Eclipse.

Faça as seguintes configurações, dentro de Run Configurations no Eclipse crie um novo Java Application e siga as instruções como nas imagens:

Acredito que com isso você consiga subir seu projeto dentro do Eclipse e debugar sem problemas sua aplicação, você poderá consumir sua aplicação REST a partir do link http://localhost:8080/ServiceSample/rest/cliente/1/ por exemplo, deixo como dica utilizar um complemento para o FireFox o RESTClient, onde você pode consumir aplicações RESTful sem problema e analizar com clareza os resultados obtidos e para consumir WebServices WSDL recomendo o soapUI que possui diversas funcionalidades para testar e sobrecarregar seu servidor e verificar o número de acessos simultâneos que ele aguenta.

É importante ressaltar que você pode escolher se deseja prover acesso aos dois WebServices dentro da aplicação para o cliente, cabe a você decidir o que é melhor.

Bom, aqui acaba nosso tutorial que acabou ficando maior do que eu esperava, tomara que eu não tenha me perdido em alguma parte e caso algo tenha ficado confuso ou passado desapercebido deixe um comentário que tentarei esclarecer.

Espero que quem leia goste do material e por favor, comente, é importante. Quero continuar este pequeno projeto adicionando funcionalidades como integração do Spring com o Hiberante entre outras coisas, para realmente ter um tutorial completo para uma aplicação robusta.

Qualquer dúvida ou sugestão, novamente, deixe um comentário. Obrigado!


IServerContext, criando objetos no contexto do Servidor. ArcGis Server.

Fala galera, neste post espero dar continuidade ao assunto tratado no post anterior. Falamos anteriormente como se obter uma conexão tanto com ArcGis Destkop como ArcGis Server e algumas diferenças básicas entre as duas maneiras, espero que tenha ficado claro e caso alguma dúvida fique no ar por favor deixe um comentário que envio um email tentando ajudar.

Bom, uma das principais diferenças quando trabalhamos com ArcGis Server é a maneira como tratamos os objetos. Você precisa pensar que agora você está em outro contexto, estamos agora trabalhando no servidor. Aplicações com acesso ao servidor não podem mais criar objetos locais, todos os objetos agora devem ser criados com o IServerContext, utilizando o método createObject, quando esse método é chamado você está recebendo um proxy do objeto criado no servidor, podendo utilizá-lo localmente. Vamos a um exemplo:

try{
	//Lembra da classe Connector do post anterior?
	IServerContext context = connector.getServerContext();
	IPoint ponto = (IPoint) context.createObject(Point.getClsid());
	//..
	//processo
	//..
	Cleaner.release(ponto);//Irei explicar o porque de se utilizar logo abaixo
}finally{
	//Lembre de fechar a conexão com o servidor.
        context.releaseContext();
	connector.close();
}

A classe Point possui o método getClsid que retorna uma String para que o contexto do servidor possa criar um proxy para que possamos trabalhar com o ponto localmente. Todos os objetos possuem este método e devem ser chamados, não devemos misturar objetos locais com objetos de servidor. Também precisamos tomar cuidado caso mais de um servidor esteja sendo utilizado no projeto, pois cada servidor tem um contexto e pode ser necessário copiar um objeto de um servidor para outro para não ter problema e assim o trabalho vai se tornando cada vez mais complexo.

Note também que utilizei um objeto Cleanner para liberar a referência ao acesso para aquele objeto, caso isso não seja feito você provavelmente terá estouro de memória após um longo tempo de processamento(digo pois já tive e ainda tenho problema com estouro de memória trabalhando com ESRI e Java). Normalmente o acesso ao objeto é liberado quando o objeto é coletado pelo GC, mas eu prefiro liberar o acesso ao objeto manualmente e a própria ESRI aconselha que seja feito este processo toda vez que deixamos de fazer referência a um proxy.

Chega por hoje, espero que o post seja util e acredito que tenha sido direto ao assunto. Flw e até o próximo!


Conexão SDE x Conexão ArcGis Server, Java e ESRI

Primerio post teorico irei tratar de um assunto que vivenciamos a pouco tempo aqui na empresa. Trabalhamos com plataforma ESRI e a forma de conexão sempre foi o passo incial para um projeto, mas mudar essa forma de conexão depois que o projeto já estava sólido também foi uma decisão difícil de se tomar.

Nós tinhamos um projeto, webservice – wsdl em Java, focado apenas ao público interno da empresa. Incialmente tudo funcionava perfeitamente, não tinhamos problemas com performance nem acessos concorrentes ao programa, porém o projeto virou um produto interessante e a empresa resolveu vender esse acesso e disponibilizar o webservice para seus clientes, o escopo então mudou, já não eram poucos acessos e então vieram os problemas de performance, estouro de memória da JVM, concorrência de dados entre outros problemas que diversas pessoas já devem ter passado ao se migrar de uma aplicação de pequeno porte para uma de grande porte. Estava realmente na hora de migrar nossa aplicação ArcGis Desktop para o ArcGis Server, que como o nome diz é destinado para esse tipo de serviço.

Irei mostrar dois exemplos práticos de como se obter cada tipo de conexão, cabe a você decidir qual o escopo do seu projeto e qual tipo implementar. Os exemplos utilizam API ArcObjects e para se utilizada você precisa ter instalado o ArcGis destop na sua máquina para obter a licença para uso.

Acesso ao ArcGis Desktop:

Connector.java

//Classe de controle de acesso as conexões do SDE
public class Connector{

    //Classe criada para obter as informações de conexão, setadas previamente por um framework de DI ou manuamente passada via get/set
    private Proriedades propriedades;

    //Classes setadas previamente via framework DI ou passadas via get/set
    private IWorkspaceFactory workspaceFactory;
    private IFeatureWorkspace featureWorkspace;
    private IPropertySet propertySet;

    //Contrutor da classe, faz a validação da licença disponível na máquina
    public SDEConnector(){
        try {
        	EngineInitializer.initializeEngine();
            com.esri.arcgis.system.AoInitialize ao = new com.esri.arcgis.system.AoInitialize();
            if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeEngine) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
                ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeEngine);
            } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcView) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
                ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcView);
            } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcEditor) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
                ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcEditor);
            } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcInfo) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
                ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //Abre uma conexão com o SDE
    public void open() throws AutomationException, IOException{
        propertySet.setProperty("server", this.propriedades.getServer());
        propertySet.setProperty("instance", this.propriedades.getInstance());
        propertySet.setProperty("user", this.propriedades.getUser());
        propertySet.setProperty("password", this.propriedades.getPassword());
        propertySet.setProperty("version", this.propriedades.getVersion());
        this.featureWorkspace = (IFeatureWorkspace) workspaceFactory.open(propertySet, 0);
    }

    //Fecha a conexão
    public void close(){
    	Cleaner.release(this.featureWorkspace);
    }
    //Retorna o acesso a uma FeatureClass
    public IFeatureClass getFeature(String featureName) throws AutomationException, IOException{
    	return this.getFeatureWorkspace().openFeatureClass(featureName);
    }

Com essa classe podemos obter uma conexão com o SDE e fecha-la quando desejarmos, podemos também ter acesso a uma IFeatureClass para editar, visualizar e listar as Features da tabela além de outras ferramentas úteis para controle dos dados geocoreferenciados.

Acesso ao ArcGis Server:

Connector.java

public class Connector {

    private static Log LOG = LogFactory.getLog(Connector.class);
    private IServerContext context;
    private Properties propriedades;

    public Connector() {
	try {
	    EngineInitializer.initializeEngine();
	    AoInitialize ao = new AoInitialize();
	    if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeEngine) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
		ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeEngine);
	    } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcView) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
		ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcView);
	    } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcEditor) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
		ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcEditor);
	    } else if (ao.isProductCodeAvailable(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcInfo) == com.esri.arcgis.system.esriLicenseStatus.esriLicenseAvailable) {
		ao.initialize(com.esri.arcgis.system.esriLicenseProductCode.esriLicenseProductCodeArcInfo);
	    }
	} catch (Exception e) {
	    Connector.LOG.error(e.getMessage(), e);
	}
    }

    public IServerContext getServerContext(){
	return this.context;
    }

    public IFeatureClass getFeatureClass(int layer) {
	MapServer mapServer = (MapServer) context.getServerObject();
	IMap map = mapServer.getMap(mapServer.getDefaultMapName());
	IFeatureLayer fLayer = (IFeatureLayer) map.getLayer(layer);
	return fLayer.getFeatureClass();
    }

    public void open() {
	try {
	    ServerInitializer serverInit = new ServerInitializer();
	    serverInit.initializeServer(propriedades.domain, propriedades.user, propriedades.psw);
	    ServerConnection connection = new ServerConnection();
	    connection.connect(propriedades.server);
	    IServerObjectManager som = connection.getServerObjectManager();
	    this.context = som.createServerContext(propriedades.service, propriedades.serviceType);
	} catch (AutomationException e) {
	    Connector.LOG.error("Problema ArcObjects");
	    Connector.LOG.error(e.getMessage());
	    this.close();
	} catch (IOException e) {
	    Connector.LOG.error("Problema com Release!");
	    Connector.LOG.error(e.getMessage());
	}
    }

    public void close() {
	try {
	    this.context.releaseContext();
	    Connector.LOG.debug("Conexao finalizada com sucesso!");
	} catch (AutomationException e) {
	    Connector.LOG.error("Problema com release!");
	    Connector.LOG.error(e.getMessage());
	    try {
		this.context.removeAll();
	    } catch (AutomationException e1) {
		e1.printStackTrace();
	    } catch (IOException e1) {
		e1.printStackTrace();
	    }
	} catch (IOException e) {
	    Connector.LOG.error("Problema com Release!");
	    Connector.LOG.error(e.getMessage(), e);
	}
    }
}

Desta forma também podemos ter acesso total a IFeatureClass e também controle para abrir e fechar conexões. Porém cada uma tem suas vantágens e desevantágens e como foi dito você precisa saber em qual escopo você está trabalhando para poder tirar proveito de cada uma.

Bom, é isso. Para um primeiro post acho que o conteudo está legal, ainda é preciso abordar algumas partes do código e irei aproveitar esse tema para continuar com o assunto em posts seguintes, irei aprofundar em exemplos de códigos para plataforma ESRI e como utilizar frameworks de DI(que eu utilizo também dentro dos meus projetos). Não espero muitos comentário nem muitas visualizações, mas realmente não é isso que preciso, quero apenas conseguir passar a informação para quem precisar e aos poucos aprender um pouco e conhecer pessoas da área. Estou focando mais no assunto relacionado a GIS pois encontro poucos exemplos/textos em português, então acredito que isso pode ajudar o pessoal a começar a desenvolver nessa plataforma.


Seguir

Obtenha todo post novo entregue na sua caixa de entrada.