domingo, 26 de julho de 2015

JPA AttributeConverter, ou como utilizar a nova API de data do Java junto com JPA 2.1


Uma das melhores novidades do Java 8 foi sua API de data. Muita gente critica a API de data do Java com razão e com isso muita gente passou a utilizar a JODA Time.

A nova API de data foi baseada no JODA Time e escrita com linguagem fluente, melhorando muito o entendimento e uso.

Infelizmente o JPA ainda não suporta nativamente esse tipo de data, mas uma novidade na especificação do 2.1 permite seu uso de maneira bastante simples.

AttributeConverter é uma funcionalidade muito importante que já existia no Hhibernate desde o JPA 1.0. Com ele podemos criar mapeamentos de de-para entre tipos de nossa entidade e banco de dados.

Isto é muito útil nos cenários abaixo:

  • Banco de dados com campo verdadeiro/falso mapeado com 1/0: Podemos fazer um converter e tratar na entidade como true e false.
  • Melhor tratamento mapa mapeamento de Enums: O tratamento de mapeamento de Enum é bem útil, mas não contempla todas as necessidades.
  • Casos em que uma informação complexa é gravada como string no banco de dados.

Por padrão se fizermos um mapeamento com a API de data do java 8, o dado será gravado serializado.

Para esse post fiz um projeto de exemplo com algumas entidades e vários mapeamentos para os tipos novos de data. Para ajudar fiz uma classe util que lista todas as tabelas e campos da base.

Desta maneira fica fácil ver como cada configuração afeta como a base é gerada via JPA.

Uma das entidades é Aluno e rodando o teste sem criar o AttributeConverter para LocalDate, podemos perceber que o campo foi gerado como binário.

@Entity
public class Aluno {
	
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Integer id;
	
	@Column(length=200, nullable=false)
	private String nome;
	
	@Column(nullable=false)
	private LocalDate dataNascimento;

Saída teste:

ALUNO
COLUMN_NAME=ID, DATA_TYPE=4, TYPE_NAME=INTEGER, COLUMN_SIZE=10
COLUMN_NAME=DATANASCIMENTO, DATA_TYPE=-3, TYPE_NAME=VARBINARY, COLUMN_SIZE=255

Para criar um atribute converter basta implementar a interface javax.persistence.AttributeConverter e realizar uma configuração via a anotação javax.persistence.Converter:

import java.sql.Date;
import java.time.LocalDate;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply=true)
public class LocalDateAtributeConverter implements AttributeConverter<LocalDate, Date> {

	public Date convertToDatabaseColumn(LocalDate attribute) {
		return Date.valueOf(attribute);
	}

	public LocalDate convertToEntityAttribute(Date dbData) {
		return dbData.toLocalDate();
	}

}

O autoApply=true serve para indicar que por padrão a implementação JPA deve realizar essa conversão.

Caso seja colocado false, é preciso usar a anotação Convert no mapeamento do campo:

        @Convert(converter=LocalDateAtributeConverter.class)
	private LocalDate dataNascimento;

A anotação convert possui precedência sobre o converter global, então pode ser utilizado para sobrescrever alguma configuração.

Depois de feita uma das mudanças acima, a tabela é gerada corretamente:

ALUNO
  COLUMN_NAME=ID, DATA_TYPE=4, TYPE_NAME=INTEGER, COLUMN_SIZE=10 COLUMN_NAME=DATANASCIMENTO, DATA_TYPE=91, TYPE_NAME=DATE, COLUMN_SIZE=8

Para o mapeamento de Enums, por padrão (@Enumerated) podemos mapear pela ordem deles (começando com 0) ou pelo nome. Ambos os casos podem ser uteis em determinadas situações, como no enum abaixo:

public enum TipoPessoa {
	PF, PJ
}

Neste caso, não temos problema em fazer o mapeamento por nome, pois fica claro que PF e PJ se referem a pessoa física e jurídica.

Em alguns casos não podemos fazer isso, seja porque não ficaria muito legível ou seja porque estamos trabalhando com uma base legada que possui seus códigos.

Vamos imaginar que para diferenciar entrada e saída, são utilizados os códigos '001' e '002', neste caso não teríamos como fazer o mapeamento utilizando a anotação @Enumerated, mas fica bem simples utilizando o converter:

        //mapeamento
	@Column(length=3)
	private TipoPonto tipo;


public enum TipoPonto {
	ENTRADA("001"), SAIDA("002");
	
	private String codigo;
	
	private TipoPonto(String codigo) {
		this.codigo = codigo;
	}

	public String getCodigo() {
		return codigo;
	}
	
	public static TipoPonto getTipoPonto(String codigo) {
		TipoPonto[] values = values();
		for (TipoPonto tipoPonto : values) {
			if (tipoPonto.getCodigo().equals(codigo)) {
				return tipoPonto;
			}
		}
		throw new IllegalArgumentException("TipoPonto não encontrado");
	}
}

@Converter(autoApply=true)
public class TipoPontoAtributeConverter implements AttributeConverter<TipoPonto, String> {

	public String convertToDatabaseColumn(TipoPonto attribute) {
		return attribute.getCodigo();
	}

	public TipoPonto convertToEntityAttribute(String dbData) {
		return TipoPonto.getTipoPonto(dbData);
	}

}

Eu fiz outros exemplos, mas todos seguem a mesma linha, então fica mais interessante brincar com o projeto de teste que eu estou disponibilizando.

O Projeto utiliza o Maven e configurei o Hibernate como provider JPA. Como SGBD utilizei o banco H2, trabalhando apenas em memória.

O download pode ser feito aqui. Em breve vou jogar esse fonte no github.

terça-feira, 16 de dezembro de 2014

Cache com Escopo de Requisição Utilizando os Interceptadores do CDI


Uma das particularidades do JSF com o Facelets é que um método get de um Bean pode ser executado mais de uma vez em uma requisição. Esse número pode aumentar quando o método for chamado nas propriedades rendered dentro de um h:datatable ou um ui:repeat.

É um comportamento bastante conhecido e o ideal é que não se coloque regras de negocio nesses métodos ou se faça cache no Managed Bean. Isto é muito comum quando precisamos executar um método para cada linha de um datatable, como no exemplo abaixo:



Neste caso o botão de exclusão é condicionado a uma regra que está no Managed Bean. Existem algumas alternativas para resolver o problema que é causado pelo método ser chamado diversas vezes e a mais comum é criar um cache no Managed Bean.

Um map pode ser utilizado para guardar os valores do que já foi executado. Isto é muito utilizado, mas faz com que o seu cache fique com o escopo do MB e isto nem sempre isto é o ideal.

Para simulação criei o uma lista com 15 elementos e fiz com que o metodo possuiPermisão retornasse true para os elementos que possuam id par:

 
 @PostConstruct
 public void init() {
  produtos = new ArrayList(15);
  for (int i = 1; i <= 15; i++) {
   Produto p = new Produto();
   p.setId((long) i);
   p.setNome("Produto " + i);
   Double valor = Math.random() * 50;
   p.setValor(valor);
   produtos.add(p);
  }
 }
 
 public boolean possuiPermissao(Produto p) {
  sleep();
  boolean resultado = p.getId() % 2 == 0 ? true : false;
  log.info(MessageFormat.format("possuiPermissao(Produto {0})",p.getId()));
  return resultado;
 } 

Ao acessar a página aparece isto no log:

21:01:22,158 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 1)
21:01:22,181 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:01:22,202 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:01:22,223 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:01:22,244 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:01:22,264 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:01:22,287 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 3)
21:01:22,309 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:01:22,329 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:01:22,349 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:01:22,370 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:01:22,390 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:01:22,412 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 5)
21:01:22,434 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:01:22,454 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:01:22,475 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:01:22,496 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:01:22,517 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:01:22,541 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 7)
21:01:22,563 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:01:22,584 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:01:22,605 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:01:22,637 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:01:22,658 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:01:22,680 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 9)
21:01:22,702 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:01:22,723 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:01:22,743 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:01:22,764 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:01:22,784 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:01:22,806 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 11)
21:01:22,828 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:01:22,849 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:01:22,869 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:01:22,890 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:01:22,910 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:01:22,933 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 13)
21:01:22,955 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:01:22,976 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:01:22,997 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:01:23,018 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:01:23,038 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:01:23,060 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 15)

Podemos notar que o método foi executado uma vez para os objetos que ele retorna false e cinco vezes para o que retorna true. Isto é um comportamento esperado e vocês podem ver uma explicação neste link aqui.

Os interceptadores do CDI permitem que a execução de um método seja executada e isto é muito útil em vários cenários, desde controle de acesso a nível de método até logar todos os métodos de um bean medindo o tempo de execução.

Vamos precisar de duas classes, para criar o nosso interceptador e utilizar eles no exemplo: RequestCache.java e RequestCacheInterceptor.java

A primeira classe é a definição na anotação que vamos utilizar para marcar os métodos que queremos fazer cache:

 
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequestCache {

}

A segunda classe implementa o interceptador e eu utilizei o próprio request para fazer o cache guardando um Map. A classe abaixo não está completa, mas da para entender todo o processo e no final do artigo deixarei um link com o projeto eclipse deste exemplo.

@Interceptor
@RequestCache
public class RequestCacheInterceptor implements Serializable {
 
 private static final long serialVersionUID = 1L;
 private static final String MAP_NAME = RequestCacheInterceptor.class.getName() + "_map_cache";

 @AroundInvoke
 public Object execute(InvocationContext invocationContext) throws Exception {
     Map mapCache = getMapCache();
     Key key = new Key(invocationContext);
     Result result = mapCache.get(key);
     if (result == null) {
         long ini = System.currentTimeMillis();
         result = new Result(invocationContext.proceed());
         result.time = System.currentTimeMillis() - ini;
         mapCache.put(key, result);
     }
     result.count++;
     return result.object;
 }   

 @SuppressWarnings("unchecked")
 private Map getMapCache() {
     Map map = (Map) getRequest().getAttribute(MAP_NAME);
     if (map == null) {
         map = Collections.synchronizedMap(new HashMap());
         getRequest().setAttribute(MAP_NAME, map);
     }
     return map;
 }


Toda vez que o nosso método, marcado com a anotação @RequestCache, for executado o método execute será chamado e método passará a ser executado somente quando for executada o método invocationContext.proceed().

Apliquei a anotação, reiniciei o Jboss e executei novamente a página:

 
 @RequestCache
 public boolean possuiPermissao(Produto p) {
  sleep();
  boolean resultado = p.getId() % 2 == 0 ? true : false;
  log.info(MessageFormat.format("possuiPermissao(Produto {0})",p.getId()));
  return resultado;
 }

21:21:51,339 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 1)
21:21:51,361 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2)
21:21:51,385 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 3)
21:21:51,407 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4)
21:21:51,432 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 5)
21:21:51,454 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6)
21:21:51,480 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 7)
21:21:51,502 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8)
21:21:51,526 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 9)
21:21:51,548 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10)
21:21:51,573 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 11)
21:21:51,596 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12)
21:21:51,619 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 13)
21:21:51,641 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14)
21:21:51,665 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 15)

Com isso podemos ver que nosso interceptador funcionou perfeitamente. Podemos usar em vários pontos do projeto e com isto economizamos códigos repetitivos.

Neste exemplo utilizei um projeto JSF criado pelo Eclispe e depois Mavenizado pelo Configure / Convert to Maven Project.

Utilizei o Jboss 7.1 o download do projeto pode ser feito aqui.

Em breve colocarei este exemplo no Github.

segunda-feira, 13 de maio de 2013

DataTable - Lazy Loading com Primefaces

Um problema bem comum com quem trabalha com aplicações é web são as grids com paginação em memória. Para uma consulta que não retorna muitos dados o problema não é muito grande, mas em um sistema em produção é muito provável que paginar em memória se torne um gargalo rapidamente.

O Seam criou uma solução bastante elegante e prática que lida bem com filtros dinâmicos. Com base em minha experiencia com o Seam, decidi implementar algo com funcionalidade parecida com JSF2 e Primefaces.

O DataTable do Primefaces suporta Lazy Loading através da classe tipada org.primefaces.model.LazyDataModel, mas o exemplo deles é feito em cima de uma lista pronta em vez de um consulta EJB-QL. Vamos utilizar os métodos setFirstResultsetMaxResults para trazer o range correto da consulta de acordo com a paginação.

Para permitir paginação é preciso saber o total de elementos que a consulta traria e para isto será utilizado uma consulta com count. Na solução do Seam esta consulta é criada automaticamente e funciona bem para a maioria dos casos, mas é possível criar manualmente a consulta. Para simplificar as coisas nesta primeira versão, a consulta do count deve ser criada manualmente.

Para os filtros o funcionamento vai ser semelhante ao do Seam, mas sem a restrição de permitir apenas uma expressão el por filtro.

A classe abstrata tipada DataList estende a LazyDataModel fazendo todo o tratamento. Criei a classe como abstrata para que seja obrigatória a implementação dos métodos abaixo:

protected Map<String, DataListFilter> getFiltros();
protected abstract String getSqlList();
protected abstract String getSqlCount();

O principal método da classe LazyDataModel  é o public List load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters). É necessário sobrescrever, pois ele sempre retorna UnsupportedOperationException("Lazy loading is not implemented.").

O DataTable do Primefaces possui sistema de ordenação e de filtros e eles são repassados por esses parâmetros, juntamente com os dados referentes a paginação:
int first - Equivalente ao OFFSET
int pageSize - Equivalente ao LIMIT
String sortField - Nome do campo usado na ordenação
SortOrder sortOrder - Enum que indica se a ordenação será ASC ou DESC
Map filters - Nessa map a key indica o nome do campo e o value o valor.

Como o sistema de filtro do método load suporta apenas Strings, fiz um suporte para filtros com valores dinâmicos baseados em 'el'.

Para definir o filtro é preciso apenas sobrescrever o método getFiltros() como pode ser visto no exemplo abaixo.
 
 protected Map<String, DataListFilter> getFiltros() {
  filtros = new HashMap<String, DataListFilter>();
  filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
  filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
  return filtros;
 }

A classe DataListFilter será a responsável pelas operações em cima do filtro. Basicamente ela precisa fazer o parse do filtro para encontrar todas as expressões #{el} e transformar em um parâmetro da Query. Por exemplo para o filtro "AND o.sigla = #{estadoDataList.siglaEstado}" será encontrada a el #{estadoDataList.siglaEstado} e a consulta EJB-QL será AND o.sigla = :estadoDataList_siglaEstado.

Outra coisa importante é avalizar todas as expressões do filtro e caso todas retornem valor o método isPopulatedExpressions() retornará true. Isto é usado pela classe DataList para saber se o filtro deve ser adicionado na String da Query bem como no momento de aplicar os parâmetros.

O regex que identifica e captura as expressões é bastante simples e utiliza grupos para poder obter as informações de maneira mais fácil: (\#\{(.*?)\})

Para avaliar as expressões criei o método abaixo, ele é bastante útil em uma aplicação JSF:
 
 private <C> C eval(String el, Class<C> classReturn) {
  FacesContext facesContext = FacesContext.getCurrentInstance();
  Application application = facesContext.getApplication();
  ExpressionFactory factory = application.getExpressionFactory();
  ELContext elContext = facesContext.getELContext();
  ValueExpression valueExpression = factory.createValueExpression(elContext, el, classReturn);
  return (C) valueExpression.getValue(elContext);
 }


Na pratica a criação do ManagedBean referente do DaraTable fica simples:
 
package br.rodrigo.dao.list;

import java.util.HashMap;
import java.util.Map;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.inject.Inject;
import javax.persistence.EntityManager;

import br.rodrigo.dao.DataList;
import br.rodrigo.dao.DataListFilter;
import br.rodrigo.model.Estado;

@ManagedBean
@RequestScoped
public class EstadoDataList extends DataList<Estado> {
 
 private static final String SQL_LIST = "select o from Estado o";
 private static final String SQL_COUNT = "select count(o) from Estado o";
 
 private Map<String, DataListFilter> filtros;
 
 private String nomeEstado;
 private String siglaEstado;
 
 @Inject
 private EntityManager em;

 private static final long serialVersionUID = 1L;

 public String getNomeEstado() {
  return nomeEstado;
 }
 
 public void setNomeEstado(String nomeEstado) {
  this.nomeEstado = nomeEstado;
 }
 
 public String getSiglaEstado() {
  return siglaEstado;
 }
 
 public void setSiglaEstado(String siglaEstado) {
  this.siglaEstado = siglaEstado;
 }

 @Override
 protected Map<String, DataListFilter> getFiltros() {
  filtros = new HashMap<String, DataListFilter>();
  filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
  filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
  return filtros;
 }

 @Override
 protected String getSqlList() {
  return SQL_LIST;
 }

 @Override
 protected String getSqlCount() {
  return SQL_COUNT;
 }
}

No arquivo xhtml o código ficaria:



Fiz esse mecanismo como prova de conceito e exercício, embora tudo esteja funcional é preciso trabalhar melhor as classes. Abaixo vocês podem fazer o download das classes e do projeto eclipse. Utilizei como servidor o Jboss 7.0.1 final e como IDE o Eclipse Juno e Jboss tools instalado via Eclipse Market.

DataList
DataListFilter
EstadoDataList
StringUtil
ScriptBanco
Página com a grid

Projeto



sexta-feira, 28 de dezembro de 2012

Integração com PagSeguro Parte 2

1. Implementando a integração


A API do PagSeguro é simples e fácil de usar e dentro do zip que se encontra para download, existe uma pasta com quatro sources de exemplos.



Esses exemplos contemplam tudo que é preciso para usar o PagSeguro e o maior trabalho mesmo é criar suas rotinas para executar o pagamento a partir de sua venda.

Para todas as rotinas da API, será preciso utilizar o email cadastrado no PagSeguro e o token de segurança que eu comentei no post anterior.

1.1 Criando o Pagamento


Ao abrir o CreatePayment.java você perceberá que realmente é simples realizar este procedimento e o maior trabalho vai ser adequar seu negócio pois, o PagSeguro pede informações que talvez você não tenha ou não esteja em um formato aceito.

O PagSeguro fez uma serie de validações dos dados enviados e vai lançar mensagens de erro indicando o que não passou na validação.

Por causa destas validações, eu tive de modificar meu sistema. Um dos pontos mesmo que ele acusa problema é com o nome de pessoa, que possui um tamanho minimo.

Por causa destas validações é bom realizar testes com os dados mínimos obrigatórios em seu sistema e verificar se algum dado deve se tornar requerido.

A lista com todas as mensagens de erro pode ser vista neste link: https://pagseguro.uol.com.br/v2/guia-de-integracao/codigos-de-erro.html#rmcl.

Para relacionar uma venda sua com uma transação do PagSeguro existe o método: paymentRequest.setReference("REF1234");

Isto é muito importante pois é com esta informação que você conseguirá descobrir para qual venda aquele pagamento se relaciona. É um campo string e eu recomendo utilizar o próprio Id da venda.

1.2 Processando as notificações


Como eu comentei no outro texto, é preciso ter uma página em seu sistema que será acessada pelo PagSeguro. Está página receberá códigos de notificação e a partir destes códigos se obtêm os dados da transação.

O código da notificação é passado com o parâmetro notificationCode e depois de capturar este valor da requisição deverá ser usado o código da classe ReceiveNotifications.java.

Com este código você consegue obter o ID da transação e status e deve obter o resto das informações acessando os dados da transação usando o código da classe SearchTransactionByCode.java.

Na prática sua venda, deverá receber um status de Aguardando Pagamento e este status será modificado pela rotina que processa as notificações.

Mais informações sobre todo o processo podem ser obtidas aqui: https://pagseguro.uol.com.br/v2/guia-de-integracao/tutorial-da-biblioteca-pagseguro-em-java.html.

2 Armazenando os dados


Eu decidi armazenar todos os dados das transações do PagSeguro para minhas vendas. Deste modo eu não preciso acessar o PagSeguro via API todas as vezes que precisar de alguma informação.

Isto também fica prático pelo fluxo do PagSeguro. Como o sistema é notificado quando existe inserção/atualização nas transações neste momento além de atualizar o status de sua venda, eu recomendo que você grave todos os dados desta transação.

Não são muitos dados e abaixo pode ser visto o DER do schema pag_seguro que eu criei.


Criei as tabelas em inglês, porque mesmo o PagSeguro sendo usado apenas no Brasil, sua API é toda em inglês.

As tabelas tb_transaction_status, tb_payment_method_code e tb_payment_method_type são tabelas com os códigos que o PagSeguro utiliza. Populei elas com base nos dados da documentação e servem para visualização mais fácil do que cada código representa.

Para facilitar a vida de vocês seguem os inserts para popular estas tabelas:

 
INSERT INTO tb_payment_method_code VALUES (101, 'Cartão de crédito Visa');
INSERT INTO tb_payment_method_code VALUES (102, 'Cartão de crédito MasterCard');
INSERT INTO tb_payment_method_code VALUES (103, 'Cartão de crédito American Express');
INSERT INTO tb_payment_method_code VALUES (104, 'Cartão de crédito Diners');
INSERT INTO tb_payment_method_code VALUES (105, 'Cartão de crédito Hipercard');
INSERT INTO tb_payment_method_code VALUES (106, 'Cartão de crédito Aura');
INSERT INTO tb_payment_method_code VALUES (107, 'Cartão de crédito Elo');
INSERT INTO tb_payment_method_code VALUES (108, 'Cartão de crédito PLENOCard');
INSERT INTO tb_payment_method_code VALUES (109, 'Cartão de crédito PersonalCard');
INSERT INTO tb_payment_method_code VALUES (110, 'Cartão de crédito JCB');
INSERT INTO tb_payment_method_code VALUES (111, 'Cartão de crédito Discover');
INSERT INTO tb_payment_method_code VALUES (112, 'Cartão de crédito BrasilCard');
INSERT INTO tb_payment_method_code VALUES (113, 'Cartão de crédito FORTBRASIL');
INSERT INTO tb_payment_method_code VALUES (201, 'Boleto Bradesco');
INSERT INTO tb_payment_method_code VALUES (202, 'Boleto Santander');
INSERT INTO tb_payment_method_code VALUES (301, 'Débito online Bradesco');
INSERT INTO tb_payment_method_code VALUES (302, 'Débito online Itaú');
INSERT INTO tb_payment_method_code VALUES (303, 'Débito online Unibanco');
INSERT INTO tb_payment_method_code VALUES (304, 'Débito online Banco do Brasil');
INSERT INTO tb_payment_method_code VALUES (305, 'Débito online Banco Real');
INSERT INTO tb_payment_method_code VALUES (306, 'Débito online Banrisul');
INSERT INTO tb_payment_method_code VALUES (307, 'Débito online HSBC');
INSERT INTO tb_payment_method_code VALUES (401, 'Saldo PagSeguro');
INSERT INTO tb_payment_method_code VALUES (501, 'Oi Paggo');

INSERT INTO tb_payment_method_type VALUES (1, 'Cartão de crédito');
INSERT INTO tb_payment_method_type VALUES (2, 'Boleto');
INSERT INTO tb_payment_method_type VALUES (3, 'Débito online (TEF)');
INSERT INTO tb_payment_method_type VALUES (4, 'Saldo PagSeguro');
INSERT INTO tb_payment_method_type VALUES (5, 'Oi Paggo');

INSERT INTO tb_transaction_status VALUES (1, 'Aguardando pagamento');
INSERT INTO tb_transaction_status VALUES (2, 'Em análise');
INSERT INTO tb_transaction_status VALUES (3, 'Paga');
INSERT INTO tb_transaction_status VALUES (4, 'Disponível');
INSERT INTO tb_transaction_status VALUES (5, 'Em disputa');
INSERT INTO tb_transaction_status VALUES (6, 'Devolvida');
INSERT INTO tb_transaction_status VALUES (7, 'Cancelada');

Achei importante criar um tabela de histórico das transações, pois assim eu consigo ter um detalhe de quanto tempo a transação passou em determinado status.

Preferi fazer o controle deste histórico via trigger:

 
CREATE OR REPLACE FUNCTION pague_seguro.register_tb_transaction_audit()
  RETURNS trigger AS
$BODY$
    BEGIN
        --
        -- Registrar historico caso tenha mudado o status
        --
        IF (TG_OP = 'INSERT') THEN
   UPDATE pague_seguro.tb_transaction_history set dt_end = now() where cd_transaction = NEW.cd_transaction AND dt_end is null;
            INSERT INTO pague_seguro.tb_transaction_history (cd_transaction, id_transaction_status, dt_begin) select NEW.cd_transaction, NEW.id_transaction_status, now();
            UPDATE pague_seguro.tb_transaction set dt_status_change = now() where cd_transaction = NEW.cd_transaction;
        END IF;
        IF (TG_OP = 'UPDATE') THEN
            IF (NEW.id_transaction_status <> OLD.id_transaction_status) THEN
      UPDATE pague_seguro.tb_transaction_history set dt_end = now() where cd_transaction = NEW.cd_transaction AND dt_end is null;
      INSERT INTO pague_seguro.tb_transaction_history (cd_transaction, id_transaction_status, dt_begin) select NEW.cd_transaction, NEW.id_transaction_status, now();
      UPDATE pague_seguro.tb_transaction set dt_status_change = now() where cd_transaction = NEW.cd_transaction;
     END IF; 
        END IF;        
        RETURN NULL; -- o resultado é ignorado uma vez que este é um gatilho AFTER
    END;
  $BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

3. Conclusão


Neste post eu não entrei em muitos detalhes da API, pois ela está bem documentada. Minha intenção passar um quadro geral de como pode ser implementada a integração em seu sistema e dar uma visão do esforço necessário.

Tentei passar as informações que eu gostaria de ter tido conhecimento antes de ter colocado a mão na massa.

Qualquer dúvida eu responderei nos comentários.

quarta-feira, 26 de dezembro de 2012

Integração com PagSeguro Parte 1

Conceito e configuração da conta.


1. Introdução

O PagSeguro é uma solução nacional para pagamentos online mantida pela UOL. Estas soluções visam facilitar a vida de compradores e vendedores e a mais famosa é o PayPal.

Como o Paypal não atuava oficialmente no Brasil, era preciso usar cartão de crédito internacional e acabava dificultando as coisas para quem compra e principalmente para quem vende.

O PagSeguro funciona bem e até agora se provou seguro, tanto para quem compra quanto para quem vende.

O maior defeito do PagSeguro, em minha opinião, é a falta de um Sandbox oficial para testes. Até existem opções criadas por terceiros, mas não são suportadas e a que eu testei nem funcionava mais com a API nova de integração.

Isto não é problema para compradores e vendedores, mas dificulta o trabalho dos desenvolvedores pois torna o teste mais trabalhoso e custoso.

Minha intenção aqui é mostrar, em alguns posts, como eu fiz para integrar um sistema meu com o PagSeguro.

2. Fluxo resumido da operação

O fluxo inicia com o usuário finalizando a compra escolhendo o modo de pagamento PagSeguro. Feito isto a aplicação faz o processamento desta venda utilizando a API do PagSeguro e recebe uma URL.

O sistema deve redirecionar o usuário para esta URL, para que ele possa finalizar o pagamento.

Depois que o pagamento é finalizado o usuário é redirecionado de volta para o sistema de origem e o fluxo das notificações se inicia.

O PagSeguro fará um ou mais POSTs em uma página de seu sistema passando dois parâmetros: notificationcode e notificationtype.

São com estas notificações que o PagSeguro informará a seu sistema o que está acontecendo com a transação.

A página que recebe o post deve utilizar o código da notificação para acessar o PagSeguro via API e obter o status da transação, obtendo várias informações como se o pagamento foi autorizado ou não.

Mais detalhes podem ser vistos neste link: https://pagseguro.uol.com.br/integracao/pagamentos-via-api.jhtml

Outra informação importante é que caso o PagSeguro não consiga enviar os dados da notificação para seu sistema, "o PagSeguro irá envia-la novamente a cada 2 horas, até um máximo de 5 tentativas".

Mas caso seu sistema fique fora do ar por mais tempo é possível obter as informações das transações realizando uma consulta por data: https://pagseguro.uol.com.br/v2/guia-de-integracao/consulta-de-transacoes-por-intervalo-de-datas.html.

3. Configuração da Conta

O primeiro passo é converter sua conta para Vendedor ou Empresa, você vai precisar disto para fazer os testes iniciais. Como não há disponibilidade de um sandbox de testes, será necessário realizar umas compras e depois cancelar.

Depois de converter a conta, será possível acessar o menu 'Integrações' e a primeira configuração será  a 'Pagina de redirecionamento'. 

Essa é a página para qual o cliente será redirecionado depois que finalizar o pagamento no PagSeguro. Eu pessoalmente prefiro o redirecionamento dinâmico, pois em caso de alguma mudança na aplicação, eu não preciso que o cliente reconfigure este parâmetro.

Você vai configurar o nome do parâmetro que o PagSeguro vai utilizar para informar o código da transação. Com este nome, você pode via API buscar as informações sobre esta transação, para informar ao cliente.


O segundo item do menu permite a criação do 'Token de segurança', que será utilizado na autenticação entre o sistema e o PagSeguro. Depois de criado ele fica visível apenas na criação e caso você esqueça ou perca, basta gerar novamente.

No terceiro item é onde ativaremos o 'Pagamento via API'


O quarto item do menu, é o 'Notificação de transações' e será o ultimo a ser configurado. Aqui vamos definir qual página de nosso sistema receberá as notificações sobre as mudanças de status das transações.

Por causa dessa característica, mesmo na fase de desenvolvimento será preciso configurar com uma URL válida. 

Isto é chato e as duas possibilidades que eu vejo são:
1. Utilizar seu servidor local. Para isso é preciso que o PagSeguro tenha acesso externo via internet. Isso pode ser complicado caso você esteja acessando a internet via um roteador ADSL ou proxy.
2. Utilizar um servidor de homologação. No meu caso eu criei uma conta no Amazon EC2 e montei um ambiente de homologação.


Nós primeiros testes, para conseguir debugar com a aplicação rodando localmente, eu iniciava o teste de minha maquina e depois monitorava as notificações que eram enviadas pelo PagSeguro para o meu servidor de homologação.

Eu monitorava logando em um arquivo os dados enviados via GET/POST para a página:

================= 2012-12-07 19:06:52 ==================
notificationcode=1F2964-960Z850D85F6-0BB4T3AFBC5E-2997F0
notificationtype=transaction


Com estes dados em mãos eu posso repassar para meu sistema local, via URL no navegador:

processaNotificacao.wsp?notificationcode=1F2964-960Z850D85F6-0BB4T3AFBC5E-2997F0&notificationtype=transaction

Minha recomendação é que todos os POSTs/GETs recebidos pela página de notificação sejam logados, assim caso não seja processado algo em seu sistema por causa de um bug, o processamento pode ser feito manualmente.

No próximo post eu falarei sobre a utilização da API e sobre a criação de tabelas para guardar em banco tudo que foi processado, realizando um controle melhor e evitando o acesso ao PagSeguro sem necessidade.

[]'s


domingo, 17 de junho de 2012

JImageResizer, redimensionando imagens em lote com Thumbnailator e WindowBuilder Pro

Semana passada eu procurei um plugin para trabalhar com Swing no Eclipse e para minha surpresa descobri que o Google tinha lançado o WindowBuilder Pro, um plugin para o Eclipse que se provou até agora ser o melhor para edição de layout para Java, dando suporte a Swing e GWT.

WindowBuilder Pro


Muitas vezes eu corria para o Netbeans quando precisava fazer algum programa em Swing, pois seu editor é amigável, mas o código gerado para os componentes UI não pode ser modificado fora do editor visual e além disso ele gera um código muito caótico para manutenção.

Depois de testar o WindowBuilder eu posso dizer que o melhor editor, fácil de usar e com muitos recursos ele trabalha de maneira bi-direcional, ou seja eu posso editar o código dos componentes tanto no editor visual quando no fonte de maneira transparente, principalmente porque ele gera um código super limpo.

Código gerado pelo WindowBuilder Pro


Outra grande vantagem é dele dar suporte alguns Layout Managers como o MiG Layout que é simples de trabalhar e bastante poderoso.

Tive a ideia de fazer um pequeno projeto para testar o WindowBuilder e resolvi criar um aplicativo muito simples de redimensionamento de imagens em lote.

Meu pai sempre me pede para redimensionar imagens para ele e eu costumo fazer isto em vários programas, então eu pensei em criar um redimensionador de imagens o simples e básico possível. Aproveitei uma ótima biblioteca criada para geração de thumbnail, o Thumbnailator e comecei o projeto.

Graças a simplicidade do Thumbnailator, a tarefa de redimensionar a imagem, salvando em outro arquivo se resumiu a esta linha abaixo.


Thumbnails.of(file).size(tamanho, tamanho).outputQuality(quality).toFile(fileDestino);



Um dos problema iniciais que eu tive foi como obter o tamanho em pixels das imagens sem precisar ler todo seu conteúdo e após uns minutos achei o código abaixo obtido neste link.

 public static Dimension getImageDimension(File file) {
  ImageInputStream in = null;
  try {
   in = ImageIO.createImageInputStream(file);
   final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
   if (readers.hasNext()) {
    ImageReader reader = (ImageReader) readers.next();
    try {
     reader.setInput(in);
     return new Dimension(reader.getWidth(0), reader.getHeight(0));
    } finally {
     reader.dispose();
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (in != null)
    try {
     in.close();
    } catch (IOException e) { }
  } 
  return null;
 }

Minha intenção desde o inicio foi criar algo Open Source, então em breve vou colocar o projeto em algum Code Hosting voltado para projetos Open Sources como o Google Code.

Atualmente estou mantendo este projeto no Assembla, que se mostrou um ótimo serviço gratuito que não obriga que o projeto seja publico. Quem tiver interesse me avisa que eu publico o zip com source aqui mesmo.

Para quem tiver interesse em usar o JImageResizer, pode fazer o download nos link abaixo:
JImageResizer 0.01


segunda-feira, 29 de agosto de 2011

Instanciando um Bean em uma consulta HQL

Em uma consulta HQL quanto projetamos mais que um objeto, o mais comum é acessarmos a consulta por um List<Object[]> essa abordagem não é muito interessante, pois torna nossa consulta muito passível de dar erro, pois basta mudar a ordem das colunas e vamos causar um erro onde a consulta estiver sendo usada.


String hql = "select o.numeroProcesso, o.dataInicio from Processo o";
Query query = EntityUtil.createQuery(hql);
List<Object[]> list = query.getResultList();

Uma abordagem mais interessante é devolver essa consulta como um List<Map<String, Object>> criando os alias paras as colunas projetadas. Dessa maneira uma mudança de ordem na consulta não traria erro para quem a utilizasse:

String hql = "select new map(o.numeroProcesso as numeroProcesso, o.dataInicio as dataInicio) from Processo o";
Query query = EntityUtil.createQuery(hql);
List<Map<String, Object>> list = query.getResultList();
for (Map<String, Object> map : list) {
Object numeroProcesso = map.get("numeroProcesso");
}

A utilização do List<Map<String, Object>> é um avanço, mas pode induzir a erros de runtime, pois o cast fica sobre responsabilidade de quem for utilizar a lista.

Uma maneira mais segura é utilizar o recurso de instanciação do bean via construtor. O hibernate permite que instanciemos em nossa consulta uma classe que as colunas projetadas sejam passadas como parâmetro do construtor da classe:





Com essa abordagem, tornamos nossa consulta bem mais segura, pois vamos devolver uma lista com o nosso bean.