Countdown

Final Countdown

Faltam dias. Ou segundos.

segunda-feira, 1 de junho de 2015

#24) Aplicações Web

Boa noite,

Hoje o tópico será sobre Aplicações Web.

1. Introdução (Segurança)

Vamos falar hoje sobre um dos campos mais hostis e inóspitos que existe no ramo da programação: O ambiente Web. Um ambiente fértil para novas aplicações e que representa cada vez mais o futuro do desenvolvimento de softwares, mas ao mesmo tempo traz uma série de dificuldades.

Adicionei um novo Gadget ao meu blog para demonstrar como a Web pode ser um ambiente terrível e perigoso. O Gadget mostra em tempo real o fluxo de ataques DDOS realizados ao redor do mundo. Lembrando a definição de ataque DDOS:

"A Distributed Denial of Service (DDoS) attack is an attempt to make an online service unavailable by overwhelming it with traffic from multiple sources. They target a wide variety of important resources, from banks to news websites, and present a major challenge to making sure people can publish and access important information".



Se você, leitor, se interessou pelo tema, aconselho que aprofunde um pouco mais sua visão com algum dos links da referência deste post. Sei que minha abordagem apreensiva quanto à segurança é um pouco inusitada, mas é ela que explica fatos como, por exemplo:
  •  A implementação de Applets em Java ser tão difícil e pouco estimulada devido aos perigos que uma máquina virtual (proveniente da execução do aplicativo) pode ocasionar ao seu utilizador.
  • Não há possibilidade de se utilizar diretamente serviços Sockets (explicaremos neste post) e envio de Packets empregando Javascript, o que poderia ser um desastre de segurança.
  • A enorme dificuldade de se hospedar arquivos online de forma gratuita, principalmente de aplicações que não sejam em HTML (um arquivo executável .jar, por exemplo), devido às aplicações maliciosas que viriam daí.
E estes são alguns poucos itens que enuncio aqui, dificuldades sérias para o nosso projeto Web que se originam fundamentalmente de problemas de segurança em rede.


2. Predefinição

A linguagem Java conta com uma boa API de Sockets pelo pacote java.net, que será o mais utilizado aqui neste post. Antes, vamos tomar algumas definições básicas:

Glossário

  1. Socket (soquete de rede) é o ponto final do fluxo de informação entre 2 aplicativos através de uma rede.
  2. Packet (pacote) é uma estrutura unitária de transmissão de dados em uma rede de comunicação.
  3. Protocolo (TCP - Transmission Control Protocol) é uma convenção que controla e possibilita uma conexão/ comunicação/ transferência de dados entre dois sistemas computacionais.
  4. IP (IP - Internet Protocol) pode ser definido como um protocolo de comunicação em conjunto com o TCP (TCP/IP).
  5. Endereço IP é uma identificação de um dispositivo em uma rede local ou pública. Cada endereço de domínio é convertido em um endereço IP pelo DNS (resolução de nomes).
  6. Porta, assim como o IP existe para identificar uma máquina, a porta é solução para identificar as diversas aplicações de uma máquina. É um número de 2 bytes (varia de 0 a  65535)
  7. DNS é um sistema de gerenciamento de nomes hierárquicos distribuídos para computadores, serviços ou qualquer recurso conectado à internet ou a uma rede privada.
  8. IPV4 é um cabeçalho que contém as informações a serem transmitidas segundo uma certa lógica. Seu endereçamento possui 32 bits.
  9. UDP é um protocolo simples da camada de transporte. Permite que a aplicação escreva um datagrama encapsulado num pacote IPV4 ou IPV6 e então envia ao destino, mas não garante a chegada ao destino ou não.
  10. Datagrama é uma unidade de transferência básica associada a uma rede de comutação de pacotes em que a entrega, hora de chegada e ordem não são garantidas.
  11. Domínio é um nome que serve para localizar um conjunto de computadores na internet.
  12. Servidor é uma aplicação central que espera a conexão de Clientes para a sua execução.

Blocos de Endereços Reservados
CIDR Bloco de EndereçosDescriçãoReferência
0.0.0.0/8Rede corrente (só funciona como endereço de origem)RFC 1700
10.0.0.0/8Rede PrivadaRFC 1918
14.0.0.0/8Rede PúblicaRFC 1700
39.0.0.0/8ReservadoRFC 1797
127.0.0.0/8LocalhostRFC 3330
128.0.0.0/16Reservado (IANA)RFC 3330
169.254.0.0/16ZeroconfRFC 3927
172.16.0.0/12Rede privadaRFC 1918
191.255.0.0/16Reservado (IANA)RFC 3330
192.0.2.0/24DocumentaçãoRFC 3330
192.88.99.0/24IPv6 para IPv4RFC 3068
192.168.0.0/16Rede PrivadaRFC 1918
198.18.0.0/15Teste de benchmark de redesRFC 2544
223.255.255.0/24ReservadoRFC 3330
224.0.0.0/4Multicasts (antiga rede Classe D)RFC 3171
240.0.0.0/4Reservado (antiga rede Classe E)RFC 1700
255.255.255.255Broadcast

3. Sockets (Java)

Vamos esquentar um pouco com um pequeno exemplo de aplicação Cliente - Servidor em Java, em que cliente e servidor trocam uma simples mensagem entre si:

A aplicação Servidor disponibiliza uma determinada porta e espera a conexão do Cliente, para depois realizar a leitura dos dados enviados por ele, para depois enviar uma curta mensagem. Note que há a criação de um Socket de conexão:

ServerSocket servidor = new ServerSocket( porta );

Server.java
package sockets;

import java.io.*;
import java.net.*;
import java.util.*;

public class Server 
{
    static String Mensagem = "Hi", N;
    static String IPclient, IPserver, porta;
    static Socket cliente;
    public static void main(String[] args) throws UnknownHostException, IOException 
    {
        // Campo para exibir IPserver e porta
        IPserver = InetAddress.getLocalHost().getHostAddress();
        porta = "7777";
        
        ServerSocket servidor = new ServerSocket(Integer.parseInt(porta));
        
        // Detecção de nova requisição do cliente.
        while (cliente == null) 
        {
            cliente = servidor.accept();
            IPclient = cliente.getLocalAddress().getHostAddress();
        }
            // Leitura do valor de N
            Scanner s = new Scanner(cliente.getInputStream());
            N = s.nextLine();
            System.out.println(N);

            // Escreve a mensagem codificada.
            PrintStream saida = new PrintStream(cliente.getOutputStream());
            saida.println(Mensagem);
            
            
            while(!cliente.isClosed())
        
        saida.close();        
        cliente.close();
    }

}

Para a aplicação Cliente, temos a conexão ao servidor e depois o subsequente envio envio de uma mensagem, para depois aguardar a mensagem enviada pelo cliente. Note que a conexão Socket é realizada no formato (IP, porta), conforme a sua definição:

 Socket cliente = new Socket( IP, porta );

Client.java
package sockets;

import java.io.*;
import java.net.*;
import java.util.*;

public class Client 
{
    static String Mensagem, N = "10";
    static String IPclient, IPserver, porta;
    static Socket cliente;
    public static void main(String[] args) throws UnknownHostException, IOException 
    {
        // Campo para digitar IPserver & Porta & exibir IPcliente
        IPserver = "192.168.197.1";
        porta = "7777";
        IPclient = InetAddress.getLocalHost().getHostAddress();
        
        // Conexão ao servidor
        cliente = new Socket(IPserver, Integer.parseInt(porta));
        
            // Cliente envia o valor de "n"
            PrintStream saida = new PrintStream(cliente.getOutputStream());
            saida.println(N);
            
            // Realiza a leitura da mensagem
            Scanner s = new Scanner(cliente.getInputStream());
            Mensagem = s.nextLine();
            System.out.println(Mensagem);

        saida.close();
        cliente.close();
    }
}

Como o leitor pode observar, o uso da aplicação é bem direto, sendo que o grande desafio é sincronizar o envio das mensagens usando Threads, identificar usuários que se conectam ao servidor e distribuir portas de conexão a eles, permitir diversos clientes simultâneos, etc.

Estes desafios podem ser resolvidos usando os conceitos que viemos desenvolvendo até agora no blog, de modo que deixaremos isso ao encargo do leitor.

4. WebSockets (Javascript)


Note que para usar WebSockets, será necessário criar um arquivo HTML estático como estrutura para o código dinâmico do Javascript. Fundamentalmente, Javascript não permite a utilização de Sockets diretamente como ocorre na linguagem Java, por motivos de segurança. 

No entanto, WebSockets surge como uma alternativa moderna para contornar esse problema, usando funções encapsuladas de forma a restringir o uso de Sockets e a sua implementação.

Abaixo temos um exemplo de aplicação de WebSockets:

<!DOCTYPE HTML>
<html>

   <head>
 
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("WebSocket is supported by your Browser!");
               // Let us open a web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
    
               ws.onopen = function()
               {
                  // Web Socket is connected, send data using send()
                  ws.send("Message to send");
                  alert("Message is sent...");
               };
    
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("Message is received...");
               };
    
               ws.onclose = function()
               { 
                  // websocket is closed.
                  alert("Connection is closed..."); 
               };
    
            }
            else
    
            {
               // The browser doesn't support WebSocket
               alert("WebSocket NOT supported by your Browser!");
            }
         }
      </script>
  
   </head>
 
   <body>
      <div id="sse">
         <a href="javascript:WebSocketTest()">Run WebSocket</a>
      </div>
   </body>
 
</html>

Por outro lado, devemos empregar um Servidor, como por exemplo o Glassfish, para a criação de um terminal Servidor, com um endereço do tipo:

"ws://localhost:8080/Bizuca/server"

package Servidor;

import java.io.IOException;
import java.util.ArrayList;
 
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/server") 
public class Servidor 
{   
    ArrayList<Player> Players = new ArrayList<>();
    public static int i = 0;
    public static String Msg = "";
    
    @OnOpen
    public void onOpen(Session session)
    {
        Players.add(new Player("null",session.getId()));
        try {
            session.getBasicRemote().sendText("Conexão estabelecida. Nº"+(i++));
        } catch (IOException ex) { }
    }
    
    @OnMessage
    public void onMessage(String message, Session session)
    {
        Msg = message;
            message+=": Sender : ("+session.getId()+")";
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException ex) { }
    }
 
    @OnClose
    public void onClose(Session session){
        for(Player P: Players)
            if (P.Id.equals(session.getId()))
                Players.remove(P);
    }
}

É possível inclusive colocar essa aplicação online com uso de servidor Glassfish, empregando por exemplo o serviço Layershift (versão trial de 14 dias), com permissão ao uso de banco de dados online e execução da sua aplicação em um ambiente online.

Ambiente Layershift para Upload de projetos.


Porém sempre existem alternativas, como o emprego da API Sockets.io, que permite o uso de Sockets de maneira simplificada, ou da API jsocket, desenvolvida no MIT, que emprega arquivos Flash para burlar o sistema de segurança agressivo de Javascript.

5. Programando em baixo nível

Somente para ilustrar um pouco mais os conceitos abordados neste tópico, temos o seguinte exemplo de aplicação de Datagramas (a unidade básica de transferência de dados) em um chat, permitindo vislumbrar em baixo nível como é realizada a troca de informações na Web:

A classe Conexão realiza a conexão dos usuários e também se encarrega da interpretação e da troca de mensagens, convertendo Datagrams em mensagens e vice-e-versa:

package online;
import java.io.IOException;
import java.util.logging.*;
import java.net.*;
import java.util.*;

public class Conexao extends Observable 
{
    private String ip, mensagem;
    private int porta;

    public void SetConexao(String ip, int porta) {
        this.ip = ip; this.porta = porta;
        new Thread(new Recebe()).start();
    }
    public String getIP () {
        try {
            return ""+InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException ex) {
            Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "";
    }
    public String getMensagem() { return mensagem; }
    public String getIp() { return ip; }
    public int getPorta() {  return porta;  }
    public void envia(String texto) {  new Thread(new Envia(texto)).start();  }

    public void notifica(String mensagem) {
        this.mensagem = mensagem;
        setChanged();
        notifyObservers();
    }

    class Recebe implements Runnable 
    {
        byte[] dadosReceber = new byte[255];
        boolean erro = false;
        DatagramSocket socket = null;

        @Override
        public void run() {
            while (true) {
                try {
                    socket = new DatagramSocket(getPorta());
                } catch (SocketException ex) {
                    Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
                }
                erro = false;
                while (!erro) {
                    DatagramPacket pacoteRecebido = new DatagramPacket(dadosReceber, dadosReceber.length);
                    try {
                        socket.receive(pacoteRecebido);
                        byte[] b = pacoteRecebido.getData();
                        String s = "";
                        for (int i = 0; i < b.length; i++)
                            if (b[i] != 0)
                                s += (char) b[i];
                        String nome = pacoteRecebido.getAddress().toString() + " disse:";
                        notifica(nome + s);
                    } catch (Exception e) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        erro = true;
                        continue;
                    }
                }
            }
        }
    }

    class Envia implements Runnable 
    {
        String texto;
        public Envia(String texto) {
            this.texto = texto;
        }
        @Override
        public void run() {
            byte[] dados = texto.getBytes();

            try {
                DatagramSocket clientSocket = new DatagramSocket();
                InetAddress addr = InetAddress.getByName(getIp());
                DatagramPacket pacote = new DatagramPacket(dados, dados.length, addr, getPorta());
                clientSocket.send(pacote);
                clientSocket.close();
            } catch (SocketException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            } catch (UnknownHostException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Interface gráfica, responsável por capturar as mensagens e exibi-las, além de gerenciar as ferramentas usadas pelo usuário:

package online;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Observable;
import java.util.Observer;

public class GUI extends JFrame implements Observer 
{
    int xMouse, yMouse;
    private Conexao conex;
    private JTextField textoIP, textoPorta;
    private JTextArea chatjTextArea, mensagemjTextArea;     
    private JButton enviarjButton, connect;
    private JScrollPane jScrollPane1, jScrollPane2; 
    private JLabel credito, IP, porta, meuIP;
    
    public GUI (Conexao conexao)
    {
        conex = conexao;
        conex.addObserver(this);
        initComponents();
        initEvents();
        setBounds(250,150,600,550);
        escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta());
        meuIP.setText("Meu IP: "+conex.getIP());
    }
    private void initComponents()
    {
        setTitle("Xhat");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setBackground(new Color(0, 0, 0));
        setLocationRelativeTo(null); setLayout(null); setVisible(true);
        
        IP = new JLabel("IP:"); IP.setForeground(Color.white); IP.setBounds(50,50,100,20); add(IP);
        porta = new JLabel("Porta:"); porta.setForeground(Color.white); porta.setBounds(200,50,100,20); add(porta);
        meuIP = new JLabel(); meuIP.setForeground(Color.white); meuIP.setBounds(200,20,150,20); add(meuIP);
        
        textoIP = new JTextField("192.168.0."); textoIP.setBounds(70,50,100,25);
        textoIP.setBackground(Color.black); textoIP.setForeground(Color.white); add(textoIP);
        textoPorta = new JTextField("5000"); textoPorta.setBounds(250,50,100,25);
        textoPorta.setBackground(Color.black); textoPorta.setForeground(Color.white); add(textoPorta);
        
        chatjTextArea = new JTextArea();  chatjTextArea.setEditable(false);
        chatjTextArea.setForeground(Color.white); chatjTextArea.setBackground(Color.black);
        jScrollPane1 = new JScrollPane(); jScrollPane1.setBounds(50,100,450,300); add(jScrollPane1);
        jScrollPane1.setViewportView(chatjTextArea);
        
        mensagemjTextArea = new JTextArea(); mensagemjTextArea.requestFocusInWindow();
        mensagemjTextArea.setForeground(Color.white); mensagemjTextArea.setBackground(Color.black);
        jScrollPane2 = new JScrollPane(); jScrollPane2.setBounds(50,400,350,50); add(jScrollPane2);
        jScrollPane2.setViewportView(mensagemjTextArea);
        
        connect = new JButton("Conectar"); 
        connect.setBackground(Color.black); connect.setForeground(Color.white);
        connect.setBounds(400,50,100,30); add(connect);
        
        enviarjButton = new JButton("Enviar"); 
        enviarjButton.setBackground(Color.black); enviarjButton.setForeground(Color.white);
        enviarjButton.setBounds(400,400,100,50); add(enviarjButton);

        credito = new JLabel("By Tuyama"); credito.setForeground(Color.white);
        credito.setBounds(250,475,100,20); add(credito);

        pack();
    }
    
    private void initEvents()
    {
        mensagemjTextArea.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                if (evt.getKeyCode() == KeyEvent.VK_ENTER)
                    envia();
            }
        });
        enviarjButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                envia();
            }
        });
        connect.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                conex.SetConexao(textoIP.getText(),Integer.parseInt(textoPorta.getText()));
                chatjTextArea.setText("");
                escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta());
            }
        });
        addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent e) { }
            public void mouseMoved(MouseEvent e) {
                xMouse = e.getX();
                yMouse = e.getY();
            }
        });
    }
    
    private void envia(){
        if (mensagemjTextArea.getText().equals("\n"))
            conex.envia("&"+xMouse+"@"+yMouse);
        if (!mensagemjTextArea.getText().isEmpty()) {
            conex.envia(mensagemjTextArea.getText());
            escreve("Você disse: "+mensagemjTextArea.getText());
            mensagemjTextArea.setText("");
        }
    }
    private void escreve(String texto){
        chatjTextArea.append(texto+"\n");
         if (!chatjTextArea.getText().isEmpty() && !chatjTextArea.isFocusOwner())
                chatjTextArea.setCaretPosition(chatjTextArea.getText().length() - 1);
    }
    @Override
    public void update(Observable o, Object arg) {
        String Msg = conex.getMensagem();
        int MouseX, MouseY, S1, S2;
        if (!Msg.contains("&")&&!Msg.contains("@"))
            escreve(Msg);
        else {
            for (S1=0; Msg.charAt(S1)!='&'; S1++);
            for (S2=0; Msg.charAt(S2)!='@'; S2++);
            MouseX = Integer.parseInt(Msg.substring(S1+1, S2));
            MouseY = Integer.parseInt(Msg.substring(S2+1));
            credito.setBounds(MouseX,MouseY,100,20);
        }
    }
}

Observe que a estrutura deste programa usa um conceito de Design Patterns (Observable & Observer) permitindo sincronizar o envio e recibo das mensagens, permitindo a troca fluente de mensagens entre as partes envolvidas.

6. Conclusão

A programação de Aplicações Web é complexa e promissora. Seria possível escrever cursos inteiros sobre suas aplicações e conceitos, aprofundar no tema de segurança e estudar mais a fundo a dinâmica de troca de informações.

Mas creio que o pequeno post de hoje deu uma pequena abordagem do tema ao nosso leitor, que agora pode se considerar iniciado no tema.

7. Referência:

(Ataque DDOS): 

(Sockets):

(Sockets & Javascript):

(Glassfish - Web Sockets (Javascript))

(jSocket - Javascript)

(Socket.io - Alternativa)

quarta-feira, 27 de maio de 2015

#23) Banco de Dados MySQL

Olá!

Hoje abordaremos o assunto Banco de Dados. Atualmente, ouvimos falar o tempo todo sobre MySQL, SQL, Database, etc no ambiente de desenvolvimento de softwares, desde pequenas até grandes empresas.
 
A verdade é que a computação se encarrega de uma tarefa crucial que é o armazenamento de dados e da criação de estruturas cada vez mais eficientes para se gerenciá-las, minimizando o custo de memória para gravar estes dados e também tornando os algoritmos de manipulação de dados mais velozes.

"Bancos de dados são coleções organizadas de dados que se relacionam de forma a criar algum sentido (Informação) e dar mais eficiência durante uma pesquisa ou estudo.São de vital importância para empresas e há duas décadas se tornaram a principal peça dos sistemas de informação. Normalmente existem por vários anos sem alterações em sua estrutura."

O MySQL é um produto da Sun, é um dos sistemas gerenciadores de banco de dados mais usados e indicados mundialmente pela sua alta performance e por ser opensource.

Independente do banco de dados usado, a linguagem padrão para a manipulação de dados é a SQL (Structured Query Language). Existem até mesmo frameworks de alto nível para a criação de banco de dados, mas estas ferramentas não serão abordadas neste tópico.

Manipulação de Banco de Dados

Qualquer seja o Sistema Gerenciador de Banco de Dados usado, temos quatro etapas básicas para criar o ambiente de manipulação de banco de dados:

  1. A criação do banco de dados.
  2. A inclusão do driver a ser usado para a conexão ao banco de dados;
  3. A definição do endereço (URL) da localização do banco de dados que será incluído na aplicação.
  4. A criação da aplicação java propriamente dita para acessar os dados.
Não é um procedimento de 7 cabeças, mas é um tanto complicado. O primeiro passo é razoável por ser bem documentado na internet. O quarto é o emprego do banco de dados no software, portanto podemos considerar como trivial. Já o segundo requer um pouco mais de atenção e será abordado neste post. Já o terceiro nem será abordado aqui. Apenas utilizaremos o banco de dados local.

1. Instalação do MySQL

Para instalar o MySQL é preciso basicamente acessar o site oficial, baixar e instalar o programa:

Em caso de dúvidas, você pode consultar a referência.

Há duas maneiras básicas de se utilizar o MySQL. A primeira é através do Prompt de Comando e a segunda é através do NetBeans. Como trabalhamos com a ferramente NetBeans neste blog, vamos optar por utilizá-la devido à maior familiaridade do leitor.

Abra o NetBeans e registre um novo servidor MySQL, na aba Serviços, colocando o nome do host a ser usado, usuário administrador e senha, conforme a figura abaixo:

Depois deste processo, teremos um novo item Servidor MySQL, contendo os bancos de dados atuais armazenados no MySQL, conforme a figura abaixo. Clique com o botão da direita e escolha conectar, para iniciar o serviço.


Muito bem! Clique novamente com o botão direito no Servidor MySQL e crie um novo banco de dados. Depois disso, clique novamente com a direita e se conecte a esse novo banco de dados. Um novo item será adicionado:


Agora vá na pasta Tabelas e escolha a opção Executar Comando. Outra alternativa é criar diretamente a Tabela, selecionando a opção:

Agora, vamos escrever um pequeno comando de criação de tabela, para assimilar a sintaxe dos comandos do MySQL. Poderíamos executar diretamente do Prompt de Comando, se quiséssemos. Poderíamos também usar a opção "Criar Tabela" e usar a interface gráfica do MySQL, mas vamos usar o meio-termo desta vez:


O comando basicamente cria três variáveis: login e senha, strings de tamanho 35, e uma variável n inteira, que por ser primary key, serve como uma coluna base para identificar os demais parâmetros, ou seja, é uma espécie de index que serve como referência. Já auto_increment impede a repetição em tabelas, criando um novo campo para cada item adicionado.

Note que eu já executei o comando e que novos itens foram adicionados ao nosso banco de dados, exibidos na aba Serviços, em nosso banco de dados. Clique em exibir dados, no item da Tabela. Será possível agora visualizar a tabela e adicionar a ela novos elementos, como mostrado na imagem abaixo:


Adicionando alguns elementos, teremos o seguinte resultado:


Agora já temos a ferramenta básica do MySQL implementada e pronta para o uso, para a adição de informações e elementos (o uso propriamente dito do banco de dados)? NÃO. Ainda faltam mais alguns procedimentos. Confesso que achei o procedimento um tanto penoso, mas vamos em frente.

2. Inclusão do Driver

Para acessar um banco de dados por meio de Java é necessário carregar um driver específico, que nada mais é que uma classe em Java. 

Procure na aba Serviços se o Driver para o MySQL já está devidamente instalado:


Caso contrário, acesse o site do MySQL (link abaixo) e baixe o conector para Java:

Não satisfeito com isso, MySQL vai simplesmente te humilhar ao requisitar a configuração de ClassPath e adição do arquivo .jar ao projeto. Simplesmente um processo vergonhoso para uma ferramenta tão conhecida mundialmente. É tão deprimente quanto comprar um carro sem as rodas. Pior ainda é tentar executar o instalador e não conseguir. Seria como abrir o capô e ver que falta o motor ¬¬.

A paciência é a maior das virtudes. Acesse o link abaixo e baixe essa #####:

Siga o procedimento do site da Referência:

Depois de instalar o driver no Banco de Dados, vamos adicioná-lo ao projeto. Acessamos as configurações do projeto e depois vamos até a aba biblioteca:
Na aba biblioteca, adicionamos ao projeto o arquivo .jar que baixamos no site, o mysql-connector-java-x.x.x-bin.jar:



Agora basta adicionar o "include" do driver que estamos utilizando em nosso código:
Class.forName("pacote.nome-do-driver")
Class.forName("com.mysql.jdbc.Driver")

3. A conexão ao Banco de Dados

Sei que este post está um saco. Portanto, vamos escrever algum código para suavizar essa tortura sem fim. É recomendável criar uma classe própria para manipular o Banco de Dados ou mesmo para estabelecer a conexão com ele. No meu caso, criei a classe BancoDados.java:

package code;
import java.sql.*;
import javax.swing.*;

public class BancoDados 
{
    final String DRIVER = "com.mysql.jdbc.Driver";
    final String URL = "jdbc:mysql://localhost:3306/players";
    
    public void conecta(){
        try{
            Class.forName(DRIVER);
            Connection connection = DriverManager.getConnection(URL,"LOGIN","SENHA");
            JOptionPane.showMessageDialog(null, "Conexao realizada com sucesso");
            connection.close();
        }catch(SQLException erro){}
        catch(ClassNotFoundException erro){}
    }
}
Lembre de trocar o nome do banco de dados, o login e a senha para realizar a correta conexão. Não perca a fé! Acredite em si mesmo! Se você tiver feito tudo certo até aqui, um miraculoso JPanel irá informar o seu sucesso, indicando que você está próximo para finalmente começar a parte mais divertida: Gerenciar o Banco de Dados, fazer o nosso programinha : )

Se você não conseguiu realizar alguma etapa, procure ajuda na referências, nos comentários deste post ou tente entrar em contato comigo.

No próximo post entraremos no item 4 do uso do Banco de Dados. Vamos enfim utilizar o que preparamos aqui com tanto cuidado e sacrifício.

Boa noite!

Referências

Instalação do MySQL:

Instalação do Driver:

Comandos de Tabela MySQL:

segunda-feira, 25 de maio de 2015

#22) Arquivos XML em Java

Boa noite!

Hoje apresento a vocês o formato de arquivo .xml (eXtensible Markup Language), o qual é muito utilizado para o armazenamento das principais configurações de software e informações diversas, desde variáveis do programa até informações sobre os diretórios de arquivos utilizados no projeto, como por exemplo, o diretório de um arquivo de imagens.

Seu emprego se deve à sua simplicidade e universalidade (possui infraestrutura única que pode ser usada por diversas linguagens de programação). É muito usada par ao compartilhamento de informações através da internet e tem forte semelhança com a estrutura da linguagem HTML.


Exemplo de arquivo .xml.

Seu conteúdo pode ser editado como um documento de texto simples, igual ao formato .html, tornando-o ainda mais interessante. Além disso, até os comentários seguem o mesmo padrão de HTML: (<!--comentário-->).

Arquivo .xml gerado pelo projeto Bizuca.

---//---


Vamos agora trabalhar em um pequeno exemplo de como implementar uma classe geradora e leitora de arquivos .xml para gerenciar as informações de nosso programa.


--- [Estrutura da Classe] ---

Os principais métodos da classe que manipulam o arquivo .xml são os seguintes:

---> loadXML()
Método que verifica a existência do arquivo XML. Caso exista, lê o arquivo. Caso contrário, o cria usando valores default.Em Game.java também há invocações diretas para gerar o arquivo .xml, salvando alterações das configurações do jogo.

------> lerXml(); 
Faz o mapeamento das configurações descritas no arquivo .xml e as variáveis do objeto da classe Config.java. Em outras palavras, carrega as configurações do arquivo e as atribui devidamente.

------> gerarXml();
Cria um documento com as informações do programa (variáveis da classe Config.java) e chama o conversor do documento para .xml. Depois de realizada a operação, chama o método para salvar o arquivo final.

---------> converter();
A partir do documento recebido como parâmetro, faz a conversão ao formato .xml.

---------> salvarArquivo();
Salva o arquivo .xml gerado.

--- [Implementação] ---


Primeiramente, vamos importar as bibliotecas necessárias. Vamos usar as API's nativas do JDK para isso, de modo que não é necessário adicionar bibliotecas externas:

import org.w3c.dom.*;
import org.xml.sax.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;


Como queremos um objeto da classe de configurações global, vamos implementá-lo segundo as técnicas aprendidas no post passado. Logo em seguida, já podemos declarar as variáveis do arquivo de configurações, bem como definir sua inicialização padrão (caso não seja possível carregar o arquivo .xml):

public class Config 
{
    private static Config instance;
    public static Config getInstance(){
        if (instance == null) instance = new Config();
        return instance;
    }
    
    // Variáveis diversas
   
    public String title = "Web Bizuca";
    public int HTela = 1280, VTela = 720;
    
    //...

---[ loadXML() ]---

Então, agora vamos criar um método intermediário entre a classe de configurações e a classe que faz uso destas configurações, que deverá ser invocado no escopo da classe que faz uso destas configurações:

    private static File file = new File("src\\config.xml");

    public void loadXML()
    {
        try{
            if (file.exists())
                lerXml();
            else gerarXml();
        } catch(Exception e){}
    }

---[ gerarXML() ]---

Se o arquivo .xml não for encontrado, o procedimento é utilizar os valores padrão de variável atribuídos no próprio escopo da classe. A partir destes dados, é gerado um novo arquivo .xml atrave´s do método gerarXml(). O mesmo método é empregado pode ser empregado na classe de utilização para guardar alterações de modificação das configurações do programa. Esse método tem a seguinte estrutura:

[ funcionamento ]

-> Deleta o arquivo .xml, caso ele exista

-> Cria um novo documento e adiciona Elementos (Tags) a ele:
Element WindowTitle = doc.createElement("Title");

-> A cada Tag, pode ser atribuído um conteúdo interno a ele:
WindowTitle.setTextContent("Web Bizuca");

-> Cada Tag filha deve ser depois adicionada a uma Tag pai ou ao próprio documento:
subTagWindowConfig.appendChild(WindowTitle);

doc.appendChild(tagConfig);

-> Depois de criado o documento, é preciso convertê-lo ao formato .xml, que é feito usando a função converter()

-> O arquivo .xml editado é salvo

[ funcionamento ]


O código exemplo ilustrativo é mostrado abaixo:

public void gerarXml() throws Exception 
    {
        if (file.exists()) file.delete();
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.newDocument();
        
        Element tagConfig = doc.createElement("Config");
        
            Element subTagWindowConfig = doc.createElement("Config_Window");
        
                Element WindowTitle = doc.createElement("Title");
                Element WindowWidth = doc.createElement("Window_Width");
                Element WindowHeigth = doc.createElement("Window_Height");
                
                WindowTitle.setTextContent("Web Bizuca");
                WindowWidth.setTextContent(""+HTela);
                WindowHeigth.setTextContent(""+VTela);
    
                subTagWindowConfig.appendChild(WindowTitle);
                subTagWindowConfig.appendChild(WindowWidth);
                subTagWindowConfig.appendChild(WindowHeigth);
                 
            tagConfig.appendChild(subTagWindowConfig);

        doc.appendChild(tagConfig);
        
        String arquivo = converter(doc);
        salvarArquivo(arquivo);
    }

---[ converter() ]---

Basicamente, converte o documento que é recebido como parâmetro para o formato padrão de um arquivo .xml, cujo conteúdo é retornado em forma de string.

    private static String converter(Document document) throws TransformerException {
        Transformer transformer
                = TransformerFactory.newInstance().newTransformer();

        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        StreamResult result = new StreamResult(new StringWriter());
        DOMSource source = new DOMSource(document);
        transformer.transform(source, result);

        String xmlString = result.getWriter().toString();
        return xmlString;
    }

[ lerXml() ]

Abre o arquivo .xml e, a partir da identificação das Tags, faz a leitura dos dados do arquivo e atribui seus valores às variáveis da classe de configurações, como o exemplo abaixo:

 private static void lerXml() throws Exception, SAXException, TransformerException 
    {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(new InputSource(file.toString()));
        
        Element raiz = doc.getDocumentElement();
        
        NodeList endList = raiz.getElementsByTagName("Config_Window");
        Element endElement = (Element) endList.item(0);
        
        instance.title = getChildTagValue(endElement, "Title");
        instance.HTela = Integer.parseInt(getChildTagValue(endElement,"Window_Width"));
        instance.VTela = Integer.parseInt(getChildTagValue(endElement,"Window_Height"));
        
    }

[ getChildTagValue() ]

Faz a busca do valor de determinada Tag do arquivo .xml, retornando o seu valor:

       public static String getChildTagValue(Element elem, String tagName) throws Exception {
            NodeList children = elem.getElementsByTagName(tagName);
            String result = null;
            if (children == null)
                return result;
            Element child = (Element) children.item(0);
            if (child == null) 
                return result;
            result = child.getTextContent();
            return result;
        }

--- [Conclusão] ---


Fizemos neste post a geração e leitura manual das informações de um arquivo .xml, o que pode ser um processo um tanto penoso em um projeto grande. Poderíamos ter usado Arrays / ArrayLists para gravar estas informações, porém seria mais difícil saber a finalidade de cada uma das variáveis se não houver uma boa documentação sobre elas, de forma que por finalidades didáticas preferimos nomear uma a uma as variáveis de configuração.

Qualquer dúvida, acesse uma das referências ou consulte a classe Config.java do projeto Web Bizuca, disponibilizado no link abaixo:
https://github.com/ftuyama/Web-Bizuca


Referências:

http://pt.wikipedia.org/wiki/XML
http://www.mballem.com/post/manipulando-arquivo-xml-parte-i-api-nativa/

sexta-feira, 22 de maio de 2015

#21) Singleton - Objeto "global" em Java

Olá!

Hoje venho rapidamente mostrar um recurso que descobri recentemente para criar um objeto global em Java. Um objeto que possa ser declarado em uma determinada classe do programa e reaproveitado em outras classes do mesmo projeto, utilizando para isso o recurso static.  

Assim, se quisermos reutilizar um objeto A em uma classe diferente, declaramos um objeto qualquer B e fazemos o objeto B receber o conteúdo de A através do método getInstance(). Se A ainda não tiver sido declarado, simplesmente o criamos.

Observe que em momento algum criamos um novo objeto B, de modo que sua declaração funciona de modo similar ao tipo ponteiro da linguagem C.

Abaixo, temos um exemplo ilustrativo:

public class Sessao {
    // Variável estática que conterá a instancia da classe
    private static Sessao instance;

    // Construtor privado (suprime o construtor público padrão).
    private Sessao() {}

    // Método público estático de acesso único ao objeto!
    public static Sessao getInstance() {
        if (instance == null)
            instance = new Sessao();
        return instance;
    }

    /*
    Pode criar outros métodos que precise aqui, como getters e setters.
    */
}
Sessao sessao = Sessao.getInstance();
// Aqui popula os atributos da sessão que você irá utilizar

Assim, o objeto sessao receberá o conteúdo do objeto instance, da classe Sessao. Poderíamos fazer:

Sessao sessaoA = Sessao.getInstance();
Sessao sessaoB = Sessao.getInstance();
Sessao sessaoC = Sessao.getInstance();

De modo que todos os objetos sessaoA, sessaoB e sessaoC se referem na verdade ao mesmo objeto estático, como faria um ponteiro. Note que poderíamos criar um objeto de Sessao diferente simplesmente fazendo:

Sessao sessao = new Sessao();


Esse conceito parece bem bobinho, mas é uma solução genial para grandes projetos, como por exemplo o Jogo Bizuca.


Referência:
http://pt.stackoverflow.com/questions/54588/como-criar-manter-variavel-global-em-java-para-login

quarta-feira, 20 de maio de 2015

#20) Junit, uma ferramenta para testes

Olá!

Junit é uma framework com suporte à criação de testes unitários automatizados para a linguagem Java. Antes de introduzi-lo, vamos mostrar o conceito de Testes unitários e a sua relevância:

Teste unitário é uma modalidade de teste que tem como objetivo testar pequenos componentes do projeto, as suas unidades lógicas básicas.

Desta forma, há uma série de vantagens, tais como:

  • Prevenção de BUG's no projeto, indicando erros a serem corrigidos.
  • Maior confiabilidade no código, sempre colocado à prova.
  • Serve como um requisito de projeto, a ser cumprido.
  • Prevenção de grandes erros em um projeto que nunca foi testado, evitando o dispêndio de tempo para se encontrar onde se localizam os erros do software.

O ideal é implementar uma bancada de testes para cada tarefa do projeto, de modo que os testes possam ser realizados juntamente com o processo de codificação.

mpjuiiujfig02.jpg
Processo de testes unitários em um projeto.


Inicializando o Junit

Para utilizar essa ferramenta, é necessário baixar o plugin do Junit para a IDE utilizada (Netbeans / Eclipse). No caso do Netbeans, podemos baixar diretamente da própria IDE, conforme o procedimento abaixo:

Primeiro, vamos decidir para qual classe desejamos criar a bancada de testes. Em seguida, acessamos em Ferramentas a opção "Criar/ Atualizar Testes", usando o Junit.



Caso a framework não tenha sido instalada, uma janela de instalação será aberta. Depois de devidamente instalada, uma nova classe de testes terá sido criada:


Observe que o código é aberto (permitindo sua modificação) e é gerado automaticamente, economizando bastante tempo de codificação.

Na classe criada, temos um método especial específico para realizar os testes:

assertEquals(expResult, result, delta);

Em que expResult é o resultado esperado para o retorno da classe, result é o resultado obtido pelo retorno da classe e delta é a tolerância para o teste de igualdade.

O método para o teste tem a seguinte estrutura básica:

    @Test
    public void testRaiz() {
        Bhaskara B = new Bhaskara();
        float raiz = B.raiz(1,2,1);
        assertEquals(-1.0, raiz , 0.1);
        System.out.println("raiz = "+raiz);
    }

Agora basta testar o arquivo:



O resultado te informa a porcentagem de testes corretos e o tempo de execução total, resultados extremamente interessantes e úteis para um projeto, permitindo otimizar o código e corrigir eventuais bugs no mesmo:



Podemos criar múltiplas funções de teste e depois testá-las simultaneamente:

Lembrando que nossa função de extrair a raiz a partir da fórmula de Bháskara só contabiliza uma raiz, de modo que o resultado do testRaiz3() deverá ser falho:


É possível ver exatamente qual teste falhou e a linha de código que gerou aquele resultado falho, de modo a facilitar que o programador encontre o erro lógico de seu código e o corrija o quanto antes:



Métodos de teste do Junit

Temos vários métodos de teste da framework Junit. Abaixo, indicamos alguns principais:

  1. assertEquals (a, b, delta);  // Verifica a igualdade a = b com tolerância delta ( b - delta < a < b + delta).
  2. assertTrue( a>b );  assertFalse( a==0 ); // Verifica uma dada condição.
  3. public void DISABLED_teste() {...}   // Desativa um determinado teste.
  4. // Testes de pertinência para um ArrayList:

  // Nossa classe
   public void adicionarPalavra(){
        palavras.add("palavra");
    }
   // Classe de testes
    public void testAdicionarPalavra(){
        operacao.adicionarPalavra();
        assertTrue(NewClass.palavras.contain("palavras"));
    }


Um último recurso que trago como bônus é o requisito de teste usando um tempo limite de execução, indicando falha caso o teste demore um tempo superior ao desejado. Abaixo, temos a implementação deste método usando Threads, como já explicado em posts passados deste blog:

    /**
     * Test of raiz method, of class Bhaskara.
     */
    @Test
    public void testRaiz() throws InterruptedException 
    {
        Thread testThread = new Thread(){
            public void run(){
                raiz = B.raiz(1, 2, 1);
            }
        };
        
        testThread.start();
        Thread.sleep((long) 1); // Tempo desejado
        testThread.interrupt();
        
        if (testThread.isInterrupted())
            fail ("demorado demais");
        
        assertEquals(-1.0, B.raiz(1, 2, 1) , 0.1);
    }

Apenas para esclarecer as notações do Junit (especialmente do código padrão gerado assim que criamos um novo pacote de testes), vou deixar um pequeno glossário abaixo, retirado de uma das referências no final deste post:

Como demonstrado neste tutorial, um dos principais aprimoramentos no JUnit4 é o seu suporte para anotações. No JUnit 4 você agora pode usar anotações para fazer o seguinte:
  • Identificar um teste usando a anotação @Test ao invés de uma convenção de nomeação
  • Identificar os métodos setUp e tearDown com as anotações @Before e @After
  • Identificar os métodos setUp e tearDown que se aplicam à toda a classe de teste. Métodos anotados com @BeforeClass são executados somente uma vez, antes que quaisquer métodos de teste na classe sejam executados. Métodos anotados com @AfterClass também são executados somente uma vez, após todos os métodos de teste serem finalizados.
  • Identificar exceções esperadas
  • Identificar testes que deveriam ser ignorados usando a anotação @Ignore
  • Especificar um parâmetro de time-out para um teste

Assim concluímos o estudo da framework Junit, de modo que agora estamos aptos a testar futuros projetos que vamos desenvolver no futuro.


Referências:
http://www.devmedia.com.br/junit-implementando-testes-unitarios-em-java-parte-i/1432
http://pt.slideshare.net/JudsonMeloBandeira/junit-uma-ferramenta-para-testes-passo-a-passo
https://netbeans.org/kb/docs/java/junit-intro_pt_BR.html