Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Monday, June 22, 2015

Explorando o Docker para construir ambientes Java

Containers

Agilidade, produtividade e qualidade são termos constantes no contexto de desenvolvimento de sistemas. O que ao primeiro momento, de forma enganosa, nos remete apenas a escrita de código em si. Na verdade existem diversos outros fatores que impactam diretamente na agilidade de um time de desenvolvimento. Fatores que ultrapassam as fronteiras da escrita de código, como por exemplo o provisionamento da infra-estrutura.

Analisando a questão do provisionamento, a virtualização foi uma das alternativas para reduzir os custos e otimizar a infra-estrutura, principalmente com o crescimento e adoção dos serviços em Cloud Computing (PaaS). Mas existem outras alternativas nesse mesmo campo. O Docker, por exemplo, é uma plataforma para construir e manter ambientes para a excecução de sistemas distribuídos.

A plataforma é formada por diversor módulos, como o Docker Engine e o Docker Hub. Ela opera sob o conceito de containers do LXC sigla de Linux Containers, que atua de forma diferente das máquinas virtualizadas. Ao invés de isolar um o SO inteiro o Linux host compartilha seu kernel para outros processos isolados rotulados como Container. O Docker Engine é uma camada sobre o LXC, e permite que usuários criem e executem um ou vários Containers, simulando um SO 'puro', abaixo de um mesmo kernel compartilhado pelo Linux host. Um processo mais leve do que a virtualização de máquinas.

Dessa forma eu poderia montar dois ambientes Java distintos, na mesma máquina, em poucos passos. Por exemplo: o primeiro Container configurado para executar uma aplicação usando o Java 7, Tomcat e Gradle no CentOS; o segundo Container configurado para executar uma aplicação usando o Java 8, Jetty e Maven no Ubuntu. Esses dois Containers poderiam ser mantidos um Mac OS X Yosemite como host (no Mac OS ou Windows será necessário instalar um boot2docker).

No Docker o Container é tratado como o artefato, ou seja, o ambiente pode ser versionado e distribuído da mesma forma como fazemos com o código fonte da aplicação. Antes de explorar o Docker, é importante compreender como funcionam três tipos de componentes:
  1. Image: uma imagem é um template que define a estrutura para Containers.
  2. Container: o Container simula o ambiente de execução como um todo, é uma 'instância' da imagem.
  3. Repository / registry: registro aonde as imagens são mantidas (versionamento). O Docker Hub é uma central aonde imagens podem ser persistidas e compartilhadas.

Primeiro Container Java

A partir desse ponto irei demonstrar como trabalhar com Containers do Docker, construindo ambientes para execução de aplicações Java. Para simular os passos seguintes é necessário que você instale o Docker e faça o HelloWorld para testar a ferramenta.

No início vou trabalhar com uma imagem do Java 8, disponibilizada (public) no Docker Hub. Essa imagem trata-se de um Ubuntu 14.04 com o JDK versão 8, disponibilizado pelo OpenJDK. Para baixar a imagem do Docker Hub use a instrução:
$ sudo docker pull java:8

No comando docker pull informei o conteúdo java:8, aonde java representa o repositório, : o separador, e 8 a versão. Após concluir o download, é possível verificar a imagem através do comando:
$ sudo docker images

A lista apresenta o repositório, a versão e tamanho da imagem. O id é uma informação que pode ser usada para remover a imagem do host. A próxima etapa é criar um Container a partir dessa imagem. e usar de alguma forma o JDK: 
$ sudo docker run java:8 java -version

O comando docker run cria a instância da imagem, o Container. O conteúdo java:8 indica qual imagem deve ser usada, e por fim a instrução que deverá ser processada pelo container. Utilizei o comando java -version como exemplo. Assim que a versão do Java é impressa na console, o processo Docker é encerrado.

Para listar todos os Containers, ativos ou que foram concluídos, use a instrução:
$ sudo docker ps -a

Note que a primeira linha exibe o Container criado para a imagem java:8, veja o valor na coluna NAMES. Trata-se de um apelido gerado pelo Docker para identificarmos o Container. Caso você execute novamente o comando docker run, um novo container será criado. Para evitar isso basta usar a opção --rm, que descartará o Container no fim da execução.

A próxima instrução criará um Container para executar uma classe Java. Como exemplo usei o clássico HelloWorld, que exibe uma mensagem na console. Importante notar que a classe já foi compilada, e o arquivo class está no filesystem do host, fora do Container:
$ sudo docker run --rm -v "$PWD":/home/user/test -w /home/user/test java:8 
  java OlaDocker

A opção -v indica o volume que é carregado para o Container, e -w o diretório de trabalho. No exemplo as duas opções apontam para o mesmo diretório (/home/user/test), que contém o arquivo OlaDocker.class. Ajuste a instrução indicando o path e o nome adequado da sua classe Java.

Docker para dev Java Web

Próximo passo é executar uma aplicação Java Web com Docker. Para isso vou usar uma imagem que criei com as seguintes característica: Tomcat 8, JDK 8 (Oracle), Maven 3.3 e Ubuntu 14.04. O conteúdo a seguir é um exemplo do Dockerfile, usando a imagem edermag/tomcat-8-dev, disponível no Docker Hub. 
FROM edermag/tomcat-8-dev

WORKDIR /source

ENV app appJavaWeb
ADD pom.xml /source/pom.xml
ADD src /source/src

RUN mvn clean package && \
    mv /source/target/$app-0.0.1-SNAPSHOT.war $CATALINA_HOME/webapps/          

CMD ["catalina.sh", "run"]

Importante: o Tomcat utiliza a porta 8080 configurada na imagem edermag/tomcat-8-dev.

Esse exemplo assume que o projeto segue o layout do Maven. Sendo assim, o arquivo Dockerfile deve ficar no mesmo diretório do arquivo pom.xml. Nesse arquivo colocamos as instruções para que o Docker construa uma imagem, local, usando como base a imagem edermag/tomcat-8-dev. As instruções são:
  • Criar o diretório /source, e usá-lo como diretório de trabalho (manipulação) do Container;
  • Setar uma variável de ambiente com o nome do artefato gerado pelo Maven;
  • Copiar o arquivo pom.xml do host para a imagem. Também copiar todo o código fonte (diretório src) do host para a imagem;
  • Executar o Maven para construir o projeto e fazer o deploy do WAR no Tomcat;
  • Por fim inicializar o Tomcat;
Para criar a imagem no Docker a partir desse arquivo, execute a seguinte instrução no diretório raiz da aplicação:
$ sudo docker build -t javaweb .

De acordo com a instrução acima, o nome da imagem é javaweb. Durante a geração dessa imagem, o build e o deploy serão realizados. Caso o código seja modificado a imagem deverá ser gerada novamente (nova versão). Após executar o comando acima, verifique novamente as imagens instaladas no host através do comando: docker images. O próximo passo é instanciar o Container da imagem javaweb:
$ sudo docker run -d -p 8081:8080 javaweb

Com essa instrução o Docker cria o Container em modo Daemon, via opção -d, e aloca a porta 8081 para redirecionar a porta 8080 da imagem, via opção -p. Verifique o NAME (apelido) do Container através do comando: docker ps.

Como o build e deploy são realizados durante a geração da imagem, a execução do Container irá inicializar o Tomcat. Para visualizar os logs do Tomcat execute a instrução (use o NAME do Container):
$ sudo docker logs {coloqueOConteudoDoNAME}

Para encerrar a execução do Container execute a instrução:
$ sudo docker logs {coloqueOConteudoDoNAME}

Com pequenos ajustes seria possível criar uma imagem (e Container) para executar a aplicação Java Web no Jetty.

www.yaw.com.br

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, 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 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

Monday, May 27, 2013

RESTEasy: Erro ao retornar List em XML - Could not find MessageBodyWriter for response object of type: xxx application/xml

Outro dia me deparei com uma exception gerada pelo RESTEasy, o erro era: Could not find MessageBodyWriter for response object of type: java.util.ArrayList of media type: application/xml.

No meu cenário, o problema ocorria ao utilizar o componente Response do RESTEasy como retorno do método. Pra ficar mais claro a seguir um exemplo de código simulando o erro, no caso a entidade e o endpoint do RESTEasy:
@Entity
@XmlRootElement
public class Carro {

  @Id
  private Long id;

  @NotNull
  private String renavam;

  private String placa;

  @NotNull
  @ManyToOne
  private Modelo modelo;

  //...

}


@Path("/carros")
public class CarroEndpoint {
  ...

  @GET
  @Path("/{id:[0-9][0-9]*}")
  @Produces("application/xml")
  public Response findById(@PathParam("id") Long id) {
    try {
      Carro c = service.findById(id); 
      return Response.ok(c).build();
    } catch (NoResultException nrex) {
      return Response.status(Status.NOT_FOUND).build(); //404
    }
  }

  @GET
  @Produces("application/xml")
  public List<Carro> listAll() {
    return service.listAll();
  }

  @GET
  @Path("/{modelo}")
  @Produces("application/xml")
  public Response listByModelo(@PathParam("modelo") String descModelo) {
    Modelo m = service.findModeloByDescricao(descModelo);
    if (m == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    List<Carro> carros = service.findByModelo(m);
    return Response.ok(carros).build(); //o problema ocorre aqui
  }

}

Os  métodos findById e listAll funcionam corretamente, ambos devolvem o XML representando os dados dos carros encontrados de acordo com a consulta. Note o retorno desses dois métodos: a entidade Carro e uma lista de Carros.

A exception ocorre no terceiro método, no listByModelo, aonde faço uso da Response (componente do RESTEasy). Nesse caso o RESTEasy não consegue transformar o ArrayList em XML. A mesma situação acontece com outra coleção, por exemplo HashSet.

Pra resolver o problema, eu utilizei o GenericEntity para "tipar" a lista, de forma que o RESTEasy possa realizar a transformação em XML. A mesma abordagem funciona com HashSet. A seguir a nova versão de listByModelo:
  ...

  @GET
  @Path("/{modelo}")
  @Produces("application/xml")
  public Response listByModelo(@PathParam("modelo") String descModelo) {
    Modelo m = service.findModeloByDescricao(descModelo);
    if (m == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    List<Carro> carros = service.findByModelo(m);
    GenericEntity<List<Carro>> entity = new GenericEntity<List<Carro>>(carros);
    return Response.ok(entity).build(); //ok
  }
  ...

A versão do RESTEasy utilizada foi a 2.3.6.Final.

[]s
http://twitter.com/edermag
http://www.yaw.com.br

Wednesday, March 27, 2013

Configurar o DataSource do MySQL no JBoss AS 7

Nesse post descrevo os passos para realizar a configuração do DataSource do MySQL no JBoss Application Server 7.

Sobre o JBoss AS

O JBoss AS 7 é a versão mais recente do container Java EE mantindo pela Red Hat, trata-se da versão open source do projeto. A Red Hat também disponibiliza  o JBoss EAP 6, como um produto (suporte e treinamento). Ambas as versões 6 e 7, na teoria, contam com a mesma base de código e features.

O JBoss sofreu uma grande reestruturação, se compararmos com versões anteriores (4 e 5). Um novo mecanismo para definir e utilizar módulos, melhorias consideráveis na performance e na administração do servidor são algumas características do JBoss AS 7. As mudanças afetam a forma de administrar o servidor, um exemplo disso é a própria configuração do DataSource.

O JBoss AS pode ser operado em dois modos: managed domain aonde múltiplas instâncias ativas são orquestradas (controladas) por um ponto (host) central; a outra é a standalone aonde uma única instância do servidor é utilizada (parecido com as versões antigas). Nesse post eu utilizo o JBoss no modo standalone.

Instalar o driver do MySQL

Depois de baixar o driver JDBC do MySQL, é necessário instalar o driver como um módulo do JBoss. No diretório do JBoss, na pasta modules crie a estrutura de sub-pastas  \com\mysql\main, e copie o jar do driver dentro dessa pasta.


Nessa pasta, crie o arquivo module.xml, com o conteúdo a seguir:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="com.mysql">
  <resources>
    <resource-root path="mysql-connector-java-5.1.24-bin.jar"/>
  </resources>
  <dependencies>
    <module name="javax.api"/>
    <module name="javax.transaction.api"/>
  </dependencies>
</module>

Note a definição do nome do módulo com.mysql, na tag module de acordo com a estrutura de diretórios criada. A pasta main não é considerada, ela indica ao JBoss que essa é a versão principal do módulo. O JBoss permite a configuração de diferentes versões do mesmo módulo! Na tag resource-root indicamos o jar do MySQL contido no diretório main, que é justamente quem implementa o módulo. Outro detalhe são as dependências do módulo, definidas na tag dependencies.


Definir o DataSource

Uma vez que o módulo foi instalado, a próxima etapa é configurar o DataSource. Acesse o arquivo jboss7.../standalone/configuration/standalone.xml e adicione o conteúdo da tag datasource e driver, como a seguir:

<?xml version='1.0' encoding='UTF-8'?>
<server xmlns="urn:jboss:domain:1.2">
  ...
  <subsystem xmlns="urn:jboss:domain:datasources:1.0">
    <datasources>
      ...
      <datasource jndi-name="java:jboss/datasources/MysqlDS" 
        pool-name="MySqlDS" enabled="true" use-java-context="true">
        <!-- url jdbc -->
        <connection-url>jdbc:mysql://localhost:3306/db</connection-url>
        <!-- identificador do driver -->
        <driver>com.mysql</driver>

        <transaction-isolation>
          TRANSACTION_READ_COMMITTED
        </transaction-isolation>
        <pool>
          <min-pool-size>10</min-pool-size>
          <max-pool-size>100</max-pool-size>
          <prefill>true</prefill>
        </pool>

        <security>
          <user-name>root</user-name> <!-- usuario mysql -->
          <password>root</password> <!-- senha -->
        </security>
      </datasource>

      <drivers>
        ...
        <!-- definicao do driver -->
        <driver name="com.mysql" module="com.mysql">
          <xa-datasource-class>com.mysql.jdbc.Driver</xa-datasource-class>
        </driver>
      </drivers>
    </datasources>
  </subsystem>
  ...
</server>

Importante: Não remova nenhum conteúdo desse arquivo. Provalvemente já existe a configuração do driver e do datasource para o Hypersonic, um exemplo do JBoss. Mantenha essas configurações.

A tag driver indica que o módulo que nós acabamos de instalar, deve ser utilizado como um driver JDBC. Nela, indicamos qual é o nome do driver. Na outra tag driver, que fica dentro da definição do datasource, colocamos o nome do driver deve ser utilizado. O restante das tags indicam as informações de conexão com o MySQL e informações para criação do datasource (pool e transação).

Coloque o JBoss AS no ar, execute o arquivo jboss...\bin\standalone.sh (Windows standalone.bat). Verifique as informações do DataSource no log:

yaw@m21:/opt/jboss-as-7.1.1.Final/bin$./standalone.sh
=================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /opt/jboss-as-7.1.1.Final

  JAVA: /usr/lib/jvm/java-7-oracle/bin/java

  JAVA_OPTS:  -server -XX:+TieredCompilation -Xms64m -Xmx512m ...
=================================================================
00:30:55,948 INFO  [org.jboss.modules] JBoss Modules version 1.1.1.GA
00:30:56,105 INFO  [org.jboss.msc] JBoss MSC version 1.0.2.GA
00:30:56,149 INFO  [org.jboss.as] JBoss AS 7.1.1.Final "Brontes" starting
...

00:30:57,184 INFO  [org.jboss.as.connector.subsystems.datasources]
(ServerService Thread Pool -- 27) JBAS010404: Deploying non-JDBC-compliant 
driver class com.mysql.jdbc.Driver (version 5.1)

...

00:30:57,667 INFO  [org.jboss.as.connector.subsystems.datasources] 
(MSC service thread 1-4) JBAS010400: 
Bound data source [java:jboss/datasources/MysqlDS]

...

Agora você pode desenvolver aplicações Java EE com JBoss e MySQL, através desse DataSource. Veja como seria o persistence.xml (JPA) para utilizar esse datasource:

<?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="appUnit">
    <!-- jndi-name -->
    <jta-data-source>java:jboss/datasources/MysqlDS</jta-data-source>
    <properties>
      <property name="hibernate.dialect" 
        value="org.hibernate.dialect.MySQLDialect"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
      <property name="hibernate.connection.charSet" value="UTF-8"/>
    </properties>
  </persistence-unit>
</persistence>


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