Friday, August 10, 2018

Layouts no JavaFX (parte 1)

JavaFX é uma API Java para construir aplicações com recursos avançados de interface gráfica com o usuário que rodam em uma variedade de dispositivos, desktop, mobile ou web.

Os componentes ou controles de interface gráfica podem ser os mais variados, como figuras geométricas, botões, tabelas, editores de texto, imagens, etc. Antes de iniciar a construção de uma tela, você precisa saber como irá dispor esses componentes na tela. Componentes indevidamente alinhados podem aumentar a curva de aprendizado do software e isso não é desejável em nenhum sistema.

Layout Managers

A forma como os controles de interface estão dispostos na janela é chamada de layout. A classes do JavaFX que servem de container para os demais controles da tela são chamadas de layout managers
e os controles nela incluídos são chamados de filhos ou children, eles podem ser adicionados ou removidos do container a qualquer momento. O que diferencia cada layout manager do outro é a forma como ele expões os children. Nessa primeira parte analisamos 5 layout managers: FlowPane, BorderPane, HBox, VBox e StackPane. Todos do pacote javafx.scene.layout.

FlowPane

FlowPane deixa os controles flutuarem ou se auto-ajustarem de acordo com os limites da janela ou do container. Por defualt, havendo espaço, os controles vão sendo expostos horizontalmente. Abaixo um código de exemplo de um FlowPane com três botões e dois radio buttons.
    
package _01flow;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Main extends Application {

 public static void main(String[] args) {  
  launch(args);
 }

 @Override
 public void start(Stage primaryStage) throws Exception {

  ExemploFlowPane exemplo = new ExemploFlowPane();
  
  Scene scene = new Scene(exemplo.flowPane);  
  primaryStage.setScene(scene);
  primaryStage.setTitle("Exemplo Flow Pane");  
  primaryStage.setWidth(300);
  primaryStage.setHeight(200);
  primaryStage.show();    
 }

 public class ExemploFlowPane {

  public FlowPane flowPane;
  
  public ExemploFlowPane() {

   Button left = new Button("left");
   Button center = new Button("center");
   Button right = new Button("right");
   ToggleGroup group = new ToggleGroup();
   RadioButton rd1 = new RadioButton("option 1");   
   RadioButton rd2 = new RadioButton("option 2");
   rd1.setToggleGroup(group);
   rd2.setToggleGroup(group);

   flowPane = new FlowPane(8,8);
   flowPane.setAlignment(Pos.CENTER);   
   flowPane.getChildren().addAll(left, center, rd1, rd2, right);
   
   left.setOnAction(e -> flowPane.setAlignment(Pos.CENTER_LEFT));
   center.setOnAction(e -> flowPane.setAlignment(Pos.CENTER));
   right.setOnAction(e -> flowPane.setAlignment(Pos.CENTER_RIGHT));  
  }
 }
}
    


Ao redimensionar a janela, os controles mudam a disposição

Com FlowPane, os componentes são realinhados a medida que o espaço se altera.

BoderPane

BorderPane é um layout divido em cinco regiões: Norte, Sul, Oeste, Centro e Leste:


Assim, você só pode inserir os componente em uma dessas regiões. O exemplo abaixo coloca um botão em cada uma das regiões. Em cada botão é registrado o evento de desaparecer quando clicado, deixando vazio o espaço que ele ocupa no BorderPane:
    
package _02borderPane;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

 public static void main(String[] args) {
  launch(args);
 }
 
 @Override
 public void start(Stage primaryStage) throws Exception {
 
  ExempĺoBorderPane exemplo = new ExempĺoBorderPane();
  Scene scene = new Scene(exemplo.borderPane);  
  primaryStage.setScene(scene);
  primaryStage.setTitle("Exemplo Border Pane");  
  primaryStage.setWidth(500);
  primaryStage.setHeight(500);
  primaryStage.show(); 
 }
 
 public class ExempĺoBorderPane implements EventHandler{
  
  public BorderPane borderPane = new BorderPane();
  String[] names = {"Hide North", "Hide South", "Hide East", "Hide West", "Hide Center"};
  Button[] buttons = new Button[5];
    
  public ExempĺoBorderPane() {

   for(int i = 0; i < names.length; i++) {
    buttons[i] = new Button(names[i]);
    buttons[i].setOnAction(this);
    buttons[i].setMaxHeight(Double.MAX_VALUE);
    buttons[i].setMaxWidth(Double.MAX_VALUE);
   }
   borderPane.setTop(buttons[0]);
   borderPane.setLeft(buttons[3]);
   borderPane.setCenter(buttons[4]);
   borderPane.setRight(buttons[2]);
   borderPane.setBottom(buttons[1]);
  }

  @Override
  public void handle(ActionEvent event) {

   for(Button button : buttons) {
    
    if(event.getSource() == button)
     button.setVisible(false);
    else
     button.setVisible(true);
   }
  }
 }
}
    

O espaço central vazio

HBox


O Layout HBox alinha os controles em uma linha horizontal ilimitada. No exemplo a seguir o evento do botão vai adicionando novos botões no HBox inicialmente vazia:

package _03HBox;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Main extends Application{

 public static void main(String[] args){
  launch(args);
 }

 @Override
 public void start(Stage primaryStage) throws Exception {

  ExemploHBox exemplo = new ExemploHBox();
  Scene scene = new Scene(exemplo.root);  
  primaryStage.setScene(scene);
  primaryStage.setTitle("Exemplo HBox");  
  primaryStage.setWidth(800);
  primaryStage.setHeight(200);
  primaryStage.show();
 }
 
 public class ExemploHBox{
    
  public HBox hBox;
  public BorderPane root;
  Button add;
  
  public ExemploHBox() {
  
   hBox = new HBox(5);
   root = new BorderPane();
   add = new Button("Add to Box");
   add.setOnAction(e -> hBox.getChildren().add(new Button("new element")));   
   root.setBottom(add);
   root.setCenter(hBox);   
  }
 }
}    




VBox

O layout VBox é como o HBox, porém com alinhamento verttical dos controles. No exemplo anterior, susbtitua HBox por VBox e o resultado é o que segue:


StackPane

StackPane empilha os controles em uma estrutura LIFO (Last In First Out), último que entra é o primeiro que sai. Dessa forma o último controle inserido é o que fica no topo. Geralemtne no uso de StackPane somente controle no topo deve ficar visível. No exemplo abaixo empilhamos no StackPane todos os layouts criados anteriormente, de modo que só o que está no topo fica visível. O botão next dispara um evento que manda o primeiro elemento da pilha para o final, deixando o próximo visível:
    
package _05StackPane;

import _01flow.Main.ExemploFlowPane;
import _02borderPane.Main.ExempĺoBorderPane;
import _03HBox.Main.ExemploHBox;
import _04VBox.Main.ExemploVBox;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

 @Override
 public void start(Stage primaryStage) throws Exception {
  
  ExemploStackPane stackPane = new ExemploStackPane();
  Scene scene = new Scene(stackPane.boderPane);
  primaryStage.setScene(scene);
  primaryStage.setTitle("Stack Pane");
  primaryStage.setWidth(600);
  primaryStage.setHeight(600);
  primaryStage.show();
  
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  launch(args);
 }
 
 class ExemploStackPane{
  
  StackPane stackPane;
  BorderPane boderPane = new BorderPane();

  public ExemploStackPane() {
  
   stackPane = new StackPane();
   ExemploFlowPane exemploFlowPane = new _01flow.Main().new ExemploFlowPane();
   ExempĺoBorderPane exempĺoBorderPane = new _02borderPane.Main().new ExempĺoBorderPane();
   ExemploHBox exemploHBox = new _03HBox.Main().new ExemploHBox();
   ExemploVBox exemploVBox = new _04VBox.Main().new ExemploVBox();
   
   stackPane.getChildren().add(exemploFlowPane.flowPane);
   stackPane.getChildren().add(exempĺoBorderPane.borderPane);
   stackPane.getChildren().add(exemploHBox.root);
   stackPane.getChildren().add(exemploVBox.root);   
   
   stackPane.getChildren().forEach(node -> node.setVisible(false));
   
   Button button = new Button("next");
   button.setOnAction(e -> {
    ObservableList list = stackPane.getChildren();
    Node top = list.get(list.size() - 1);
    Node newTop = list.get(list.size() - 2);    
    top.setVisible(false);
    top.toBack();    
    newTop.setVisible(true);    
   });
   
   boderPane.setCenter(stackPane);
   boderPane.setBottom(button);
  } 
 }
}
    



Na segunda parte deste post, analisamos o AnchorPane, GridPane, TilePane e BorderImage.

       

Friday, August 3, 2018

É hora de se tornar um Oracle Certified Professional!

Oracle Certified Professional (OCP) é a segunda cerftificação oficial que profissionais que trablham com Java podem tirar. A primeira é a Oracle Certified Associate (OCA). Se você quiser saber mais detalhes sobre a OCA, como estudar, agendar a prova e  obtê-la rapidamente, veja Obtendo sua OCA (Oracle Certified Associate Java SE 8) em 60 dias.

Oracle Certified Professional (OCP)

 Acabei de prestar o exame 1ZO-809, que é o exame sob o qual você se submete a fim de se tornar um Oracle Certified Professional em Java. Felizmente passei com 70%, o score mínimo para passar é 65%. Neste post dou meu feedback para você também passar no exame.



A OCP é uma continuação da OCA, portanto os criadores do exame partem da premissa de que todos os tópicos cobrados na OCA você já os tem dominados. Dessa forma, o exame da OCP inclui indiretamente todos os tópicos do exame anterior, mais uma série de novos objetivos, sobre a maioria dos quais é cobrado conhecimento profundo, principalmente sobre as classes dos novos pacotes incluídos a partir da versão 8 do Java, java.util.function e java.util.stream. Principais tópicos cobrados no exame 1ZO-809:
  • API de concorrência (Thread, Runnable, Callable, Executors, etc.)
  • Programação Funcional
  • Streams API
  • Java I/O
  • Java NIO
  • Generics e Collections
  • Design Patterns e Design Principles
Todo processo de agendamento do exame 1ZO-809 é idêntico ao da OCA (inclusive o preço), portanto não vou abordar essa parte. Para saber mais detalhes sobre o agendamento, confira
no link acima que contem o meu feedback sobre a OCA. Este post foca mais no conteúdo do exame OCP e métodos de estudo.

Até a data deste post, o Java estava na versão 11, de acordo com a nova estratégia da Oracle de lançar uma nova versão a cada 6 meses. No entanto, os exames de certificação disponíveis contemplavam somente até a versão 8. Isso não é um problema porque as diferença entre os recursos criados a partir do Java 8 até o 11 não são tão significativas quanto as mudanças introduzidas entre o Java 7 e 8. Outrossim ainda que a Oracle disponibilizasse amanhã um novo exame de certificação para a versão 10 ou 11, não seria recomendável fazê-lo logo de cara, porque você praticamente não terá nenhum sólido material de apoio como livros, fóruns, vídeos, etc. O exame da OCP 8 já existe há mais de 4 anos, muita gente já fez, muitos reprovaram, muitos passaram, há, portanto, bastante feedback e o material de apoio disponível para quem quiser encarar o desafio de obter a OCP é muito grande e diversificado. Este post é mais um.


Dificuldade do Exame

Indo direto ao ponto, o exame da OCP é muito mais difícil que o da OCA. Para passar, você precisará comer Java. Beber Java. Respirar Java. E - principalmente - pensar em Java. Seguindo essas dicas, o exame se torna fácil!

Sendo mais específico, o exame possui apenas 2 grandes dificuldades, as quais, você superando, tem grandes chances de ser bem sucedido. Superá-las é simples, mas não fácil.

Diferentemente da OCA, acredito não ser possível ser bem sucedido nesse exame se você não trabalha com Java. Esse é o ponto de partida. Você precisa de um nível de familiaridade com Java que dificilmente é obtido caso você não utilize a  tecnologia no dia-a-dia. Essa familiaridade é importante porque, não bastasse o profundo conhecimento exigido sobre cada tópico, os criadores do exame também são famosos por trapacear nas questões. Refiro me as famosas pegadinhas. Elas estão por toda parte. Essa é a primeira grande dificuldade a que me referi. Veja este exemplo: O que o código abaixo exibe na tela?

    Stream<Integer> prime = Stream.of(2,3,5,7);
    Stream<Integer> composite = Stream.of(4,6,8);
    ConcurrentMap<Boolean, List<Integer>> data =
          Stream.combine(prime, composite).parallelStream()
              .flatMap(s -> s)                        
              .collect(Collectors.groupingByConcurrent(n -> (n % 2) == 0));
        
    System.out.println(data.get(true).size()+" "+data.get(false).size());
Parece ser uma questão sobre parallel reduction, mas na verdade o código nem compila. A classe Stream não tem o método combine() e para criar um parallel stream a partir de um stream convencional você deve chamar o método parallel(), o método parallelStream() não existe.

Para não cair nesses truques, uma habilidade que você precisa treinar muito é se acostumar a ler códigos, de preferência sem formatação, e descobrir onde estão os erros de compilação. Claro, isso só depois de estar bem familiarizado com a API em questão. Assim você supera a primeira grande dificudade.
A segunda grande dificuldade é a quantidade muito extensa de tópicos cobrados no exame e o conhecimento profundo exigido sobre cada um. Superar essas dificuldade também é simples. Você precisa ter uma estratégia e uma metodologia de estudo, caso contrário ficará patinando, vai passar um ano, e você ainda não estará pronto para o exame! Compartilho minha estratégia de estudo.


Método de estudo

Comecei a estudar em 19/Março e fiz a prova em 31/7. Pouco mais de 4 meses de preparação. Acredito que não mais do que 6 meses de estudo para esse tipo de exame é um tempo aceitável.

1 - Escolha o Material de Apoio

A primeira coisa que você deve fazer é escolher seu material de apoio. Existem alguns livros no mercado, mas recomendo fortemente o OCP Study Guide de Jeanne Boyarsky e‎ Scott Selikoff da editora Sybex:


O livro é bem organizado e cobre 100% do conteúdo da prova. Os autores são didáticos, mas não fazem milagres. Você vai descobrir que é impossível ser didático tentando ensinar, por exemplo, o Fork/Join Framework ou Parallel Reductions. É o tipo de assunto que é o aluno quem aprende e não o professor quem ensina. Cada capítulo do livro possui cerca de 20 exercícios relacionados. O site da editora Sybex também tem 3 simulados com 60 questões cada um e repostas comentadas. Uma ferramenta valiosíssima da qual você não deveria abrir mão!


2 - Metodologia

Existe uma ciência do estudo. Muita coisa já foi publicada sobre como estudar de forma rápida e eficiente. Existem dezenas de técnicas que comprovadamente funcionam. Um livro que me deu vários insights foi Como passar no vestibular do dr. Lair Ribeiro. Utilizei as técnicas de anotação, comparação de abordagens, leitura skimming ou dinâmica, leitura analítica e mapas mentais.

Com uma caneta marca texto, primeiro faça uma leitura dinâmica sobre o capítulo. O objetivo nessa leitura não é entender tudo, mas se familiarizar com o assunto. A medida que for lendo, marque os trechos, imagens e tabelas que considerar importantes:

Marcando os trechos mais importantes para posterior releitura

Ao terminar a leitura dinâmica, recomece, agora com a leitura analítica, mais lenta e detalhada. A medida em que você prossegue coma leitura analítica, vá elaborando os mapas mentais (o software que usei foi o MindMUp 2.0 para o Google Drive), no mínimo cada mapa mental deve conter todas as subseções do capítulo em questão (mas essa regra pode ser quebrada, depende de cada um...). Mapas mentais são muito úteis porque o cerebro memoriza mais facilmente estruturas hierarquicas e coloridas. Há vários estudos demonstrando isso.


Exemplo de Mapa Mental sobre a API I/O

Durante a leitura analítica, implemente os vários códigos de exemplo e faça experiências.

Utilize a técnica de comparação de abordagens. O cerebro também memoriza mais facilmente quando você tem acesso a 2, 3 ou quatro abordagens diferentes sobre o mesmo assunto. Utilize essa técnica somente nos assuntos em que tiver mais dificuldade, se usar em todos, vai perder muito tempo. Utilizei a comparação de abordagens principalmente nos tópicos que cobrem Concurrency API e Stream API, para este último utilizei os capítulos 4 e 5 do livro Java 8 in Action de Raoul Urma, Mario Fusco e Alan Mycroft da editora Manning. Com relação a API Stream o livro Java 8 in Action possui uma abordagem muito mais interessante do que a presente em OCP Study Guide. Mas creio que as ambas se complementam:



Quando estiver mais ou menos na metade da leitura analítica de um capítulo, comece a fazer a leitura dinâmica do próximo capítulo. Prossiga assim até o final.


Revisões

Releia de forma periódica todos os trechos marcados e os mapas mentais. Revise a estrutura do mapa mental alguns dias depois de terminá-lo, acrescente ou elimine nós se achar necessário. A grande vantagem dos mapas mentais é que eles permitem que você estude uma grande quantidade de tópicos de forma rápida e ainda memorizá-los mais facilmente.

Optei por fazer os exercícios do livro e os simulados na última semana, somente depois de terminar todos os capitulos.


Por que tirar uma certficação?

Certificação oficial é uma vantagem enorme no seu currículo. Quem pensa o contrário, com certeza não tem nenhuma.

Uma certificação oficial significa que a empresa realizadora do exame (geralmente líder no seu segmento de mercado) assina embaixo que você possui todas as credenciais mencionadas. Por exemplo, a chefe de um RH pode não entender nada de Java e outras tecnologias em detalhe, mas com certeza ela conhece as marcas Oracle, Microsoft, IBM, CISCO, Amazon etc. Quando você tem a certificação, essas marcas passam a recomendar você!

Outro aspecto interessante, é que ao passar no exame você ganha um certificado em forma de diploma, geralmente assinado por algum executivo de alto escalão da empresa. Antigamente os certificados da Microsoft vinham com a assinatura de ninguém menos que Bill Gates!


Outro recurso bacana é que você ganha medalhas. E pode vinculá-las à URL que atesta sua certificação. Nessa URL qualquer pessoa no mundo inteiro pode checar as competências cobradas no exame que você passou. É um diferencial profissional e tanto!


Os exames são difíceis e isso é bom porque limita a oferta de certificados no mercado. Outra mensagem implícita nos certificados é que você é um profissional que encara desafios. Muita gente competente tem condições de passar, mas não está disposta a se arriscar. O mero agendamento do exame tem um custo muito alto. Se não passar, você simplesmente perde todo dinheiro. Não há absolutamente nenhuma garantia, exceto a sua dedicação em se preparar para o exame.

E aí vai encarar?


       

Wednesday, June 20, 2018

WildFly 12: Transaction was rolled back in a different thread!

Durante uma tentativa de configurar o banco de dados com a base de CEP dos Correios, obtive o seguinte erro:

 org.hibernate.HibernateException: Transaction was rolled back in a different thread!

Insistindo novamente, percebi que o erro ocorria sempre durante a inserção dos registros da tabela de logradouros, a qual possui mais de 1 milhão de registros.

Quando as transações são gerenciadas pelo container de aplicações (Container Managed Transactions), no meu caso o WildFly 12, elas têm um timeout defualt de 5 minutos. Ou seja, se você estiver realizando uma operação muito demorada, esse tempo pode se esgotar e o container vai lançar a excessão acima e dar um rollback na transação.

Para aumentar o timeout da transação, abra o console de administração do WildFly. A partir da aba Configurations, clique em Subsystems e depois Transactions. O Transaction Manager exibe uma série de opções de configuração para as transações, uma delas é o default timeout (em segundos). Clique em editar e coloque o valor que se adequa a sua demanda. Por exemplo, 900 segundos dividos por 60 equivalerão a transações com timeout de 15 minutos, ao invés de 5 (300 segundos).

Configuração de Transações no console de administração do WildFly 12

       

Saturday, June 16, 2018

Dual-Boot - Windows 10 e Linux: Instalando o Windows por último


Um sistema de Dual-Boot é um computador que possui dois ou mais sistemas operacionais instalados no mesmo HD, em partições diferentes, e dá a opção de carregar um ou outro durante a inicialização.

O Dual-Boot mais comum é criar duas partições e instalar uma versão do Windows em uma e uma do Linux na outra. Porém, dependendo de qual sistema operacional você instala primeiro, o Dual-Boot pode requerer mais algumas configurações para funcionar corretamente.

GRUB (GNU GRand Unified Bootloader) é o gerenciador de boot padrão em sistemas baseados em UNIX, como o Solaris e as distribuições Linux. Quando você liga o computador, o gerenciador de boot é o primeiro programa que roda, ele é responsável por carregar e tranferir o controle da máquina para o kernel do sistema operacional.

Quando você quer implementar um sistema Dual-Boot com Linux e Windows, e instala o Linux por último, o GRUB se encarrega de verificar se outras partições do HD possuem outros sistemas operacionais instalados, caso afirmativo, ao iniciar a máquina, ele exibe as opções disponíveis e espera o usuário selecionar qual sistema operacional ele quer carregar.

Porém se você instalar o Windows por último, ele sobreescreve o GRUB com o seu próprio gerenciador de boot, o Windows Boot Manager (BOOTMGR), o qual só é capaz de carregar versões do Windows, deixando outros sistemas Não-Microsoft incarregáveis (unloadable).

CENÁRIO: você tem um HD com duas partições. O Linux Mint 18.2 instalado em uma partição e o Windows 10 em outra. Como o Windows 10 foi instalado por último, o Linux não está mais carregando.

Implementando Dual-Boot após instalar o Windows 10 ao lado do Linux Mint 18.2

Basicamente você precisa reinstalar o GRUB, porém isso não poderá ser feito no Windows.

Crie uma imagem ISO da versão Linux que você usa em um pen drive. Você terá que dar um boot por esse dispositivo.

Após ligar a máquina, pressione F11 algumas vezes e depois F12 (essas teclas podem variar dependendo do fabricante),  esse procedimento faz exibir o setup da BIOS, no qual você pode escolher por meio de qual drive a máquina deve iniciar (a aparência desse menu também pode variar por fabricante). Escolha a opção que corresponda ao drive USB, que contem a imagem da versão Linux que você utiliza.

Bios Menu

Após escolher o drive USB, o Linux será carregado. Agora você deve acessar a partição que contem o Linux e reinstalar o GRUB na pasta boot. Para isso, abra o shell e execute os seguintes comandos:
  • sudo fdisk -l   para saber o nome da partição que contem o seu Linux       
  • sudo mount /dev/<nomePartição> /mnt   anexa à árvore de diretórios o sistema de arquivos da partição que contem o Linux
  • sudo grub-install --root-directory=/mnt dev/<nomeParticao>
  • sudo update-grub     reinstala o GRUB

Execute os comandos como na imagem abaixo. Nela o nome da partição esquecida que contem o Linux é sda1 (você deve substituir pelo nome que aparecer na sua máquina), cujo tamanho é 619,6 GB:

Comandos para atualizar o GRUB
Repare que ao executar o comando sudo grub-install, aparece a mensagem de erro failed to get canonical path of 'auf'. Você pode ignorará-la.

Reinicie sua máquina. Dessa vez o GRUB dará a opção de escolher se você quer carregar o Windows ou o Linux. Caso o Linux carregue diretamente sem dar a opção de escolher o Windows, instale o Boot Repair.

sudo add-apt-repository ppa:yannubuntu/boot-repair && sudo apt-get update
sudo apt-get install -y boot-repair && boot-repair

Após instalá-lo, clique no botão Recommended Repair.



Agora que o GRUB foi reparado, o Dual-boot estará implantado e ao iniciar a máquina você pode escolher qual sistema carregar:

Sistema de Dual-Boot

         

Friday, June 8, 2018

Cliente de Email com JavaFX

Clientes de email são processos que acessam um serviço de email em algum servidor remoto. Esse modelo também é conhecido como modelo cliente-servidor. A essência desse modelo é a existência de um processo cliente e um processo servidor, geralmente em máquinas diferentes, mas nem sempre. No contexto deste post, o processo cliente é uma aplicação Java que acessa os serviços disponibilizados por um servidor de email remoto.

aplicação cliente de email

JavaMail API 

JavaMail API é o framework Java para desenvolver sistemas que enviam, recebem e manipulam mensagens e qualquer tipo de conteudo através de um provedor de emails na internet. Sua especificação é definida pela JSR-919.  O nucleo duro da API fica no pacote javax.mail e é parte integrante do Java Enterprise Edition (No Java Standard Edition é necessário incluir explicitamente a biblioteca para usá-la no seu projeto).

O JavaMail é uma API de alto-nível. Ou seja, ela é capaz de abstrair os componentes de um sistema de email, tais como mensagens, protocolos, pastas, repositório, etc, de maneira bastante intuitiva para o desenvolvedor, isso torna a inclusão de rotinas de mensageria na sua aplicação Java mais fácil e rápida.

Por exemplo, tome-se a classe final javax.mail.Session, que representa uma sessão de comunicação estabelecida entre a aplicação cliente e algum servidor remoto:


Os métodos são auto explicativos:

getInstance(Properties props, Authenticator auth): fabrica uma instância de javax.mail.Session devidamente autenticada com algum servidor de email sob as propriedades definidas no objeto java.util.Properties.

getStore(): retorna o repositório de email daquela conta no servidor de email. A partir de javax.mail.Store você pode acessar as pastas do repositório, como INBOX por exemplo, e manipular as mensagens contidas naquela pasta.

Toda API JavaMail segue esse padrão instuitivo e desacoplado . As principais classes do pacote javax.mail como Store, FolderMessage, Address são abstratas e a implementação concreta fica a cargo do provedor de email com o qual você está trabalhando.
Uma aplicação pode interagir com outra aplicação ou com um ser humano, neste último caso precisamos de uma interface gráfica amigável que possibilite ao usuário conversar com o sistema. JavaFX é a mais recente plataforma multimídia do Java Standard Edition, sucessora do javax.swing, que permite o desenvolvimento de interfaces gráficas avançadas para aplicações desktop e web que executam em uma variedade de dispositivos.

Neste post criamos uma aplicação cliente de email que utiliza:
  1.  API JavaMail para se conectar com o servidor de email remoto 
  2. JavaFX para criar a Interface Gráfica com o Usuário

Projeto Cliente-Servidor

Utilizando o eclipse, navegue pelos menus File/New/project. No assistente que abrir escolha Maven/Maven Project. No próximo assistente marque a opção Create a Simple Project. Next. Dê o nome ao seu projeto de JavaFxEmailClient e finalize.

O arquivo de coordenadas pom.xml fica como segue:

<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">
 <modelVersion>4.0.0</modelVersion>
 <groupId>JavaFxEmailClient</groupId>
 <artifactId>JavaFxEmailClient</artifactId>
 <version>2.0</version>
 <properties>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>
 <dependencies>
  <dependency>
   <groupId>javax</groupId>
   <artifactId>javaee-api</artifactId>
   <version>8.0</version>
  </dependency>  
 </dependencies>
</project>


Agora clique com o botão direito no diretório raiz de seu projeto e escolha MAVEN/Update Project. Pronto. Agora que nosso projeto está devidamente configurado podemos começar a criar as classes. O seguinte diagrama de classes define o design do nosso cliente de email:



ArquivoModel.java
Como nosso sistema cliente poderá enviar mensagens com anexos, a classe ArquivoModel representa um Wrapper do arquivo real. O padrão Wrapper serve para encapsular funcionalidades e criar um nível adicional de abstração:
package main;

//Classe Wrapper para trabalhar com um arquivo anexado 
public class ArquivoModel {

 private String nomeArquivo;
 private byte[] conteudo;
 private String mimeType;
 
 public ArquivoModel(String nomeArquivo, byte[] conteudo, String mimeType) {
  super();
  this.nomeArquivo = nomeArquivo;
  this.conteudo = conteudo;
  this.mimeType = mimeType;
 }
   //getters & seters omitidos...
}


JanelaEnviar.java
A classe JanelaEnviar extende o container javafx.scene.layout.GridPane. Ela agrega todos os controles visuais que fazem a interface gráfica com o usuário e captura os eventos disparados pelo usuário:
package main;
//IMPORTS OMITIDOS...
//interface gráfica com o usuário GUI
public class JanelaEnviar extends GridPane {

 //controles da GUI
 TextField txtHostServer;
 TextField txtPara;
 TextField txtDe;
 TextField txtAssunto;
 TextField txtUsername;
 PasswordField txtSenha;
 Button btoEnviar;
 Label lblHostServer;
 Label lblPara;
 Label lblDe;
 Label lblAssunto;
 Label lblUserName;
 Label lblSenha;
 Text mensagem;

 // tabela de arquivos anexados
 TableView<ArquivoModel> tabelaAnexos;

 // lista de arquivos exibidos pela tabela
 ObservableList<ArquivoModel> listAnexos;

 // widget do javafx que gera tags html
 HTMLEditor htmlEditor;

 Scene scene;

 public JanelaEnviar(EventHandler<ActionEvent> eventControler) {
  // TODO Auto-generated constructor stub

  this.setAlignment(Pos.CENTER);
  this.setVgap(5);
  this.setHgap(5);
  this.setPadding(new Insets(10));

  txtHostServer = new TextField("smtp.gmail.com");
  txtPara = new TextField();
  txtDe = new TextField();
  txtAssunto = new TextField();
  txtUsername = new TextField();
  txtSenha = new PasswordField();

  btoEnviar = new Button("Enviar");
  btoEnviar.setOnAction(eventControler);

  lblHostServer = new Label("SMTP Server:");
  lblPara = new Label("Para:");
  lblDe = new Label("De:");
  lblAssunto = new Label("Assunto:");
  lblUserName = new Label("Login:");
  lblSenha = new Label("Senha:");
  mensagem = new Text();
  htmlEditor = new HTMLEditor();
  htmlEditor.setPrefHeight(430);

  // node, col, row
  this.add(lblHostServer, 0, 0);
  this.add(txtHostServer, 1, 0);
  this.add(lblPara, 0, 1);
  this.add(txtPara, 1, 1);
  this.add(lblDe, 0, 2);
  this.add(txtDe, 1, 2);
  this.add(lblAssunto, 0, 3);
  this.add(txtAssunto, 1, 3);
  this.add(lblUserName, 0, 4);
  this.add(txtUsername, 1, 4);
  this.add(lblSenha, 0, 5);
  this.add(txtSenha, 1, 5);

  this.add(setUpTabelaAnexos(), 0, 6, 2, 1);

  this.add(htmlEditor, 0, 7, 2, 1);

  VBox vBox = new VBox(5, btoEnviar, mensagem);
  vBox.setAlignment(Pos.CENTER);
  this.add(vBox, 0, 8, 2, 1);

  scene = new Scene(this);
 }

 // método auxiliar de configuraçã da tabela
 private Node setUpTabelaAnexos() {

  listAnexos = FXCollections.observableArrayList();

  // vincula a list na tabela
  tabelaAnexos = new TableView<>(listAnexos);

  // mensagem quando a tabela estiver vazia
  tabelaAnexos.setPlaceholder(new Label("Nenhum Arquivo Anexado"));

  // a tabela tera 2 colunas: nome do arquivo e outra coluna com um botão em cada
  // linha para excluir o anexo,
  // caso o usuário mude de ideia

  // configurando a coluna nome do arquivo
  TableColumn<ArquivoModel, String> colNomeArquivo = new TableColumn<>("Tabela de Anexos");

  // especifica o tipo de dado da coluna e em qual campo do objeto ele está
  colNomeArquivo.setCellValueFactory(new PropertyValueFactory<>("nomeArquivo"));
  colNomeArquivo.setPrefWidth(450);

  // coluna utilitária que possui um botão remover em cada linha
  TableColumn<ArquivoModel, ArquivoModel> colRemoveAnexo = new TableColumn<>();
  colRemoveAnexo.setPrefWidth(80);
  colRemoveAnexo.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue()));

  // configura o conteudo da coluna com um objeto TableCell personalizado
  colRemoveAnexo.setCellFactory(coluna -> new TableCell<ArquivoModel, ArquivoModel>() {

   final Button btoRemoveAnexo = new Button("X");

   // metodo updateItem é chamado automaticamente quando se constroi a tabela
   @Override
   protected void updateItem(ArquivoModel arquivoAnexo, boolean empty) {

    super.updateItem(arquivoAnexo, empty);

    // se a linha for vazia, não faz nada
    if (arquivoAnexo == null) {
     setGraphic(null);
     return;
    }
    // se tiver registro na linha, configura o botão
    setGraphic(btoRemoveAnexo);
    // toda vez quando o botão é clicado, o respectivo anexo é removido
    btoRemoveAnexo.setOnAction(event -> getTableView().getItems().remove(arquivoAnexo));
   };
  });

  // adiciona as coluna na tabela
  tabelaAnexos.getColumns().addAll(colNomeArquivo, colRemoveAnexo);
  tabelaAnexos.setPrefHeight(150);

  // configurando o botão anexar
  Button btoAddAnexo = new Button("Anexar...");

  // registrando o evento de ação para quando o botão for acionado pelo usuário
  btoAddAnexo.setOnAction(event -> {
   try {
    // dialog para seleção de arquivos
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("Escolher Anexo");

    // exibe a dialog de seleção de arquivos vinculada à JanelaEnviar
    // quando o usuário fecha a dialog de seleção ela retorna um objeto java.io.File
    Path anexo = fileChooser.showOpenDialog(((Node) event.getSource()).getScene().getWindow()).toPath();

    // verifica se o anexo realmente existe
    if (Files.exists(anexo)) {

     // extrai o tipo de dado que o anexo contem
     String mimeType = Files.probeContentType(anexo);
     System.out.printf("mime type %s", mimeType);

     // converte o anexo em byte[]
     byte[] conteudo = Files.readAllBytes(anexo);

     // extrai o nome do anexo
     String nomeAnexo = anexo.getFileName().toString();

     // cria o objeto ArquivoModel, utilitario para manipular os anexos
     ArquivoModel novoAnexo = new ArquivoModel(nomeAnexo, conteudo, mimeType);

     // adiciona o anexo na lista vinculada à tabela
     listAnexos.add(novoAnexo);
    }
   } catch (NullPointerException | IOException e) {
    e.printStackTrace();
   }
  });

  // colaca a tabela e o botão addAnexo em uma VBox e retorna
  VBox vBoxTabelaAnexo = new VBox(5, tabelaAnexos, btoAddAnexo);

  return vBoxTabelaAnexo;
 }

 // retorna os anexos
 ArquivoModel[] getAnexos() {
  return listAnexos.toArray(new ArquivoModel[listAnexos.size()]);
 }

 void setMensagemDeSucesso(String msg) {
  mensagem.setFill(Color.GREEN);
  mensagem.setText(msg);
 }

 void setMensagemDeErro(String msg) {
  mensagem.setFill(Color.RED);
  mensagem.setText(msg);
 }
}

EventoEnviarEmail.java
A classe EventoEnviarEmail gerencia os eventos do usuário que ocorram na classe JanelaEnviarEmail. Os objetos da API JavaMail são usados aqui:
package main;

//IMPORTS OMITIDOS
//Controle de Eventos da JanelaEnviar
public class EventoEnviarEmail implements EventHandler{

 JanelaEnviar janelaEnviar;
 String hostServer;
 String para;
 String de;
 String assunto;
 String username; 
 String senha;
 String conteudoHtmlMensagem;
 
 ArquivoModel[] anexos;   
 
 public void handle(ActionEvent event) {
  // TODO Auto-generated method stub
  preencherCampos();
  
  try {      
   enviarEmail();          
   janelaEnviar.setMensagemDeSucesso("Email Enviado com Sucesso");
   System.out.println("OK");
  }
  catch (UnsupportedEncodingException e) {
   janelaEnviar.setMensagemDeErro("Caracteres não suportados na mensagem");
   e.printStackTrace();
  }
  catch (AuthenticationFailedException e) {
   //endereco invalido
   janelaEnviar.setMensagemDeErro("Usuário ou senha Inválidos");
   e.printStackTrace();
  }
  catch (AddressException e) {
   //endereco invalido
   janelaEnviar.setMensagemDeErro("Formato de endereço de email Inválido");
   e.printStackTrace();
  }
  catch (MessagingException e) {
   //endereco invalido
   janelaEnviar.setMensagemDeErro("Erro ao enviar a mensagem");
   e.printStackTrace();
  }
  catch (Exception e) {
   // TODO: handle exception
   janelaEnviar.setMensagemDeErro("Erro ao enviar a mensagem");
   e.printStackTrace();
  }
  finally {
   janelaEnviar.txtSenha.setText("");
  }
 }
 
 private void enviarEmail() throws AddressException, MessagingException, UnsupportedEncodingException {
  
  // TODO Auto-generated method stub
  Authenticator authenticator = new MeuAutenticador(username, senha);
  
  //configura objeto properties
  Properties props = new Properties();
  props.put("mail.smtp.host", hostServer);  
  props.put("mail.smtp.host", "smtp.gmail.com");
  props.put("mail.smtp.socketFactory.port", "465"); //porta ssl
  props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); //especifica classe para criar SMTP Socket
  props.put("mail.smtp.auth", "true"); //autenticação requerida  
  
  //obtem um objeto Session sob as credenciais e propriedades especificadas
  Session session = Session.getInstance(props, authenticator);
  
  Message mensagem = new MimeMessage(session);
  
  Address enderecoDe = new InternetAddress(username, de); //address except
  Address enderecoPara = new InternetAddress(para);
  
  mensagem.setFrom(enderecoDe); //messaging excep
  mensagem.setRecipient(Message.RecipientType.TO, enderecoPara);
  mensagem.setSubject(assunto);  
  
  //coloca o conteudo e os anexos na mensagem
  setConteudoMensagem(mensagem, anexos);    
    
  Transport.send(mensagem);  //auth failed except
 }
 
 private void setConteudoMensagem(Message mensagem, ArquivoModel[] arquivos) throws MessagingException {
  
  Multipart multipart = new MimeMultipart();  
  
  //cria um BodyPart representando um conteudo HTML
  BodyPart messageBodyPart = new MimeBodyPart();    
  
  messageBodyPart.setContent(conteudoHtmlMensagem, "text/html");
  
  //adiciona o BodyPart nop Multipart
  multipart.addBodyPart(messageBodyPart);
  
  messageBodyPart = new MimeBodyPart();
  
  //DataHandler para operações diversas nos diferentes tipos de dados
  DataHandler dataHandler = null;     
       
  //loop: cria um BodyPart para cada anexo e adiciona-o no Multipart 
  for(ArquivoModel anexo : arquivos) {
   
   dataHandler = new DataHandler(anexo.getConteudo(), anexo.getMimeType());
   messageBodyPart = new MimeBodyPart();         
   messageBodyPart.setDataHandler(dataHandler);
   messageBodyPart.setFileName(anexo.getNomeArquivo());
   multipart.addBodyPart(messageBodyPart);
  }  
  
  //adiciona o MUltipart na mensagem
  mensagem.setContent(multipart);
 }
  

 private void preencherCampos() {
  // TODO Auto-generated method stub
  hostServer = janelaEnviar.txtHostServer.getText();
  para = janelaEnviar.txtPara.getText();
  de = janelaEnviar.txtDe.getText();
  assunto = janelaEnviar.txtAssunto.getText();
  username = janelaEnviar.txtUsername.getText();
  senha = janelaEnviar.txtSenha.getText();
  anexos = janelaEnviar.getAnexos();
  //extrai o conteudo html de HTMLEditor na forma de uma String 
  conteudoHtmlMensagem = janelaEnviar.htmlEditor.getHtmlText();  
 } 
}

MeuAutenticator.java
No JavaMail, para logar em uma conta de email no servidor remoto, você precisa sobrescrever o método getPasswordAuthentication() da classe javax.mail.Authenticator porque é esse método que o objeto javax.mail.Session chama para efetivar o login. Se a tentativa de login feita por Session falhar, a exceção javax.mail.AuthenticationFailedException é lançada:
package main;

//IMPORTS OMITIDOS
public class MeuAutenticador extends Authenticator {

 private final PasswordAuthentication passwordAuthentication; 
 
 //passamos login e senha no construtor
 public MeuAutenticador(String login, String senha) {
  super();
  passwordAuthentication = new PasswordAuthentication(login, senha);
 }
 
 //toda aplicação deve sobrescrever getPasswordAuthentication()
 @Override
 protected PasswordAuthentication getPasswordAuthentication() {
  // TODO Auto-generated method stub
  
  return passwordAuthentication;
 }
}

Main.java
Na classe Main iniciamos a aplicação. Para inciar uma aplicação JavaFx, sobrescrevemos o método start() da classe abstrata javafx.application.Application:
package main;

import javafx.application.Application;
import javafx.stage.Stage;

//Enviando conteudo HTML
public class Main extends Application {

 @Override
 public void start(Stage primaryStage) throws Exception {
  // TODO Auto-generated method stub
  EventoEnviarEmail evento = new EventoEnviarEmail();
  JanelaEnviar janelaEnviar = new JanelaEnviar(evento);
  
  evento.janelaEnviar = janelaEnviar;
  
  primaryStage.setTitle("Cliente de Email com JavaFX");
  primaryStage.setScene(janelaEnviar.scene);  
  primaryStage.show();  
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  launch(args);
 }
}

Agora já podemos executar o projeto. O resultado é a aplicação que exibimos no início deste post:



Após preencher todos os campos, envie a mensagem ao destinatário clicando no botão enviar.


       

Thursday, May 10, 2018

Relatórios Utilizando Bean Datasource (parte 2)

Utilizando o JasperStudio

Como dissemos na parte 1 deste artigo, o JasperStudio é uma ferrementa visual que agiliza a criação de relatórios, de outra forma, teríamos que criar o arquivo jrxml na mão. Caso não tenha o JasperStudio instalado, você pode baixá-lo aqui.


Abra o JasperStudio. Clique com o botão direito do mouse em MyReports/New/Jasper Report:


Escolha Blank A4. Depois Next. Dê o nome do relatório de relatorioFuncionarios e clique em Finish:



Após criar o relatório, você terá a seguinte tela:


O painel Pallete à direita representa os componentes que você pode arrastar e colocar no relatório (textos estáticos, linhas, imagens, campos de texto, imagens, subrelatórios, etc.).

No painel Outline a esquerda, você tem os Fields, cujos nomes devem coincidir com os atributos da classe RelatorioFuncionariosBean.java, portanto, é através dos elementos Field que o JasperReport saberá quais métodos ele deve chamar na classe java para preencher os campos dinâmicos do relatório. Ainda no painel Outiline, temos o item Parameters que representa parâmetros dinâmicos que acrescentamos no relatório em tempo de execução utilizando java.util.Map (nome do parâmetro/valor do parâmetro).

Clique com o botão direito no item Parameters e escolha a opção Create Parameter. No painel Properties à direita aparece as propriedades do parâmetro que você criou, cujo nome padrão é Parameter1. Dê o nome a esse parâmetro de FuncionariosDataSource e em class coloque net.sf.jasperreports.engine.data.JRBeanCollectionDataSource.


Da mesma forma, crie outro parâmetro chamado LOGOMARCA e defina o tipo da classe para java.io.InputStream.

Repare que o relatório é divido em bandas, cujos nomes são auto-explicativos, com exceção da banda Detail 1 que pode ser usada para campos cujo tipo estende java.util.Collection.

Se o seu data source tem campos que extendem a interface java.util.Colleciton, então você pode exibí-los no seu relatório utilizando os elementos Subreport, Table ou List do painel Palette. No nosso caso utilizarema a Table para exibir os funcionários.

Adiconado Table no Relatório

No painel Outline clique com o botão direito do mouse em cada banda e delete-a. Com exceção de Detal1 e Title. Ele vai ficar como segue:


No painel Palette ache o elemento Table e adicione-o na banda Detail1. No assitente Dataset que vai aparecer escolha a opção Create Table using a new dataset e Next:


Em seguida escolha a opção Create an empty dataset e dê o nome para o seu dataset de TabelaFuncionarios e Next:


Na janela Connection, marque a opção Use JRDataSource expression.



Clique no botão assitente, localizado à direita, para abrir o ExpressionEditor. No painel esquerdo selecione parameters e no painel do meio ache o parametro que você criou FuncionariosDatasource, dê duplo clique nele e Finish. Depois Finish de novo na janela connection.


O elelemento Tabela aparece no seu relatório. Precisamos de uma tablea com apenas duas colunas, nome e cpf do funcionário, não será necessário cabeçalhos nem rodapé. Para editar seu formato, dê uma duplo clique na tabela. Vamos deixá-la com duas colunas e uma linha, excluindo as demais.



Repare que a tabela também possui bandas, assim como o relatório principal. Mas só vamos precisar da banda detail e column header. Clique com o botão direito nas demais e delete-as. Crie mais uma coluna na tabela. No painel Palette localize o elemento lable static e adicione um em cada coluna.


No painel Outline, clique com o botão direito do mouse no item fields e adicione um campo nome e outro cpf, ambos do tipo String. Após criá-los, arraste-os na repsectiva linha da tabela.


Clique no botão main report e volte para a perspectiva do relatório inteiro. Assim como você criou os campos cpf e nome do funcionario, crie mais dois campos para representar o nome e o cnpj da empresa e arraste-os para a banda title do relatório, associe também os respectivos static labels para esses campos. Arraste tabem um componente Image para representar a logomarca da empresa.


Agora, nas propeidades do componente Image, em Expression, coloque o parâmetro LOGOMARCA, como $P{LOGOMARCA}.


Pronto. Salve e compile o relatório. O JasperStudio vai gerar o arquivo relatorioFuncionarios.jasper na pasta do seu projeto na aba Project Explorer à esquerda. Copie esse arquivo e cole-o no pacote relatorios do seu projeto no eclipse.

Execute o projeto e gere o relatório: