Tuesday, February 26, 2019

SpringBoot + MySQL + Docker-Compose (parte 2)

Na parte 1 deste artigo criamos o front-end. Nesta, criamos o back-end e o serviço de banco de dados.

Back-end com Spring Boot

Acesse a página start.spring.io e preencha os campos de dependências conforme a imagem abaixo e clique em Generate project. Unzipe o arquivo baixado e importe o projeto para o eclipse.



O projeto back-end tem como base o pacote com.app.backend, a partir do qual criamos o pacote entidade, que contém a entidade Pessoa.java idêntica ao do projeto front-end. Temos também o pacote repository, que declara uma interface para persistência de dados utilizando o Spring data. E o pacote controller, que expõe um web service REST que recebe as requisições do front-end. A estrutura do projeto back-end é como da imagem abaixo:


Interface PessoaRepository.java cuja implementação fica a cargo do container do Spring:

package com.app.backend.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.app.backend.entidade.Pessoa;

@Repository
public interface PessoaRepository extends JpaRepository{

}

Web Service REST PessoaController.java:
package com.app.backend.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.app.backend.entidade.Pessoa;
import com.app.backend.repository.PessoaRepository;

@RestController
@RequestMapping("/pessoa")
public class PessoaController {
 
 @Autowired
 PessoaRepository pessoaDao;
 
 @PostMapping(value = "/salvar")
 public ResponseEntity salvarPessoa(@RequestBody Pessoa pessoa) {
    
  try {
   pessoa = pessoaDao.save(pessoa);
   ResponseEntity response = new ResponseEntity<>(pessoa, HttpStatus.OK);   
   return response;
  }
  catch(Exception e) {
   e.printStackTrace();
   ResponseEntity response = new ResponseEntity<>(e.getMessage(), 
               HttpStatus.INTERNAL_SERVER_ERROR);   
   return response;
  }  
 }
 
 @GetMapping(value = "/todas")
 public ResponseEntity getTodas() {  
 
  List result = pessoaDao.findAll();  
  ResponseEntity> response = new ResponseEntity<>(result, HttpStatus.OK);
  return response;
 }   
}

A entidade Pessoa.java é idêntica àquela descrita na parte 1 deste artigo. O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker:
    FROM openjdk:8
    MAINTAINER "http://finalexception.blogspot.com"
    ADD target/back-end.jar .
    ENTRYPOINT ["java", "-jar", "back-end.jar"]


Na pasta main/resources temos o arquivo application.properties do Spring vazio. Deixe-o conforme abaixo:
# JPA PROPS
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy

# APPLICATION CONTEXT PATH
server.servlet.context-path=/app-backend

# DATABASE
spring.datasource.url=jdbc:mysql://mysql-standalone:3306/bd_pessoa?useSSL=true&serverTimezone=UTC
spring.datasource.username=rafael
spring.datasource.password=12345

spring.database.driverClassName =com.mysql.cj.jdbc.Driver

Isso é tudo! Com o botão direito do mouse no projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo back-end.jar na pasta target, conforme descrito no Dockerfile.

Subindo a aplicação com Docker-compose

Nossa aplicação tem 3 serviços, poderia ter dezenas. Subir e gerenciar uma grande quantidade de serviços pode ser trabalhos, é aí que entra o docker-compose.

O Compose é um arquivo binário escrito em Python que você deve instalar no host que roda o docker.

A pasta dos projetos back-end e front-end devem estar dentro do mesmo diretório, que também devre conter o arquivo docker-compose.yml:



Com o Docker-compose, definimos os serviços da aplicação em arquivo YML e o Compose se encarrega de publicar a aplicação na engine do Docker.

docker-compose.yml
:
version: '3.5'
services:
  mysql-standalone:
    image: mysql:5.6
    environment:
      - MYSQL_ROOT_PASSWORD=rootpass
      - MYSQL_DATABASE=bd_pessoa
      - MYSQL_USER=rafael
      - MYSQL_PASSWORD=12345
    expose:
      - 3306
  backend:
    build: back-end
    depends_on:
      - mysql-standalone
    ports:
      - 8989:8080
    environment:
      - DATABASE_HOST=mysql-standalone
      - DATABASE_USER=rafael
      - DATABASE_NAME=bd_pessoa
      - DATABASE_PASSWORD=12345
      - DATABASE_PORT=3306
  frontend:
    build: front-end
    depends_on:
      - backend
    ports:
      - 8180:8080


O arquivo docker-compose.yml acima também serve como uma valiosa documentação do projeto. Nele vemos que nossa aplicação possui 3 serviços, cada qual em um host virtual separado: mysql-standalone, backend e frontend. Execute o comando $ docker-compose up para subir aplicação:



Agora você pode acessar o front-end na porta 8180 e testar a aplicação:


O código fonte do projeto completo está no git-hub.

"Fiquem unidos a mim, e eu ficarei unidos a vocês. O ramo que não fica unido à videira não pode dar frutos. Vocês também não poderão dar frutos, se não ficarem unidos a mim. Eu sou a videira, e vocês são os ramos. Quem fica unido a mim, e eu a ele, dará muito fruto, porque sem mim vocês não podem fazer nada"


João 15:4-5

       

Monday, February 4, 2019

SpringBoot + MySQL + Docker-Compose (parte 1)


A maioria das aplicações modernas são constituidas de múltiplos pequenos serviços independentes que interagem e assim dão forma à própria aplicação. Chamamos isso de microserviços. Um simples exemplo pode ser uma aplicação com 3 serviços:
  1. web front-end
  2. web back-end
  3. banco de dados
Coloque os três serviços juntos e temos uma aplicação com alguma utilidade.

Subir e gerenciar vários serviços pode se tornar uma tarefa difícil. É aí que entra o Docker-Compose.

Ao invés de juntar tudo com scripts e comandos extreamente longos, o Docker-Compose permite você descrever toda aplicação em único arquivo de configuração declarativo. Depois então você sobe todos os serviços com um único comando:

$ docker-compose up

Vamos ilustrar o uso do docker-compose criando uma aplicação formada por 3 serviços: uma interface REST no backend (com Spring Boot), um front-end HTML (Java Server Faces) e um banco de dados MySQL. Cada serviço totalmente separado um do outro

Front-End com Java Server Faces

Nosso front-end é um formulário HTML com JSF para cadastrar uma entidade Pessoa, a qual possui dois campos: nome e profissão. O front também lista todas as pessoas registradas na base de dados em um elemento table. O container é o WildFly. Há uma camada de serviço para fazer a requisições ao backend, representada pela classe Service.java e retorná-las ao managed bean. A classe que recebe os eventos da página HTML é ManagedBean.java:

classe ManagedBean.java

package com.app;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Model;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import com.app.entidade.Pessoa;

@Model
public class ManagedBean {

 @Inject
 private Service service;
 private Pessoa pessoa = new Pessoa();
 private List pessoas;
 
 
 public String salvar() {
  
    try {
  pessoa = service.salvarPessoa(pessoa);
  pessoa = new Pessoa();
  FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, 
           "salvo com sucesso", "salvo com sucesso"));
   }  
   catch(Exception e) {
   FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
            e.getMessage(), e.getMessage()));
  }
  return null;
 }
 
 public Pessoa getPessoa() {
  return pessoa;
 }
 public void setPessoa(Pessoa pessoa) {
  this.pessoa = pessoa;
 }
 public List getPessoas() {
  try {   
   pessoas = service.getPessoas();  
   return pessoas;
  }
  catch(RuntimeException e) {
   FacesContext.getCurrentInstance()
   .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
           e.getMessage(), e.getMessage()));   
  }
  return null;
 }
 public void setPessoas(List pessoas) {  
  this.pessoas = pessoas;
 } 
}

classe Service.java

package com.app;

import java.net.URI;
import java.util.List;
import javax.enterprise.inject.Model;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import com.app.entidade.Pessoa;

@Model
public class Service {

 private final String URL_BACKEND = "http://backend:8080/app-backend/";
 
 public Pessoa salvarPessoa(Pessoa pessoa) {
  
  final String operacao = "salvar";  
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  //usando JAX-RS Client API para enviar o objeto pessoa ao server back-end
  Entity data = Entity.entity(pessoa, MediaType.APPLICATION_XML_TYPE);
  Response response = ClientBuilder.newClient()
      .target(uri)
      .request(MediaType.APPLICATION_XML)
      .post(data, Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return pessoa;
  else
   throw new RuntimeException("Ocorreu um erro ao salvar a Pessoa");    
 }
 
 public List getPessoas() {
  
  final String operacao = "all";
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  Response response = ClientBuilder.newClient()
       .target(uri)
       .request(MediaType.APPLICATION_JSON_TYPE)
       .get(Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return response.readEntity(List.class);
  else
   throw new RuntimeException("Não foi possível obter a Lista de Pessoas");  
 }
}

Entidade Pessoa.java
package com.app.entidade;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pessoa implements Serializable{

 private Long id;
 private String nome;
 private String profissao;

    //getters & setters omitidos
}

Página index.xhtml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html">

<h:head>
 <title>Cadastro Pessoa</title>
</h:head>
<h:body>
 <h:form>
  <h:messages />
  <h:panelGrid columns="2">
   <h:outputText value="Nome" />
   <h:inputText value="#{managedBean.pessoa.nome}" />
   <h:outputText value="Idade" />
   <h:inputText value="#{managedBean.pessoa.profissao}" />
  </h:panelGrid>
  <h:commandButton action="#{managedBean.salvar}" value="Salvar" />
  <p />
  Lista
  <h:dataTable value="#{managedBean.pessoas}" var="pessoa" border="1">
   <h:column>
    <h:outputText value="#{pessoa.nome}" />
   </h:column>
   <h:column>
    <h:outputText value="#{pessoa.profissao}" />
   </h:column>
  </h:dataTable>
 </h:form>
</h:body>
</html>

O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker: A aplicação roda dentro do Wildfly (FROM) e o arquivo front-end.war é adicionado na pasta /opt/jboss/wildfly/standalone/deployments/ do servidor:

   FROM jboss/wildfly
   MAINTAINER "http://finalexception.blogspot.com"
   ADD target/front-end.war /opt/jboss/wildfly/standalone/deployments/

Com o botão direito do mouse na pasta raiz do projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo front-end.war na pasta target, conforme descrito no Dockerfile.

Neste ponto a aplicação pode rodar, porém ela ainda não estará funcional, já que depende de dois outros serviços: o backend propriamente dito, este por sua vez depende do banco de dados.

Na parte 2 deste artigo criamos e subimos esses dois serviços que faltam.



"Ouçam agora, vocês que dizem: 'Hoje ou amanhã iremos para esta ou aquela cidade, passaremos um ano ali, faremos negócios e ganharemos dinheiro'. Vocês nem sabem o que acontecerá amanhã! Que é a sua vida? Vocês são como a neblina que aparece por um pouco de tempo e depois se dissipa. Em vez disso, deveriam dizer: 'Se o Senhor quiser, viveremos e faremos isto ou aquilo'."

Tiago 4:13-15


       

Tuesday, December 18, 2018

Docker: Imagens (muito!) menores com multi-stage-build

Quando se fala em containers no Docker, menos é mais, ou grande significa lento e difícil. Por essa razão, imagens no Docker precisam ser pequenas. A melhor imagem é sempre a menor possível.

É importante perceber que, dependendo da forma como as instruções são colocadas no Dockerfile, a imagem final poderá ser - muito! - maior ou menor.


Multi-stage builds é um novo recurso do Docker disponível a partir da versão 17.05. É uma ferramenta extremamente útil para deixar a imagem que irá rodar aplicação a menor possível. Basicamente, com Multi-Stage Builds nós temos um único Dockerfile com mais de uma instrução FROM. Cada instrução FROM representa um estágio de construção da imagem e pode facilmente copiar artefatos dos estágios anteriores para o atual. Vamos a um exemplo aproveitando um post anterior deste blog.

Diminuindo o tamanho da imagem em 90%

Neste artigo intitulado Hello World Java-Docker, criei uma aplicação java simples e que rodava em container do Docker, sem explorar outros recursos, apenas como uma introdução sobre como rodar o Java no Docker. O arquivo Dockerfile.simple é o que segue:
# constrói uma imagem baseada no openjdk 8
FROM openjdk:8

ADD HelloJavaDocker.class .

ENTRYPOINT ["java", "HelloJavaDocker"]
    
Após gerar o .class pelo comando javac (fora do Docker), construímos a imagem chamada hello-java-docker pelo comando:
$ docker build -f Dockerfile.simple -t hello-java-docker .    
E em seguida criamos o container e rodamos a aplicação:
$ docker run -it hello-java-docker    
A aplicação executa com sucesso dentro da imagem hello-java-docker



Entretanto, ao executar o comando docker images, veremos que a imagem hello-java-docker é incrivelmente grande, possuindo o tamanho de quase 624MB:
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
hello-java-docker   latest              83ce2e26c600        About a minute ago   624MB
openjdk             8                   c14ba9d23b3a        4 weeks ago          624MB
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $     
Podemos diminuir esse tamanho, alterando o Dockerfile, dividindo a construção da imagem em dois estágios. Afinal, não precisamos de todo ambiente JDK apenas para um Hello World. Precisamos apenas da versão básica da JRE. Então criamos um arquivo chamado Dockerfile.builders com as instruções que seguem:
FROM openjdk:8 AS Builder
ENV APP_HOME=/root/dev/apps/
WORKDIR $APP_HOME
COPY HelloJavaDocker.java $APP_HOME
RUN javac HelloJavaDocker.java


FROM openjdk:8-jre-alpine
WORKDIR root
COPY --from=Builder /root/dev/apps/HelloJavaDocker.class .
ENTRYPOINT ["java", "HelloJavaDocker"]    
  1. A primeira instrução FROM, para a qual demos o alías de Builder, utiliza a imagem openjdk:8.
  2. Criamos uma variável de ambiente com ENV dentro da imagem
  3. Copiamos o arquivo com o código java para APP_HOME
  4. Dessa vez compilamos a classe dentro do container
  5. O próximo FROM cria uma imagem baseada no JRE Linux Alpine
  6. Copiamos o .class gerado em Builder
  7. Executamos a aplicação
Agora, vamos construir uma imagem chamada chamada hello-java-docker-smaller. Em seguida criamos o container:
$ docker build -f Dockerfile.builders -t hello-java-docker-smaller .
$ docker run -it hello-java-docker-smaller    
O resultado da aplicação é o mesmo. Porém ao rodar docker images, veremos que o tamanho da nova imagem hello-java-docker-smaller é de apenas 83MB. Uma queda no tamanho de quase 90%:
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
hello-java-docker-smaller   latest              b5daa3a785c1        3 minutes ago       83MB
                                    c1625c3ad04b        3 minutes ago       624MB
hello-java-docker           latest              83ce2e26c600        6 minutes ago       624MB
openjdk                     8                   c14ba9d23b3a        4 weeks ago         624MB
openjdk                     8-jre-alpine        2e01f547f003        7 weeks ago         83MB
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ 
    


"Falando novamente ao povo, disse Jesus: “Eu sou a luz do mundo; aquele que me segue, não andará em trevas, mas terá a luz da vida."
Jo 8:12


       

Sunday, November 25, 2018

Máscaras no JasperReport: o jeito certo

Durante o desenvolvimento de relatórios, um trabalho para o qual geralmente é relegado menor prioriade e que, portanto, ao final costumam ser feitos na base da gambiarra, são as máscaras dos campos.

É bastante comum soluções que colocam toda lógica de formatação do valor do campo de texto dentro do próprio campo de texto, como no screenshot abaixo tirado do editor de expressão do JasperStudio:


O código da expressão na imagem é o que segue:
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 13) ? 
 ("(" + ($F{telefoneEmpresaAdm}.substring(0,4).trim()) + ") " + $F{telefoneEmpresaAdm}.substring(4,9) + "-" + $F{telefoneEmpresaAdm}.substring(9,13)) : 
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 12) ? 
 "(" + (($F{telefoneEmpresaAdm}.substring(0,4).trim())+ ") " + $F{telefoneEmpresaAdm}.substring(4,8) + "-" + $F{telefoneEmpresaAdm}.substring(8,12)) : 
 ${telefoneEmpresaAdm}
    
Tudo isso dentro de um mísero campo de texto!

Esse código representa a lógica para criar a máscara para um campo de texto cujo conteúdo é um número de telefone. Esta lógica supostamente atende aos diversos cenários em que um número de telefone pode ocorrer: celular ou fixo? contém DDD? em algumas cidades do Brasil o número do  telefone fixo tem mais dígitos do que outras, etc.

Essa abordagem pode até resolver o problema no curto prazo, mas obviamente não é uma boa solução em termos de manutenção do projeto porque a lógica de formatação fica espalhada nos campos. Em projetos com dezenas ou até centenas de relatórios, essa estratégia se torna inviável.

A solução ideal é centralizar tais tarefas em um único local, de modo que todos os relatórios busquem alí a máscara para um respectivo campo. Esse local é uma classe com dezenas de métodos estáticos cada um contendo uma lógica de formatação para um determinado tipo de campo e cenário. Podemos inclusive invocar esses métodos a partir da template .jrxml, seja em produção ou em teste.

Projeto de Exemplo

Para exemplificar esta abordagem criamos um projeto que chama um relatório cuja fonte de dados é um banco de dados relacional. Para criar e popular o banco de dados, execute o seguinte script SQL (o banco de dados que utilizo neste exemplo é o MySQL):
create database if not exists bancoDeDados;
use bancoDeDados;
create table if not exists algumaEntidade(cep varchar(255), 
            telefone varchar(255), 
            data date, 
            valor decimal(5,2), 
            cpf varchar(255));
                                 
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');    
Nesse script criamos colunas com tipos de dados que aparecem com frequência em qualquer relatório e sempre requerem algum tipo de máscaras formatação.

A classe Mascaras assume a responsabilidade de controlar toda lógica de geração de máscaras para todos os relatórios do projeto:
package main;

import java.math.BigDecimal;
import java.sql.Date;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import javax.swing.text.MaskFormatter;

public class Mascaras {

 public static String getDataAtualCompleta() {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd 'de' MMMM 'de' yyyy", new Locale("pt", "BR"));
  
  LocalDate hoje = LocalDate.now();
  return dtf.format(hoje);
 }
 
 public static String getCEP(String cep) {
  
  try {
   MaskFormatter mask = new MaskFormatter("#####-###");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cep);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getTelefone(String telefone) {
  
  try {
   String pattern = null;
   
   if(telefone.length() == 10) 
    pattern = "(##) ####-####";  //fixo com ddd   
   
   else if(telefone.length() == 11)
    pattern = "(##) #-####-####"; // celular com ddd
   //else if... outros formatos
   
   MaskFormatter mask = new MaskFormatter(pattern);
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(telefone);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getDataSimples(Date date) {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy", new Locale("pt", "BR"));  
  LocalDate hoje = date.toLocalDate();
  return dtf.format(date);
 }
 
 public static String getCPF(String cpf) {
  
  try {
   MaskFormatter mask = new MaskFormatter("###.###.###-##");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cpf);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getValorMonetario(BigDecimal valor, boolean usaSimboloMoeda) {
  
  NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
  
  DecimalFormatSymbols dfs = ((DecimalFormat)format).getDecimalFormatSymbols();
  
  if(!usaSimboloMoeda)
   dfs.setCurrencySymbol("");
  
  ((DecimalFormat)format).setDecimalFormatSymbols(dfs);
  
  return format.format(valor);  
 }
}    
Em seguida temos a template jrxml que representa o modelo do relatório. Ela contém a query que busca os dados no banco de dados que criamos:
<?xml version="1.0" encoding="UTF-8"?>

<jasperReport name="TesteMascaras">
  
 <queryString language="SQL">
 
  SELECT algumaEntidade.cep,
   algumaEntidade.telefone,
   algumaEntidade.data,
   algumaEntidade.valor,
   algumaEntidade.cpf
  FROM algumaEntidade
 
 </queryString>
 
 <field name="cep" class="java.lang.String"/>
 <field name="telefone" class="java.lang.String"/>
 <field name="data" class="java.sql.Date"/>
 <field name="valor" class="java.math.BigDecimal"/>
 <field name="cpf" class="java.lang.String"/>
 
 <variable name="somaValor" class="java.math.BigDecimal" calculation="Sum">
  <variableExpression>$F{valor}</variableExpression>
 </variable>
 
 <title>
  <band height="46"  >
   <staticText>
    <reportElement x="29" y="8" width="180" height="30" />
    <textElement>
     <font isBold="true"/>
    </textElement>
    <text>MÁSCARAS</text>
   </staticText>
   <textField>
    <reportElement x="370" y="8" width="175" height="30" />
    <textFieldExpression>main.Mascaras.getDataAtualCompleta()</textFieldExpression>
   </textField>
  </band>
 </title>
 <columnHeader>
  <band height="61"  >
   <staticText>
    <reportElement x="0" y="0" width="80" height="30" />
    <text>cep</text>
   </staticText>
   <staticText>
    <reportElement x="90" y="0" width="78" height="30" />
    <text>telefone</text>
   </staticText>
   <staticText>
    <reportElement x="180" y="0" width="100" height="30" />
    <text>data</text>
   </staticText>
   <staticText>
    <reportElement x="422" y="0" width="100" height="30" />
    <text>valor</text>
   </staticText>
   <staticText>
    <reportElement x="297" y="0" width="100" height="30" />
    <text>cpf</text>
   </staticText>
  </band>
 </columnHeader>
 <detail>
  <band height="40"  >
   <textField>
    <reportElement x="0" y="0" width="80" height="30" />
    <textFieldExpression>main.Mascaras.getCEP($F{cep})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="90" y="0" width="85" height="30" />
    <textFieldExpression>main.Mascaras.getTelefone($F{telefone})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="180" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getDataSimples($F{data})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="422" y="0" width="100" height="30" />
<!--     podmeos inclusive passar parametros que não sejam o campo $F, neste caso passomos um boolean literal -->
    <textFieldExpression>main.Mascaras.getValorMonetario($F{valor}, false)</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="297" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getCPF($F{cpf})</textFieldExpression>
   </textField>
  </band>
 </detail>
 <summary>
  <band height="44"  >
   <textField>
    <reportElement x="410" y="7" width="134" height="30" />
    <textFieldExpression>main.Mascaras.getValorMonetario($V{somaValor}, true)</textFieldExpression>
   </textField>
  </band>
 </summary>
</jasperReport>
    
E a classe Main que inicia a aplicação e chama o relatório:
package main;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;

import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.view.JasperViewer;

public class Main {

 public static void main(String[] args) throws Exception {

  String url = "jdbc:mysql://localhost/bancoDeDados?useSSL=true&serverTimezone=UTC&user=root&password=root";

  Class.forName("com.mysql.cj.jdbc.Driver");
  
  try (Connection conn = DriverManager.getConnection(url);) {

   InputStream stream = Main.class.getResourceAsStream("TesteMascaras.jxml");
   
   JasperReport jasper = JasperCompileManager.compileReport(stream);
     
   JasperPrint print = JasperFillManager.fillReport(jasper, null, conn);
   
   JasperViewer viewer = new JasperViewer(print);
   viewer.setVisible(true);
  }
 }
}    
O resultado é o relatório com os campos devidamente formatados abaixo.


Esse é o padrão correto para criação de máscaras em relatórios no Java.

"Assim diz Javé dos exércitos: Neste lugar agora arruinado, sem gente e sem criações, e também em todas as suas cidades haverá pastagens, onde os pastores farão suas ovelhas repousar."
Jr 33,12


       

Monday, November 19, 2018

Livro "A Philosophy of Software Design" de John Ousterhout

No livro A Philosophy of Software Design de John Ousterhout, o autor dedica várias páginas sobre comentários nas classes. Pode parecer apenas uma distração enfadonha, até você se dar conta de que comentários são a pedra angular para um conjunto de boas práticas no desenvolvimento de software.

Ousterhout apresenta algumas convenções para fazer comentários, dentre essas ele recomenda que a primeira coisa que você deve fazer ao criar uma classe é escrever comentários, os quais se dividem em dois tipos: alto-nível e baixo-nível.

Comentários de alto-nível são sobre a finalidade da classe e sua relação com outras classes. Esse tipo de comentário deve resumir tudo o que alguém precisa saber sobre a classe, exceto detalhes de implementação.

Comentáros de baixo nível são aqueles espalhados pela classe, devem funcionar como uma lanterna, cuja finalidade é esclarecer o como e o porquê - desde que o código não esteja claro o suficiente para responder a isso por si só.

Abordagem interessante.


Friday, November 16, 2018

Hello World Java-Docker

Docker é um software que cria, gerencia e coordena containers. Containers por sua vez são processos que rodam dentro do Docker. Exemplos de containers são:
  • Wildfly JBoss 
  • MySQL
  • Linux Ubuntu
  • Ruby
  • Windows Nano
  • React NPM
  • JDK 6, 7, 8 etc.

Diferentemente dos processos que executam isoladamente em sistema operacional qualquer, os quais são compilados para executar somente naquele ambiente e ainda podem demandar outras instalações e configurações a parte, os containers são extremamente portáveis e rodam sobre uma imagem que representa o ambiente completo requirido para que aquele processo execute com sucesso.

Uma imagem no Docker é uma definição estática e imutável de todo um ambiente de execução.

Já o container é uma instância (em execução ou parada) de uma imagem. No Docker, containers estão para uma imagem assim como objetos estão para uma classe no Java.

Uma vez que containers não são instalados diretamente no sistema operacional local, nem suas dependências, que podem ser muitas, o maior benefíco do Docker é a portabilidade, já que um container pode executar em qualquer outro sistema operacional que tenha o Docker instalado.

O maior benefíco do uso de containers é a portabilidade

Hello World da Imagem Open JDK 10




Neste exemplo introdutório criamos uma classe chamada HelloJavaDocker que vai executar no container chamado hello-java-docker criado a partir da imagem Open JDK 10:

public class HelloJavaDocker{

    public static void main(String[] args){

        System.out.println("HELLO WORLD FROM JAVA DOCKER IMAGE!! *************\n\n");
    }
}

Compile a classe e gere o arquivo HelloJavaDocker.class:

$ javac HelloJavaDocker.java

No mesmo diretório no qual se encontra o arquivo HelloJavaDocker.class, crie um arquivo chamado Dockerfile. Este arquivo contém as instruções sobre como executar a classe HelloJavaDocker em qualquer lugar:

# imagem
FROM openjdk:10

# ADD recebe 2 parametros
# 1) fonte para adicionar na imagem; 2) localização do fonte dentro da imagem
ADD HelloJavaDocker.class .

# diz ao Docker como executar o fonte
ENTRYPOINT ["java", "HelloJavaDocker"]


Construindo a Imagem e Rodando o Container

Caso você não tenha o docker instalado na sua máquina, você pode baixá-lo, no Linux, pelo seguinte comando:
    
        $ wget -qO- https://get.docker.com/ | sh
    
Para versões a partir do Windows 10 (64bit) você pode baixar o executável na página oficial do Docker.

A maioria das operações no Docker se resumem a 2 passos:
  1. Construir a imagem, comando docker build
  2. Rodar o container, comando docker run

Para construir a imagem OpenJDK, vá para o diretório onde se encontra o arquivo Dockerfile e HelloJavaDocker.class e execute o seguinte comando:
    
        $ ls
        Dockerfile  HelloJavaDocker.class  HelloJavaDocker.java
        $ docker build -f Dockerfile -t hello-java-docker .
    

A opção -f  (file) recebe como argumnento a localização do Dockerfile.

A opção -t (tag) recebe como argumento o nome que daremos ao container e já cria uma instância baseada nessa imagem.

Agora execute a instância da imagem, que é o container hello-java-docker:
    
        $ docker run -it hello-java-docker
    


A classe HelloJavaDocker executou dentro do container hello-java-docker baseado na imagem Open JDK 10. 

Repare que nem sabemos se o Java está instalado e configurado no host local, pode ser sim, pode ser que não... Isso é indiferente! (na verdade o Java está instalado porque compilamos a classe fora da imagem, porém também poderíamos compilar o arquivo .java dentro do Docker)

Referências:
krammark23 Youtube
Docker Deep Dive, Nigel Poulton (2016)

       

Tuesday, October 16, 2018

Modular Projects no NetBeans 9.0

NetBeans 9 é a primeira release desde que a IDE passou a fazer parte da família Apache, cujo nome oficial agora é Apache NetBeans.

O NetBeans 9 também é a primeira versão desta IDE a conter suporte para o sistema de modularização da plataforma java, recurso que foi incluido a partir da versão 9 do java. Um módulo na plataforma Java consiste em:
  • Uma coleção de pacotes
  • Uma lista de quais pacotes estarão acessíveis para outros módulos
  • Uma lista de todos os módulos dos quais o módulo atual depende
Um módulo, portanto, pode controlar seletivamente quais pacotes estão disponíveis, isso reforça o encapsulamento e o gerenciamento de dependências, evitando problemas recorrentes na configuração do class path de seu projeto, tais como classes duplicadas ou não encontradas (ClassNotFoundException).



Java Modular Project

O NetBeans 9 apresenta uma nova opção de projeto, que é o Java Modular Project.
Caso queira modularizar sua aplicação, você deve escolher essa opção ao criar um novo projeto. 


Vamos criar um novo Java Modular Project que faz uso do módulo java.xml.bind, popularmente conhecido como JAXB API, ou seja, nosso projeto de exemplo converte uma classe java em um arquivo xml (você pode conferir todos os módulos disponíveis no javadoc do Java 10). Clique em Next e dê o nome ao novo projeto de JavaModular.

Clique com o botão direito na raiz do projeto JavaModular, navegue por new / module:


Dê o nome ao novo módulo de modular.test. (nomes de módulo devem ser globalmente únicos, a melhor forma de garantr isso é utilizar a mesma abordagem para nomeação de pacotes). O NetBeans já cria automaticamente o arquivo de configuração obrigatório em todos os módulos, chamado de module-info.java.

Clique com o botão direito no módulo que você acabou de criar e adicione um novo pacote chamado com:


Repare na estrutura do projeto. No pacote com vamos criar a classe Pais, que será convertida em um arquivo XML:
package com;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pais {
 
 @XmlElement
 private String nome;
 @XmlElement
 private double area;

 public Pais() {
 }

 public Pais(String nome, double area) {
  this.nome = nome;
  this.area = area;
 }
    //getters e setters...

Ao criar as delcarações de import para os pacotes javax.xml, ocorre um erro de compilação, package javax.xml.bind.annotation is not visible. Isso porque esses pacotes estão em um módulo separado. Precisamos declarar explicitamente no arquivo module-info que nosso módulo modular.test precisa desses pacotes, os quais pertencem ao módulo java.xml.bind. Para isso utilizamos a palavra chave requires dentro do arqivo de configuração do módulo:

module modular.test{
 
    requires java.xml.bind;

}

Agora no pacote com crie a classe Main na qual instanciaremos um objeto da classe Pais:
package com;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {

 public static void main(String[] args) throws JAXBException {
 
  Pais brasil = new Pais("Brasil", 8_600_000);
  JAXBContext context = JAXBContext.newInstance(Pais.class);
  Marshaller m = context.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
  m.marshal(brasil, System.out);
 }
}

Até aqui vimos claramente como a modularização reforça o encapsulamento. Um módulo só pode acessar pacotes explicitamente exportados por outros módulos, ou seja, só conseguimos usar as classes do pacote java.xml.bind porque o módulo java.xml exporta tais pacotes. Por exemplo, se quisermos que o pacote que acabamos de criar com esteja disponível para uso em outros módulos, então no arquivo module-info adicionamos a declração exports para esse pacote:

 exports com;

Reflection API em Módulos

Outro ponto interessante a notar é que frameworks de mapeamento como o JAXB, que estamos usando neste exemplo, ou o JPA, que mapeia classes Java para tabelas no banco de dados, entre outros,  utilizam a API Reflection para fazer o mapeamento das propriedades das classes, mesmo que essas propriedades sejam privadas. Em um ambiente modular, tentar acessar campos privados via reflection não é 100% garantido. Sim, isso significa que um sistema desenvolvido no Java 8 (ou mais antigo) que faz uso de APIs como JAXB, JPA ou outros frameworks que utilizam extensivamente a API Reflection, pode não rodar no ambiente Java 9 (ou mais novo).

Tente executar a classe Main e comprove. Uma exceção é lançada porque o JAXB tenta acessar os campos privados da classe Pais via reflection.

Para liberar o acesso via reflection não basta exportar sua classe, é necessário utilizar a declaração opens para determinada classe ou pacote. O arquivo module-info portanto deve ficar como segue:
module modular.test {
    exports com;
    requires java.xml.bind;
    opens com;
}

Agora sim podemos executar a classe Main. Ela imprime na tela a o objeto Pais no formato xml:
<?xml version="1.0" encoding="UTF-8"?>
<pais>
    <nome>Brasil</nome>
    <area>8600000.0</area>
</pais>