Thursday, April 30, 2015

Build com Gradle para construir projeto Spring Boot, MVC, JPA e QueryDSL

O Spring Boot foi criado para facilitar o setup e estrutura de projetos Java. Já estamos usando essa tecnologia na YaW a mais de um ano, com resultado bem satisfatório. Ele disponibiliza a infra necessária para criar e executar um projeto Java, stand-alone, em poucos passos. Fornece um web container embutido: Tomcat, Jetty ou Undertown. Integrado a ferramenta de build ele também turbina a configuração e gestão das dependências.

Exemplo de build.gradle:
buildscript {

  ext {
    springBootVersion = "1.3.0.BUILD-SNAPSHOT"
    springLoadedVersion = "1.2.0.RELEASE"
    queryDSLVersion = "3.6.3"
    hsqldbVersion = "2.3.2"
  }

  repositories {
    mavenLocal()
    mavenCentral()
    maven { url "http://repo.spring.io/release" }
    maven { url "http://repo.spring.io/milestone" }
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "https://plugins.gradle.org/m2/" }
  }

  dependencies {
    classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
  }
}

apply plugin: "java"
apply plugin: "spring-boot"

sourceCompatibility = 1.8

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/snapshot" } 
}

sourceSets {
  generated {
    java {
      srcDirs = ["src/main/generated"]
    }
  }
}

configurations {
  querydslapt
}

dependencies {
  compile "org.springframework.boot:spring-boot-starter-data-jpa"
  compile "org.springframework.boot:spring-boot-starter-web"
  compile "com.mysema.querydsl:querydsl-jpa:${queryDSLVersion}"
  querydslapt "com.mysema.querydsl:querydsl-apt:${queryDSLVersion}"
  runtime "org.hsqldb:hsqldb:${hsqldbVersion}"
}

task generateQueryDSL(type: JavaCompile, group: "build", description: "Gera o codigo das classes do QueryDSL") {
  source = sourceSets.main.java
  classpath = configurations.compile + configurations.querydslapt
  options.compilerArgs = [
    "-proc:only",
    "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
  ]
  destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

compileJava {
  dependsOn generateQueryDSL
  source generateQueryDSL.destinationDir
}

compileGeneratedJava {
  dependsOn generateQueryDSL
  classpath += sourceSets.main.runtimeClasspath
}

clean {
  delete sourceSets.generated.java.srcDirs
}

Por natureza o Gradle segue um sintaxe enxuta para o build. O Spring Boot atua como um wrapper para as dependências de Web e persistência. Precisamos indicar ao Gradle para carregar o driver HSQLDB em memória. O volume maior de configurações é por conta da task (generatedQueryDSL) para geração de código das entidades QueryDSL e das configurações de plugin. Compartilhei no Github esse arquivo build.gradle, junto com o projeto demonstração.

Para fazer o build e executar o projeto use a instrução:
$ gradle bootRun

@edermag

Sunday, April 26, 2015

Eficência na construção de consultas JPA com QueryDSL

A algum tempo incorporei o QueryDSL na stack de tecnologias utilizadas pela YaW. É uma ferramenta que vem me ajudando bastante no desenvolvimento de projetos Java. O QueryDSL é uma tecnologia unificada para a construção de consultas em projetos Java, através de diferentes mecanismos de persistência.

Além do Hibernate / JPA, o QueryDSL atua em conjunto com as seguintes tecnologias:
  • SQL (JDBC): construção de consultas tipadas sob JDBC (e driver);
  • JDO: construção de consultas tipadas sob o JDO (e provider SQL / NOSQL);
  • MongoDB: construção de consultas tipadas sob MongoDB (NOSQL);
  • Lucene: construção de consultas tipadas em full text search;
  • Coleções: consultas de pojos armazenados em coleções do Java;

Outro ponto interessante é que o QueryDSL pode ser utilizado com as linguagens Scala e Groovy. O projeto evoluiu bastante, apesar disso ainda mantém o foco principal: uma linguagem sofisticada e alto nível, para viabilizar a construção de consultas. Na minha opnião o ponto forte do QueryDSL, além da tipagem, é possibilidade de organizar e reaproveitar código para construir as consultas.

Bem, meu objetivo nesse post é demonstrar o QueryDSL com JPA / Hibernate, com banco de dados HSQLDB. Um exemplo de como construir consultas com QueryDSL, em entidades mapeadas via JPA. Compartilhei no Github um projeto que demonstra como trabalhar com o QueryDSL e JPA, vou usá-lo como referência. A primeira etapa é a configuração das dependências do QueryDSL e plugin complementar para Maven. No arquivo pom.xml, é possível visualizar as configurações de dependências com o conteúdo a seguir:
  ...
  <dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>${querydsl.version}</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>com.mysema.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>${querydsl.version}</version>
  </dependency>
  ...

O plugin responsável por gerar as classes de consultas baseadas nas entidades da aplicação:

  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
                        
      <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>apt-maven-plugin</artifactId>
        <version>1.1.3</version>
        <executions>
          <execution>
            <goals>
              <goal>process</goal>
            </goals>
            <configuration>
              <outputDirectory>target/generated-sources/java</outputDirectory>
              <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

Dica: após baixar o projeto, antes de olhar o código fonte, execute o comando Maven:
$ mvn clean generate-sources

Dica para o Eclipse: será necessário adicionar a pasta target/generated-sources/java no classpath do projeto (em Java Build Path na aba Folders). A partir disso a toda nova entidade, será necessário executar a geração de código do Maven e refresh na pasta do Eclipse.

Uma característica importante do QueryDSL é a geração de código. A partir de cada entidade JPA mapeada na aplicação, o QueryDSL gera uma classe a qual iremos utilizar para construir as consultas. Por exemplo, para a entidade Mercadoria o QueryDSL gera a classe QMercadoria, uma extensão de Expression que representa uma expressão tipada. Cada atributo, original, da classe Mercadoria téra uma representação em QMercadoria. Por exemplo o atributo nome é definido como StringPath em QMercadoria, ou seja uma expressão tipada para String. Veja na classe QMercadoria o tipo do atributo categoria, uma outra entidade.

Com o ambiente configurado, a próxima etapa é construir as consultas com o QueryDSL. O código a seguir foi retirado da classe MercadoriaQuery, o método findAllByCriterio. A API DSL é utilizada para construir a consulta SQL sobre a entidade Mercadoria. Essa consulta varia de acordo com o preenchimento de dos filtros em uma página Web. Veja o código:
public static List<Mercadoria> findAllByCriterio(EntityManager em, 
    FiltrosPesquisaMercadoria filtros) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  
  Predicate where = whereByCriterio(mercadoria, filtros);
  
  int offset = filtros.getOffset(); 
  return query.from(mercadoria)
    .where(where)
    .offset(offset)
    .limit(filtros.getLinhas())
    .list(mercadoria);
}

private static Predicate whereByCriterio(QMercadoria mercadoria, 
    FiltrosPesquisaMercadoria filtros) {
  BooleanBuilder builder = new BooleanBuilder();
  
  if (!Strings.isNullOrEmpty(filtros.getDescricaoMercadoria())) {
    builder.and(likeWithLowerCase(mercadoria.descricao.toLowerCase(), 
      filtros.getDescricaoMercadoria()));
  }
  if (!Strings.isNullOrEmpty(filtros.getNomeMercadoria())) {
    builder.and(likeWithLowerCase(mercadoria.nome, 
      filtros.getNomeMercadoria()));
  }
  if (filtros.getPrecoDe() != null && filtros.getPrecoDe() > 0) {
    builder.and(mercadoria.preco.goe(filtros.getPrecoDe()));
  }
  if (filtros.getPrecoAte() != null && filtros.getPrecoAte() > 0) {
    builder.and(mercadoria.preco.loe(filtros.getPrecoAte()));
  }
  if (!Strings.isNullOrEmpty(filtros.getCategoria())) {
    builder.and(likeWithLowerCase(mercadoria.categoria.descricao, 
      filtros.getCategoria()));
  }
  return builder;
}

O QueryDSL, via o método list, faz a conversão dos dados (ResultSet) usando o tipo Mercadoria. Enquanto o método whereByCriterio concentra a lógica para construir as condições da consulta SQL. Esse mesmo método é utilizado para construir outra consulta, responsável pelo contador da paginação.
public static long countByCriterio(EntityManager em, 
    FiltrosPesquisaMercadoria filtros) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  
  Predicate where = whereByCriterio(mercadoria, filtros);
  return query.from(mercadoria)
    .where(where)
    .count();
}
Sobre a paginação, de volta ao método findAllByCriterio indico o offset e limit, informações necessárias para executar a consulta SQL usando a paginação.

O método findAllGroupedByCategoria é utilizado para construir uma consulta de Categorias, usando funções agregadoras SQL. Essa consulta retorna a quantidade de pedidos, o menor e maio preço por Categoria. O QueryDSL retorna esses dados encapsulados em um objeto do tipo Tuple. Mas é possível customizar um mapper, para converter um Tuple em um bean especifico. Verifique a classe CategoriaGroupMapper, veja como implementar um mapper para o QueryDSL. Veja o código para construir a consulta agrupada:
public static List<CategoriaGroup> findAllGroupedByCategoria(
    EntityManager em) {
  JPAQuery query = new JPAQuery(em);
  QMercadoria mercadoria = new QMercadoria("m");
  QCategoria categoria = new QCategoria("c");
    
  return query.from(mercadoria)
    .innerJoin(mercadoria.categoria, categoria)
    .groupBy(categoria.descricao)
    .list(new CategoriaGroupMapper(categoria, mercadoria));
}

Por fim, o último trecho desse post exibe uma outra funcionalidade do QueryDSL, a construção de comandos bulk UPDATE via API alto nível. O método updatePrecosByCriterio, define a lógica para aumentar o preço das mercadorias. Essa funcionalidade pode ser acionada a partir da listagem de Mercadorias, o comando UPDATE será construído de acordo com os filtros preenchidos no formulário.
public static long updatePrecosByCriterio(EntityManager em,
    double percentual, FiltrosPesquisaMercadoria filtros) {
  QMercadoria mercadoria = new QMercadoria("m");
  Predicate where = whereByCriterio(mercadoria, filtros);
  
  return new JPAUpdateClause(em, mercadoria)
    .where(where)
    .set(mercadoria.preco, 
      mercadoria.preco.doubleValue().multiply(percentual))
    .execute();
}

Nesse projeto, além de apresentar as funcionalidades do QueryDSL para JPA, utilizei outras tecnologias complementares:
  • Spring Boot: otimiza a organização de dependências do Maven e disponibiliza um container Web embutido (Tomcat) com a aplicação.
  • JQuery: framework Javascript.
  • Foundation: framework css.

Monday, March 30, 2015

Spark: Java Web com MongoDB a partir do método main

O Spark é um micro-container para aplicações Web em Java. Baseado no Sinatra, um ambiente para execução de aplicações Web em Ruby, trata-se de uma tecnologia leve e compacta. O projeto, até o momento, não é apadrinhado por nenhum player (empresa / fundação). A última versão emprega componentes e usa o modelo de programação do Java 8, tornando o código mais limpo e direto.

Trata-se de uma alternativa bem interessante para executar projetos, sem nenhuma burocracia, com foco apenas no código das funcionalidades. O trecho de código a seguir foi extraído do site do projeto, o tradicional HelloWorld, na versão Spark. Parecido com uma aplicação Java pura, o ponto de entrada é o método main. Nele usamos o método get do Spark para definir o fluxo de processamento para uma determinada URL:
import static spark.Spark.get;

public class HelloWorld {
    
    public static void main(String[] args) {
        get("/", (request, response) -> "Bem-vindo ao Spark!");
    }
    
}

Uma vez que a aplicação é executada e a URL http://localhost:4567/ acionada, o código dentro do método main é processado e o texto Bem-vindo ao Spark! apresentado pelo navegador. O Spark usa a porta 4567 por default.

Além do método get, na classe spark.Spark existem diversos métodos para semantica HTTP, como por exemplo: post, put, delete. A estrutura é bem simples, esses métodos possuem uma versão recebendo os argumentos:
  • String path: indica a URL que quando acionada executa o fluxo de código;
  • Route route: uma SAM (Lambda Java 8) que define o método handle responsável pelo fluxo de execução, com os argumentos request e response, retornando Object. 
Com o Maven, o setup do Spark é trivial. Basta definir a dependência para o JAR spark-core, de 89Kb.

Tenho usado o Spark a algum tempo, para implementar projetos pontuais. Vou destacar um projeto, baseado nas demonstrações da YaW, aonde demonstro como utilizar o MongoDB em uma aplicação Java Web. O projeto foi compartilhado e está disponível no meu Github. Outro detalhe sobre esse projeto é que o front-end foi desenvolvido com a tecnologia Freemaker, um template engine responsável por gerar HTML a partir de templates.

A seguir o trecho da classe MercadoriaController, responsável por carregar a lista de mercadorias:
    ...
    get("/", (request, response) -> {
        List mercadorias = mercadoriaDao.getAll();
        
        SimpleHash root = new SimpleHash();
        root.put("activeLista", true);
        root.put("mercadorias", mercadorias);
        
        if (request.params(":error") != null) {
            root.put("error", request.params("error"));
        }
            
        return new ModelAndView(root, "lista_template.ftl");
    }, new CustomFreeMarkerEngine());
    ...

Além de usar os componentes de acesso, o método get faz uso de FreeMarkerEngine para carregar o template Freemaker. A informação do template e o bean, com os dados dinâmicos para gerar o HTML são definidos no componente ModelAndView.

Nesse outro trecho da controller, o código que coloca a mercadoria em edição:
    ...
    get("/edit/:id", (request, response) -> {
        String idMercadoria = request.params(":id");
        Mercadoria mercadoria = null;
        
        try {
            mercadoria = mercadoriaDao.findById(new ObjectId(idMercadoria));
        } catch (Exception ex) {
            response.redirect("/?error=Registro não econtrado!");
            return null;
        }
         
        SimpleHash root = new SimpleHash();
        root.put("activeIncluir", true);
        root.put("mercadoria", new MercadoriaConverter().toRequest(mercadoria));
            
        return new ModelAndView(root, "mercadoria_template.ftl");
    }, new CustomFreeMarkerEngine());
    ...

Além do Freemaker, utilizamos nesse projeto o Bootstrap e JQuery.

No site oficial e no Github do Spark são disponibilizados maiores detalhes sobre o projeto.

@edermag

Monday, March 16, 2015

Instalar e configurar vsftpd (ftp) no Ubuntu

O vsftpd é um servidor FTP, free, para Linux que oferece boa performance e segurança, com configuração relativamente simples. Nesse post eu demonstro como instalar e configurar o vsftpd com usuários virtuais. Usuários que acessam o FTP, mas que não possuem acesso ao Linux (server). O Linux utilizado como referência é o Ubuntu Server 14.04. Os usuários virtuais são persistidos no formato db4 (Berkeley DB).

Primeira etapa é instalar o vsftpd e db-utils (se ainda não estiver instalado):
$ sudo apt-get install vsftpd db-utils

Assim que a instalação do vsftpd estiver concluída, faça um backup do arquivo vsftpd.conf (original):
$ sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.back

Modifique o arquivo vsftpd.conf para que contenha apenas o conteúdo:
$ sudo apt-get install vsftpd db-utils

Assim que a instalação do vsftpd estiver concluída, faça um backup do arquivo vsftpd.conf:
$ sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.back

Modifique o conteúdo do arquivo vsftpd.conf para que contenha apenas as linhas:
listen=YES
anonymous_enable=NO
local_enable=YES

# Usuarios virtuais com os mesmos privilegios de usuarios locais
virtual_use_local_privs=YES
write_enable=YES

# Nome do servico PAM (Pluggable Authentication Modules) - em /etc/pam.d/
pam_service_name=vsftpd.virtual

# Ativa usuarios virtuais
guest_enable=YES

# Define o diretorio home para cada usuario virtual, baseado em um template: /home/{virtual}/$USER
user_sub_token=$USER

# usuario local (raiz), acima dos usuarios virtuais: vftpd - /home/vftp
local_root=/home/vftp/$USER

# Restringindo usuarios as suas respectivas pastas (home)
chroot_local_user=YES
allow_writeable_chroot=YES

# Esconde informacoes do dono
hide_ids=YES

# Habilita o log /var/log/vsftpd.log
xferlog_enable=YES


Agora vamos criar o banco de dados dos usuários virtuais do vsftpd. Para simplificar a manipulação vou criar dois usuários: joao e maria. Ambos serão armazenamos, inicialmente, em um arquivo texto e posteriormente convertidos em arquivo db4. O arquivo texto será organizado em linhas, primeiro o username e depois password. Os arquivos são mantidos no diretório /etc/vsftpd (será necessário criar esse diretório):
$ sudo mkdir /etc/vsftpd
$ sudo vi users.txt

Coloque o seguinte conteúdo no arquivo users.txt:
joao
joao123
maria
maria456

Salve o arquivo e execute as instruções para: converter para formato db4; modificar o acesso ao arquivo:
$ db_load -T -t hash -f users.txt vsftpd-virtual-user.db 
$ chmod 600 vsftpd-virtual-user.db

Se for conveniente, remova o arquivo users.txt. Fique atento para a inclusão de novos usuários, caso deseje remover o arquivo texto.

Próxima etapa é criar o arquivo PAM e indicar que o repositório de usuários (db4):
$ sudo vi /etc/pam.d/vsftpd.virtual

Coloque o seguinte conteúdo no arquivo vsftpd.virtual:
auth       required     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-user
account    required     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-user
session    required     pam_loginuid.so

Agora é o momento de criar a estrutura de pastas no Linux, aonde os arquivos trafegados via FTP serão mantidos. Cada pasta será devidamente vinculada ao usuário, de acordo com as configurações realizadas previamente no vsftpd:
$ mkdir -p /home/vftp/{joao,maria}

Ajustar o dono e o grupo desses diretórios:
$ chown -R ftp:ftp /home/vftp

Repita os dois últimos comandos para cada novo usuário adicionado ao vsftpd.

Última etapa é reiniciar o serviço vsftpd:
$ service vsftpd restart

O teste pode ser via comando ftp do Linux, ou no Firefox via plugin FireFTP. Indique a url, o login (ex joao) e a senha (ex joao123).

Documentação utilizada como referência:
@edermag

Monday, September 16, 2013

Algoritmos de ordenação String (char) e o uso de Collator no Java 7

Um dos temas que eu mais gosto na área da computação é a Análise de Algoritmos, especificamente a ordenação de estrutura de dados. O Java utiliza diversos algoritmos na classificação de dados em sua API, com o foco em unir a eficiência com a performance. Inclusive, no Java 7 foram implementadas algumas melhorias no uso de algoritmos de ordenação. Nesse post eu descrevo as características sobre ordenação de caracteres e String em Java.

Arrays de char

Pra começar, um exemplo simples demonstrando como ordenar os caracteres que compõe uma String:
  String content = "ebdca";
  char[] chars = content.toCharArray();
  
  Arrays.sort(chars);
  
  String sorted = new String(chars);
  System.out.println(sorted); //abcde

A classe Arrays do Java ordena o array chars na ordem natural, alfabética. Mas o que acontece dentro do método sort? Como o array é pequeno o algoritmo utilizado para ordenação é o Insertion Sort, aonde a comparação ocorre no sentido de elementos da esquerda para a direita, garantindo que todos os elementos à esquerda estejam ordenados.

Simulação:

Array original -> e b d c a
Comparação 1 -> b < e, sim  -> Resultado: b e d c a
Comparação 2 -> d < e, sim  -> Resultado: b d e c a
Comparação 3 -> d < b, não
Comparação 4 -> c < e, sim  -> Resultado: b d c e a
Comparação 5 -> c < d, sim  -> Resultado: b c d e a
Comparação 6 -> c < b, não
...

A análise matemática de Insertion Sort varia de acordo com o conteúdo do array. No melhor caso, quando o array já está ordenado, o número de comparações seria linear O(n). Enquanto no pior caso, se os elementos estiverem em ordem decrescente, o número de comparações seria quadrático O( N^2). Em arrays pequenos Insertion Sort é uma ótima opção.

Em um array maior, com mais de 47 caracteres, o algoritmo utilizado é o QuickSort c/ Dual-Pivot. Como o nome diz, trata-se de uma variação de QuickSort com dois pivôs, abordagem parecida com QuickSort 3-way. Alguns estudos apontam que essa estratégia pode reduzir em até 10% o tempo em relação ao QuickSort tradicional. O algoritmo é recente, reformulado em 2009 por Vladimir Yaroslavskiy. O uso desses algoritmos fazem parte de melhorias do Java 7, verifique a classe DualPivotQuicksort.java.

Um detalhe muito importante sobre a comparação de char, é que o Java utiliza o Unicode para determinar a pontução de cada caracter. Nesse caso 'A' vem bem antes de 'a', e 'ã' vai bem depois de 'a'. Modificando o conteúdo da variável content para "abcdeãA", o valor de sorted seria "Aabcdeã". Volto a falar sobre isso no decorrer do post.

Array de String

O próximo trecho de código também é bem simples, nele coloco quatro strings em um array e peço para a classes Arrays realizar a ordenação:
  String content[] = { "Jose", "Andre", "Claudia", "Bruna" };
  
  Arrays.sort(content);
  for (String s: content) {
    System.out.print(s+" ");
  }

Por se tratar de um array pequeno, a API do Java utiliza o Binary Insertion Sort, uma alternativa um pouco mais performática que usa a busca binária (binary search) para determinar a posição correta da troca. No pior caso o número de comparações é linearítmica O(n log n).

Já em arrays maiores, a partir de 32 elementos, o algoritmo de ordenação é o TimSort. TimSort é um algoritmo derivado de Merge Sort e Insertion Sort, no melhor caso o número de comparações é linear O(n), no pior é linearítmico O(n log n). Esse também é um algoritmo recente, descoberto em 2002 por Tim Peters, para a linguagem Python. O uso desses algoritmos também fazem parte de melhorias do Java 7, verifique a classes  TimSort.java e ComparableTimSort.java.

Importante a ressalva de quem define a classificação de cada objeto durante o sort é o método compareTo de Comparable.

Coleções de String

No próximo exemplo uso a coleção TreeSet para organizar algumas Strings:
  Collection<String> content = new TreeSet<>();
  content.add("Jose");
  content.add("Andre");
  content.add("Claudia");
  content.add("Bruna");

O TreeSet mantém os elementos classificados via Comparator ou Comparable, nesse caso através do método compareTo de Comparable. O TreeSet, como em versões anteriores do Java, mantém os elementos em TreeMap, que por sua vez continua utilizando o Red-Black Tree como algoritmo de ordenação. Por manter os elementos em uma ãrvore balanceada, esse algoritmo é extremamente eficiente, suas as operações são logaritmicas O(log n).

O trecho a seguir, um exemplo de ordenação em uma lista de String:
  List<String> content = new ArrayList<>();
  content.add("Jose");
  content.add("Andre");
  content.add("Claudia");
  content.add("Bruna");
  Collections.sort(content);

Em listas o Java usa o mesmo mecanismo de ordenação aplicado em arrays. O método sort de Collections usa o toArray da lista p/ recuperar os elementos contidos em um array, realiza a ordenação do array (Arrays.sort) e por fim atualiza os elementos na lista de acordo com resultado da ordenação.

O compareTo não funciona como o esperado?

A classificação natural da String é a ordem alfabética (lexicographically), ela ocorre através do método compareTo de Comparable. Esse método funciona perfeitamente quando não há necessidade de usar caracteres especiais, ou diferenciar letras minúsculas e maiúsculas. No começo do post, citei algumas dificuldades com o uso de caracteres com acentuação e maiúsculas com char, o mesmo vale para a String!

Veja um exemplo:
  String content[] = { "Andrei", "Andréa", "Cláudia", "Claudio" };
  Arrays.sort(content);

O resultado desse sort é: "Andrei", "Andréa", "Claudio" e "Cláudia".

Uma solução simples para esse tipo de problema é usar classe Collator, introduzida a partir do Java 5. Collator é uma classe abstrata, que implementa Comparator e define um conjunto de regras para realizar a comparação entre strings a partir de um determinado locale. Os principais idiomas são suportados por Collator, através de sua classe filha RuleBasedCollator. Uma sobrecarga do método sort, de Arrays, recebe um Comparator como argumento viabilizando o uso de Collator.
Veja:
  String content[] = { "Andrei", "andréa", "Cláudia", "Claudio" };
  Collator colPtBr = Collator.getInstance(new Locale("pt_BR"));
  Arrays.sort(content, colPtBr);

Note o valor "andréa", usando somente letras minúsculas. Agora o resultado do sort é: "andréa", "Andrei", "Cláudia" e "Claudio".

O Collator em pt_BR determina que caso duas palavras sejam semelhantes, variando apenas a acentuação, a palavra com acento vem logo após a palavra sem acento. Veja o resultado de comparações via Collator e Comparable com algumas Strings:
  System.out.println(colPtBr.compare("Andréa", "andrea"));  // 1: Andréa após andrea
  System.out.println("Andréa".compareTo("andrea"));  // -32: Andréa antes andrea

  System.out.println(colPtBr.compare("maca", "maçã"));  // -1: maca antes de maçã
  System.out.println("maca".compareTo("maçã"));  // -132: maca antes de maçã 

Mais um detalhe sobre o Collator pt_BR, é em relação a letras minúsculas e maiúsculas. No caso de duas strings semelhantes, variando apenas letras minúsculas e maiúsculas, o Collator assume que a String com letra minúscula fica antes da String com maiúscula. Veja:
  System.out.println(colPtBr.compare("pedro", "PEDRO")); // -1: pedro antes de PEDRO
  System.out.println("pedro".compareTo("PEDRO")); // 32: pedro após PEDRO

A classe Collator permite o uso de estragégias "mais fortes" de comparação. No método setStrength é possível, por exemplo, pedir ao Collator que as diferenças entre acentuação, maiúsculas e minúsculas sejam ignoradas. Veja um exemplo:
  colPtBr.setStrength(Collator.PRIMARY);
  
  System.out.println(colPtBr.compare("pedro", "PEDRO")); // 0
  System.out.println(colPtBr.compare("Andréa", "andrea"));  // 0
  System.out.println(colPtBr.compare("maca", "maçã"));  // 0

Em casos mais simples, cuja a ordenação deve apenas desconsiderar diferenças entre letras maísculas e minúsculas, é possível usar um Comparator pronto e disponível na classe String: a constante CASE_INSENSITIVE_ORDER.
  String content[] = { "A", "x", "c", "B", "d", "a" };
  Arrays.sort(content, String.CASE_INSENSITIVE_ORDER);

Resultado da ordenação é: A a B c d x

Collator vs performance

O uso em de Collator em grande volume de objetos pode degradar a performance. Uma alternativa para reverter esse problema é utilizar a classe CollationKey, que armazena a chave binária em representação a uma string. Veja como funcionaria essa estratégia:
  String content[] = { "Andrei", "andréa", "Cláudia", "Claudio" };
  CollationKey[] collKeys = new CollationKey[content.length];
  
  Collator colPtBr = Collator.getInstance(new Locale("pt_BR"));
  for (int i = 0; i < collKeys.length; i++) {
    //guarda a chave da string
    collKeys[i] = colPtBr.getCollationKey(content[i]);
  }
  
  Arrays.sort(collKeys); //ordeno as chaves do collator
  
  for (int i=0; i < collKeys.length; i++) {
    System.out.print(content[i]+" ");
  }

O truque desse último trecho é colocar as chaves geradas pelo Collator em um array, ordenar esse array e por fim exibir o as strings de acordo com a ordem das chaves.

Coloquei boa parte do código desse post no github.

@edermag

Wednesday, September 04, 2013

Como extrair um diretório de um Zip c/ Java

Exemplo de um programa simples, com Java 7, para extrair um diretório (e filhos se existir) contido em um arquivo zip (tar / war / ear / jar ...).
public class ExtractZipDir {
 
  public static void main(String[] args ) {
    if (args.length < 2) {
      System.out.println("Informe o arquivo zip e a pasta");
      return;
    }
    
    String zipName = args[0];
    String dirName =  args[1];
    extract(zipName, dirName);
  }
  
  static void extract(String zipName, String dirName) {
    long before = currentTimeMillis();
    try (ZipFile zip = new ZipFile(new File(zipName))) {
      ZipContent zContent = new ZipContent(zip);
      int files = zContent.extract(dirName);
      System.out.printf("Extraiu %s arquivos (e, %sms)", files,
        currentTimeMillis() - before);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
    
  private static class ZipContent {
    private final ZipFile zip;
    private Map<String, List<ZipEntry>> content = new HashMap<>();
    private int count;
    
    private static final String SEPARATOR = "/";
    private static final String ROOT_DIRECTORY = ".";
    private static final int BUFFER = 2048;
    
    private ZipContent (ZipFile zipFile) {
      zip = zipFile;
      organizeEntries(zip.entries());
    }
      
    private void organizeEntries(Enumeration<? extends ZipEntry> entries) {
      putRootDir();
       
      while (entries.hasMoreElements()) {
        ZipEntry entry = entries.nextElement();
        putEntry(entry);
      }
    }
      
    private void putEntry(ZipEntry entry) {
      String path = extractDirNameFromEntry(entry);
      List<ZipEntry> entries = content.get(path);
      if (entries == null) {
        content.put(path, new LinkedList<ZipEntry>());
      } else {
        entries.add(entry);
      }
    }
    
    private void putRootDir() {
      content.put(ROOT_DIRECTORY, new LinkedList<ZipEntry>());
    }
    
    private List<ZipEntry> listContent(String folder) {
      return content.get(folder);
    }
    
    private boolean hasDirectory(String folder) {
      return content.keySet().contains(folder);
    }
    
    private Queue<String> getAllDirectoriesFromParent(String dir) {
      Queue<String> q = new LinkedList<>();
      for (String d: content.keySet()) {
        if (d.contains(dir) && !d.equals(dir))
          q.add(d);
      }
      q.add(dir);
      return q;
    }
      
    private int extract(String dir) {
      if (!dir.equals(ROOT_DIRECTORY)) {
        if (!dir.endsWith(SEPARATOR)) {
          dir = dir.concat(SEPARATOR);
        }
        
        if (!hasDirectory(dir)) {
          throw new RuntimeException("Directory not found!");
        }
      }
      
      count = 0;
      Queue<String> dirs = getAllDirectoriesFromParent(dir);
      while (!dirs.isEmpty()) {
        String path = dirs.poll();
        List<ZipEntry> entries = listContent(path);
        File parent = new File(path);
        parent.mkdirs();
        
        for (ZipEntry entry: entries) {
          extract(parent, entry);
          count++;
        }
      }
      return count;
    }
      
    private void extract(File path, ZipEntry e) {
      String fileName = extractFilenameFromEntry(e);
      File destFile = new File(path, fileName);
      try (BufferedInputStream is = new BufferedInputStream(zip.getInputStream(e));
           FileOutputStream fos = new FileOutputStream(destFile);
           BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER)) {
        int currentByte;
        byte data[] = new byte[BUFFER];
        
        while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
          dest.write(data, 0, currentByte);
        }
        dest.flush();
      } catch (IOException e) {
        throw new RuntimeException("Não foi possível extrair!\n" +
          e.getMessage(), e);
      }
    }
    
    private static String extractDirNameFromEntry(ZipEntry entry) {
      String name = entry.getName();
      if (name.lastIndexOf(SEPARATOR) != -1) {
        return name.substring(0, name.lastIndexOf(SEPARATOR)+1);
      }
      return ROOT_DIRECTORY;
    }
    
    private static String extractFilenameFromEntry(ZipEntry entry) {
      String name = entry.getName();
      if (name.lastIndexOf(SEPARATOR) == -1) {
        return name;
      }
      return name.substring(name.lastIndexOf(SEPARATOR)+1);
    }
  }
}

Alguns formas de como executar esse programa (Linux / Windows):
$ java ExtractZipDir /home/user/meu.zip dir
$ java C:\usuario\Documents\meu.zip dir

Compartilhei esse código no github.

@edermag

Thursday, August 15, 2013

Problemas c/ jar assinado pós upgrade do Java 7: invalid SHA1 signature file digest

Durante a migração de um projeto, ou melhor atualização, do Java 6 para o 7, encontrei alguns problemas relacionados certificação dos jars. Um módulo desse projeto é em Swing, instalado no cliente via Java Web Start. A aplicação swing é formada por vários jars, que por sua vez devem ser assinados com o certificado definido pela empresa. Depois de atualizar o workspace e o build, na inicialização da aplicação o Web Start lançou uma java.io.IOException: invalid SHA1 signature file digest for...

O jarsigner mudou no Java 7, o algoritmo default da assinatura passou a ser o SHA-256. No Java 6 era o SHA-1. Esse foi o motivo do erro, na verdade existiam alguns jars usando as duas assinaturas, uma para SHA1 e outra SHA256 (é possível visualizar isso no Manifest.MF).

A solução do problema foi deixar explicito no jarsigner, o uso do algoritmo correto, através da propriedade -digestalg SHA1. Veja:
$ jarsigner -keystore .... -digestalg SHA1

No ANT, é possível definir a propriedade na task signjar:
<signjar ... digestalg="SHA1">
  ...
</signjar>

E por fim, como indicar essa propriedade no Maven:
  <!-- omiti o restante do pom.xml -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jarsigner-plugin</artifactId>
        <version>1.2</version>
        <executions>
          <execution>
            <id>sign</id>
            <phase>package</phase>
            <goals>
              <goal>sign</goal>
            </goals>
          </execution>
        </executions>
 
        <configuration>
          <keystore>...</keystore>
          <alias>...</alias>
          <storepass>...</storepass>
          <keypass>...</keypass>
    <digestalg>SHA1</digestalg>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...

Em outro post demonstro como usar o jarsigner pelo Maven.

@edermag

Thursday, August 08, 2013

Como executar testes no JMeter (remoto) a partir do ANT

Dica útil para projetos aonde os builds são realizados pelo ANT. O projeto jmeter-ant-task define uma task customizada do ANT, para executar testes (stress / carga / ...) com o JMeter.

Na task jmeter existem diversos atributos utilizados para indicar qual é o diretório de instalação do JMeter, qual é o diretório com os planos de testes (arquivos jmx), qual diretório deve conter resultados dos testes devem ser armazenados e outros.

O trecho de build.xml a seguir demonstra como utilizar a task para executar o plano de testes MyTest.jmx. 
<project name="MyProject" basedir=".">

  <!-- define uma var que guarda a url de app qualquer -->
  <property name="app.url" value="htpp://localhost:8080/myapp" />

  <!-- vars utilizadas pelo jmeter -->
  <property name="jmeter-home" value="/opt/jmeter" />
  <property name="jmeter-lib-dir" value="${jmeter-home}/lib" />
  <property name="jmeter-results" value="/home/app/jmeter-results" />
  <property name="jmeter-tests" value="/home/app/jmeter-tests" />

  <!-- Carrega a task do JMeter -->
  <taskdef name="jmeter" classpathref="apache.ant.ext.lib.classpath" 
    classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask" />
  
  <target name="jmeter.run" depends="" >
    <echo>Acionando o JMeter local p/ os testes de stress</echo>
    <jmeter jmeterhome="${jmeter-home}" testplan="${jmeter-tests}/MyTest.jmx"
     resultlogdir="${jmeter-results}/jtl" jmeterlogfile="${jmeter-results}/log">
     
      <property name="app.url" value="${app.url}"/>

      <!-- passa argumentos p/ a vm do jmeter -->
      <jvmarg value="-Xincgc"/>
      <jvmarg value="-Xmx128m"/>
    </jmeter>
  </target>

  ...
</project>

No inicio do build são definidas variáveis utilizadas pelo o script, boa parte delas na task jmeter. Um detalhe sobre o plano de teste é que ele depende de uma variável que indica a url da aplicação, app.url. Note o uso da tag property, nela eu repasso a variável definida no build ANT, a ${app.url} com o endereço da aplicação. Dessa é possível enviar informações para o JMeter, sem manipular o arquivo jmeter.properties. Na tag jvmarg você pode indicar parâmetros para a VM do JMeter.

O target a seguir simila a execução do Jmeter remoto, em outro servidor. Para executar o JMeter remoto, é necessário pelo menos dois nós (instâncias) do JMeter, uma cliente e outra servidor, veja mais na documentação do projeto. O próximo target aciona o JMeter cliente, na mesma máquina que o build está sendo executado, indicando a propriedade remote_hosts. Dessa forma a instância cliente irá acionar o JMeter servidor nesse endereço indicado.
...
  <!-- host/ip para o jmeter remoto -->
  <property name="jmeter-host" value="192.168.1.18"/>
  <property name="jmeter-port" value="1099"/>

  <target name="jmeter.run.remote">
    <echo>Acionando o JMeter remoto p/ os testes de stress</echo>
    <jmeter jmeterhome="${jmeter-home}" jmeterlogfile="${jmeter-results}/log" resultlogdir="${jmeter-results}/jtl" runremote="true">
      <!-- outra forma de definir os planos de testes -->
      <testplans dir="test/jmeter/jmx" includes="MyTest.jmx"/>

      <!-- indica que essa property deve ser enviada ao jmeter server -->
      <property name="app.url" remote="true" value="${app.url}"/>

      <!-- indica a url do jmeter server -->
      <property name="remote_hosts" value="${jmeter-host}:${jmeter-port}"/>
    </jmeter>
  </target>
...

Note que a propriedade app.url foi marcada como remote="true", dessa forma o JMeter cliente repassa o valor para o JMeter servidor. Mais informações sobre essa task foram disponibilizadas na página do projeto.

http://twitter.com/edermag
http://www.yaw.com.br

Thursday, August 01, 2013

Overview das ferramentas do Spring para persistência de dados

Durante o desenvolvimento ou manutenção (principalmente) de um projeto de software, seja qual for o estilo, é sempre delicado lidar com os componentes de infra-estrutura responsáveis pela camada de persistência.

Vou descrever alternativas que o Spring Framework oferece para simplificar esse trabalho, em cenários variados, como o foco em base de dados relacional. Ou seja, um overview das ferramentas do Spring para trabalhar com os componentes de persistência.

Código legado, bem legado...

Projetos Java antigos, desenvolvidos a + de 8 anos, normalmente não utilizam uma solução ORM (Mapeamento Objeto Relacional). Nesses projetos é muito comum o uso de bibliotecas "caseiras", escritas in house, para otimizar o uso do JDBC.

O Spring JDBC é um modulo do Spring interessante para esse tipo de cenário. Com ele é possível reduzir consideravelmente o volume de código JDBC. O principal componente do Spring JDBC é o JdbcTemplate, ele disponibiliza uma série de métodos para operações CRUD, consultas e comandos em lote. Para tirar proveito do uso contextos, injeção de dependências e inversão de controle, faz todo o sentido trabalhar em conjunto com o Spring Bean. Dessa forma seria possível injetar a referência do JdbcTemplate nos componente DAO (pattern Data Access Object).

O código a seguir demonstra um fragmento do DAO que utiliza o JdbcTemplate para inserir/atualizar uma entidade (Mercadoria).
@Component
public class MercadoriaDAO {

  //SQL
  private final static String INSERT_MERCADORIA = "INSERT INTO mercadoria (nome,descricao,preco,quantidade) VALUES (?,?,?,?)";
  private final static String UPDATE_MERCADORIA = "UPDATE mercadoria SET nome = ?, descricao = ?, preco = ?, quantidade = ? WHERE id = ?";
  private final static String GET_MERCADORIA_BY_ID = "SELECT * FROM mercadoria WHERE id = ?";
  private final static String GET_MERCADORIAS_BY_NOME = "SELECT * FROM mercadoria WHERE nome like ?";
  
  @Autowired
  private JdbcTemplate jdbcTemplate;
  
  public void save(Mercadoria m) {
    if (m.getId() == null) {
      jdbcTemplate.update(INSERT_MERCADORIA, 
        new Object[]{ m.getNome(), m.getDescricao(), m.getPreco() });
    } else {
      jdbcTemplate.update(UPDATE_MERCADORIA,
        new Object[]{ m.getNome(), m.getDescricao(), m.getPreco(), m.getId() });
    }
  }
  ...
}

Além do método update também pode ser utilizado para realizar a remoção da entidade. No trecho de código a seguir coloco dois exemplos de consultas, utilizando JdbcTemplate. Note que na consulta utilizamos o componente RowMapper, o MercadoriaRowMapper. O RowMapper é utilizado pelos métodos query de JdbcTemplate, ele lê os dados do ResultSet e faz a transformação em uma instância de Mercadoria.

O método queryForObject é utilizado para retornar uma instância da entidade (ou null) com filtro por id, por exemplo. Enquanto o método query retorna uma lista de objetos que podem ser encontrados de acordo com o SQL.
  ... //ainda em MercadoriaDAO
  
  private class MercadoriaRowMapper implements RowMapper<Mercadoria> {

    public Mercadoria mapRow(ResultSet rs, int row) throws SQLException {
      int id = rs.getInt("id");
      String nome = rs.getString("nome");
      String descricao = rs.getString("descricao");
      double preco = rs.getDouble("preco");
   
      return new Mercadoria(id, nome, descricao, qtde, preco);
    }
  }

  public Mercadoria findById(Integer id) {
    return jdbcTemplate.queryForObject(GET_MERCADORIA_BY_ID,
      new Object[] { id }, new MercadoriaRowMapper());
  }
  
  public List<Mercadoria> getMercadoriasByNome(String nome) {
    return jdbcTemplate.query(GET_MERCADORIAS_BY_NOME,
      new Object[] { nome + "%" }, new MercadoriaRowMapper());
  }
  ...

Outra estratégia, que não me agrada, seria fazer no DAO a Mercadoria uma herança para JdbcDaoSupport, e acessar o JdbcTemplate encapsulado nesse componente. Com Spring JDBC o código DAO pode ficar bem mais compacto. Para ilustrar isso, compare o DAO c/ JDBC puro e o DAO utilizando Spring JDBC. Veja também as configurações do Spring e o o pom.xml com as dependências para esses módulos.

Projetos com Hibernate

O Spring também oferece soluções para reduzir o esforço e agregar funcionalidades durante o desenvolvimento de projetos Java utilizando soluções ORM, como Hibernate ou JPA. O Spring ORM é outro módulo da suíte Spring, ele oferece funcionalidades para facilitar o uso de soluções baseadas em Mapeamento Objeto Relacional.

Em versões antigas do Hibernate, era trabalhoso manter a Session vinculada ao contexto de execução, por exemplo utilizar a mesma instância em diferentes DAOs dentro do mesmo fluxo de request. Por isso o Spring criou o HibernateTemplate, com a proposta de disponibilizar a Session corrente ao contexto de execução.

Mas a partir do Hibernate 3.0.1 com contextual sessions, isso não é mais necessário. Nas versões mais recentes do Spring é possível usar a Session no contexto de execução, além de centralizar as configurações e realizar a injeção da SessionFactory nos DAOs.

A seguir o código do DAO da Mercadoria com Hibernate:
@Component
public class MercadoriaDAO {

  @Autowired
  private SessionFactory sessionFactory;
  
  private final Session getCurrentSession(){
    return this.sessionFactory.getCurrentSession();
  }
  
  public void save(Mercadoria m) {
    if (m.getId() == null) {
      this.getCurrentSession().persist(m);
    } else {
      this.getCurrentSession().merge(m);
    }
  }
  
  public Mercadoria findById(Integer id) {
    return (Mercadoria) this.getCurrentSession().get(Mercadoria.class, id);
  }
  
  public List<Mercadoria> getMercadoriasByNome(String nome) {
    return this.getCurrentSession()
      .createQuery("from model.Mercadoria m where m.nome like ?")
      .setParameter(0, nome+"%")
      .list();
  }
  ...
}

O spring-config.xml a seguir centraliza no Spring as configurações com banco de dados e do Hibernate.  Nesse exemplo o banco de dados utilizado é o HSQLDB (local).
<?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"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
  <context:component-scan base-package="." />
  
  <!-- SessionFactory, DataSource, ... -->

  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="ds" />
    <property name="packagesToScan" value="model" />
  
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.hbm2ddl.auto">create</prop>
      </props>
    </property>
  </bean>
  
  <bean id="ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:file:mercadoria"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>
  ...
</beans>

Projetos com JPA++

O Spring também oferece funcionalidades bem interessantes para projetos que utilizam a Java Persistence API (JPA), através do módulo Spring Data JPA. Através da interface JpaRepository o Spring define os métodos de consulta e CRUD. O desenvolvedor trabalha de forma alto-nível, criando uma interface de persistência, enquanto a implementação fica por conta do próprio Spring. Outro elemento do Spring Data JPA é a anotação @Query, responsável por informar consultas customizadas via JPAQL.

Para esse tipo de componente o Spring adota o pattern Repository, que abstrai uma coleção de objetos com o design mais próximo ao domínio da aplicação do que com o banco de dados. Veja como ficaria a interface MercadoriaRepository:
public interface MercadoriaRepository extends JpaRepository<Mercadoria, Integer> {

  @Query("select m from Mercadoria m where m.nome like ?1")
  List<Mercadoria> getMercadoriasByNome(String nome);

}

Uma vez que a interface foi definida, o Spring cria um proxy que implementa os métodos de persistência. Esse proxy será injetado em classe de negócio/controller pelo Spring. Veja o exemplo:
@Component
public class MercadoriaService {

  @Autowired
  private MercadoriaRepository repo;
  
  public void save(Mercadoria m) {
    m.save(m);
  }
  ...
}

Nesse exemplo demonstrei funcionalidades básicas da interface JpaRepository, mas existem outrs funcionalidades como o suporte a paginação da consulta SQL, veja esse exemplo. No github compartilhamos outros dois projetos que utilizam Spring Data JPA, uma aplicação web e outra desktop. Ambas fazem uso do JpaRepository.

Um pouco além: NoSQL

Na verdade esse módulo compõe o Spring Data, uma solução "guarda-chuva" com o objetivo de unificar e simplicar o armazenamento de dados em bancos relacionais e NoSQL. Abaixo desse projeto existe o módulo Spring Data MongoDB, responsável por abstrair o acesso ao MongoDB. Não é o proposito desse post abordar soluções NoSQL, mas para ter uma idéia o trecho de código a seguir demonstra repositório da Mercadoria (como Document) em versão MongoDB:
public interface MercadoriaRepository extends MongoRepository<User, String> { 
  
  @Query("{ nome: ?0 }")
  List<User> getMercadoriasByNome(String nome);
  
}

Abaixo do Spring Data ainda existem sub-projetos para Neo4j, Apache Hadoop, REST e outros. É possível saber um pouco mais sobre essa tecnologia, em artigo introdutório sobre Spring Data no InfoQ Brasil.

Esse é apenas um resumo de algumas soluções oferecidas pelo Spring Framework para resolver questões relacionadas a persistência em projetos Java. Sou da turma que gosta do Spring e de Java EE. Acredito que tirando proveito das melhoras funcionalidades das duas stacks, aumentamos o nosso poder fogo e logo a possibilidade de desenvolver um projeto com sucesso.

http://twitter.com/edermag
http://www.yaw.com.br

Saturday, July 20, 2013

Testes integrados com JUnit, Maven e Jetty

Nesse post descrevo uma alternativa para configurar e executar testes unitários e integrados com JUnit através do Maven. O Maven, em seu ciclo de build, define a fase (phase) integration-test. Essa fase realiza os procedimentos do deploy com a possibilidade de executar rotinas de testes após a implantação do artefato.

O contexto que utilizo aqui é de uma aplicação web com serviços REST. Nesse caso seria útil implementar testes de integração para testar o funcionamento desses serviços REST. É possível no mesmo projeto, dentro do diretório src/test/java implementar rotinas de testes unitários e integrados. No Maven podemos segmentar os tipos de testes, na configuração do plugin maven-surefire-plugin. No pom.xml indicamos quais são os testes que devem ser executados após o deploy, na fase integration-test.

A seguir o techo do pom.xml, aonde configuro o plugin, indicando quais são os componentes de teste unitário e quais são de testes integrados.
<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/xsd/maven-4.0.0.xsd">
...
  <build>
    ...

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.15</version>
        <configuration>
          <excludes>
            <exclude>**/integration/*.java</exclude>
          </excludes>
       </configuration>
       <executions>
         <execution>
            <id>integration-test</id>
            <goals>
              <goal>test</goal>
            </goals> 
            <phase>integration-test</phase>
            <configuration>
              <excludes>
                <exclude>none</exclude>
              </excludes>
              <includes>
                <include>**/integration/*.java</include>
              </includes>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

...
</project>

A estratégia foi criar um pacote com conteúdo integration, e nele armazenar os componentes que implementam os testes de integração. Para executar os testes integrados bastar executar a instrução:
mvn integration-test

O build do Maven valida, compila, faz o pacote, realiza o deploy do pacote e por fim processa os testes integrados. Compartilhei no meu github um projeto, uma aplicação web c/ RESTEasy, que utiliza essa alternativa para processar testes integrados com Maven.

http://twitter.com/edermag
http://www.yaw.com.br

Wednesday, July 17, 2013

Maven e Jetty uma excelente combinação p/ desenv web

Minhas experiências no desenvolvimento de projetos web com Maven e Jetty, até agora, foram positivas. Essa combinação é eficiente e produtiva, favorece o fluxo de trabalho codifica + compila + executa + testa.

Trabalhar com o Jetty, em um projeto gerenciado pelo Maven, é algo bem simples. Basta configurar o pom.xml com o plugin jetty-maven-plugin. O trecho a seguir é um exemplo de como habilitar o plugin no 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/xsd/maven-4.0.0.xsd">

  ...

  <build>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>8.1.11.v20130520</version>
      </plugin>
    </plugins>
  </build>

</project>

Após configurar o Maven, existem várias formas para trabalhar com o Jetty. Um dos goals mais interessantes é o jetty:run, com ele o Maven roda o projeto sem gerar o war, ou seja, os fontes são implantados "diretamente" no Jetty:
mvn jetty:run

Depois dessa instrução o Jetty é inicializado (por default na porta 8080) e os fontes do projeto são implantados, acessível pela url http://localhost:8080/. É possível encerrar a execução do Jetty com ctrl-c na console, ou em alguma outra aba acionar o goal jetty:stop, veja:
mvn jetty:stop

Essa estratégia é interessante para produtividade durante o desenvolvimento, caso alguma classe seja modificada o plugin do Jetty implanta a mudança, realiza o hot deploy.

Outra alternativa é executar o Jetty e realizar o deploy do war, após a geração do pacote pelo Maven.
mvn jetty:run-war

Essa abordagem faz sentido quando não é necessário realizar hot deploy de mudanças após o deploy no Jetty.

O plugin suporta diversas configurações, como por exemplo a porta de execução do Jetty e tempo para verificar fontes (hot deploy). Veja um exemplo do plugin com configurações complementares:
...
  <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.11.v20130520</version>
    <configuration>
      <scanIntervalSeconds>10</scanIntervalSeconds>
      <webApp>
        <!-- Contexto da aplicação -->
        <contextPath>/${project.artifactId}</contextPath>
      </webApp>
      <connectors>
        <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
          <!-- Porta do Jetty -->
          <port>8000</port>
          <maxIdleTime>60000</maxIdleTime>
        </connector>
      </connectors>
    </configuration>
  </plugin>
...

De acordo com a configuração do plugin acima, o Jetty vai rodar na porta 8000 utilizando o nome do artefato como contexto web, sendo que cada conexão com container (Socket) tem um timeout de 60 segundos. Além disso o plugin verifica modificações no código a cada 10 segundos, para realizar o hot deploy.

Compartilhei um projeto Java web no github, que demonstra como funciona a integração do Maven com o Jetty. Saiba mais detalhes sobre o jetty-maven-plugin, na página do plugin.

http://twitter.com/edermag
http://www.yaw.com.br

Wednesday, July 10, 2013

Configurar o Datasource do MySQL no Jetty

O Jetty é um web server e servlet container, com ele é possível servir conteúdo estático e/ou dinâmico (Java EE Web). Trata-se de um projeto open source, atualmente mantido pela fundação Eclipse, uma solução escolhida por muitos desenvolvedores pela simplicidade e eficiência.

No Jetty é possível configurar datasource com o banco de dados de várias formas. Aqui nesse post, vou descrever os passos para realizar a configuração do datasource do MySQL no Jetty, e como acessá-lo via JPA/Hibernate.

Uma boa prática é utilizar um pooling de conexões com o banco de dados na definição de datasources, otimizando o consumo de memória e velocidade de requisições com o banco. Por isso vou utilizar um plugin c3p0 com Jetty na configuração do datasource.

A estratégia é adicionar um novo arquivo de configuração, xml, na pasta WEB-INF do projeto web. O nome do arquivo com a configuração do datasource é jetty-env.xml. Veja o exemplo
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure 
  PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" 
  "http://jetty.mortbay.org/configure.dtd">

<Configure id="wac" class="org.eclipse.jetty.webapp.WebAppContext">

  <!-- MySQL DataSource -->
  <New id="MysqlDS" class="org.eclipse.jetty.plus.jndi.Resource">
    <Arg></Arg>
    <Arg>jdbc/MysqlDS</Arg>
    <Arg>
      <New class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <Set name="driverClass">com.mysql.jdbc.Driver</Set>
        <Set name="jdbcUrl">jdbc:mysql://localhost:3306/db</Set> <!--url jdbc-->
        <Set name="user">root</Set> <!-- usuario do banco de dados -->
        <Set name="password">root</Set> <!-- senha do banco de dados -->
      </New>
    </Arg>
  </New>
</Configure>

Note que que no jetty-env.xml defino o JNDI name jdbc/MySQL, configure as as credenciais e a url jdbc de acordo com sua instalação mySQL. Agora é necessário fazer a busca do datasource pelo JNDI name configurado, a seguir o exemplo de como fazer em isso em JPA, no arquivo persistence.xml (JPA):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="estoqueUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source>jdbc/MysqlDS</non-jta-data-source>
    <properties>
      <!-- configurações do Hibernate -->
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
    </properties>
  </persistence-unit>
</persistence>

Um detalhe importante, é necessário garantir os jars do driver JDBC MySQL e c3p0 na distribuição do projeto web (WEB-INF/lib). No Maven basta apenas adicionar as duas dependências no pom.xml, como a seguir:
<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/xsd/maven-4.0.0.xsd">

  ...

  <dependencies>
    ...
    <!-- MySQL JDBC -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.25</version>
    </dependency>

    <!-- C3p0 -->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
    ...
  </dependencies>

  ...
</project>

Mais detalhes na documentação do Jetty.

http://twitter.com/edermag
http://www.yaw.com.br