Arquivo → July, 2008
Compre a Java Magazine deste mês e ganhe 8 video aulas minhas!
Que surpresa agradável: comprei a Java Magazine deste mês e, ao acessar o site no qual 8 video aulas são dadas como brinde aos compradores da revista, percebi que consistem nas aulas de 1 a 8 da minha série “Construindo uma aplicação do início ao fim”. Não é legal?
Filtrando com Grails
Criar filtros em Grails, assim como a maioria das tarefas, trata-se de uma tarefa incrívelmente simples se compararmos com o modo como costumamos implementá-los ao trabalharmos diretamente com Servlets. Apesar de ser possível criar inteceptadores, estes só são eficientes quando precisamos lidar com pouquíssimos controladores (um no máximo em minha opinião). Filtros, no entanto, são criados visando atender um número maior de controladores de maneira mais eficiente.
Para demonstrar a criação de um filtro, irei usar o exemplo mais clichê que vêm à minha mente: validação de usuários. Imaginemos por exemplo como funciona o Orkut: se não estivermos logados nos serviços do Google, seremos saudados infinitamente por um formulário de login, e apenas um formulário de login veremos enquanto não fornecermos um login e uma senha válidos. Só poderemos acessar nosso perfil e demais páginas do serviço se já estivermos corretamente logados no sistema.
Sendo assim, mãos à obra: como tudo no Grails, devemos seguir algumas convenções na criação de nosso filtro. A primeira delas diz respeito ao arquivo no qual definiremos nossos filtros. Deverá ser criada uma classe Groovy cujo nome siga a seguinte convenção: [o nome que você quiser]Filters, que por sua vez deverá estar implementada em um arquivo presente no diretório /grails-app/conf, cujo nome será… [o nome que você quiser]Filters.groovy.
Em nosso exemplo, iremos portanto criar um arquivo chamado AprendendoFilters.groovy. Neste arquivo, devemos incluir um bloco chamado Filters, tal como no exemplo abaixo:
class AprendendoFilters {
def Filters = {
// aqui posso definir quantos filtros quiser, tal como o exposto abaixo:
acessoBasico(controller:'*', action:'*') {
// aqui entrará minha validação
}
outroFiltroQualquer(controller:'aquele', action:'voceSabe') {
// No bloco Filters, posso definir QUANTOS filtros quiser (tenha moderação)
}
}
}
Há dois tipos de filtros: aqueles voltados a controladores e aqueles voltados a URL’s específicas. O exemplo exposto acima pertence à primeira categoria (mais abaixo falo da outra). Destrinchando nosso bloco Filters, pode-se observar que criei dois filtros distintos: o primeiro se aplica a todos os controladores e a todas as ações referentes aos mesmos. Caso meu projeto estivesse bem padronizado, e todos os meus controladores possuíssem uma action chamada facaBobagem, poderia configurá-lo tal como no código abaixo sem problemas:
acessoBasico(controller:'*', action:'facaBobagem) {
// Gerencio permissões
}
Já o segundo exemplo listado, sabe muito melhor o que quer. Aplica-se somente ao controlador aquele e deseja interceptar apenas a action voceSabe.
Caso eu quisesse implementar um filtro relativo a URL’s (eis a segunda categoria), poderia implementá-lo tal como no exemplo abaixo:
apenasURLs(uri:'/voceSabe/**') {
// implementação
}
Já no corpo do filtro, você pode definir qual o seu tipo de atuação. Há três tipos de atuação distintos:
- before – Executa o filtro antes da action em questão
- after – Executa após a execução da action. Recebe como primeiro argumento o modelo aplicado à visualização
- afterView – Executado após a view ter sido renderizada
Então, agora que sabemos as regras básicas de funcionamento de um Filtro, vamos implementar o nosso filtro de gerenciamento de acesso. Observe o código abaixo:
acessoBasico(controller:'*', action:'*') {
/*
Repare: dentro do meu bloco acessoBasico, incluo outro bloco, chamado before
*/
before = {
if (!session.usuario && !actionName.equals('login') {
redirect(controller:'entrada', action:'apresentacao')
return false
}
return true
}
}
Como pode ser visto, o filtro atua antes de qualquer action ser executada. Em seu corpo, é verificada a presença do atributo usuario na sessão e, caso o mesmo não esteja presente, e o nome da action seja diferente de login, redireciono-o para o controlador entrada acessando a action apresentacao.
Aonde uma dúvida pode surgir: o que vem a ser actionName? actionName consiste em uma das propriedades presentes em todos os filtros do Grails. Neste caso, actionName retorna o nome da ação que está sendo interceptada. Convém mencionar que propriedades presentes nas classes de controle, como session, request, response, flash, params, e outras também encontram-se presentes para os filtros.
Sendo assim, como pode ser visto, a criação de filtros em Grails é muito mais simples do que o modo com o qual estamos habituados a trabalhar quando lidamos com Servlets. Nenhum arquivo de configuração foi tocado, nossa única ação consistiu em criar um arquivo com o nome correto, no lugar correto seguindo algumas convenções. Convention over Configuration rocks!
A escolha errada equivale à destruição da biblioteca de Alexandria (e até Platão entra na história)
Neste vídeo (1991 eu acho), Steve Jobs demonstra o NextStep (que acabou virando o Mac OS X) e, após assisti-lo, fica nítido o quão atrasada é a plataforma Windows.
É incrível observar por exemplo o ambiente de desenvolvimento do NextSTEP. Vemos em 1991 um ambiente de desenvolvimento que, mesmo hoje, em 2008, ainda não temos! Traduzindo: estamos no mínimo 17 anos no atraso. Ao observar um dos meus computadores com Windows Vista, fica nítido que ele ainda é menos avançado que o NextSTEP de 20 anos atrás.
Alguns estudiosos dizem que a destruição da biblioteca de Alexandria atrasou o desenvolvimento da humanidade em algo em torno de 200 a 500 anos. Pessoalmente, acredito que a opção do mercado pela plataforma Microsoft causou efeito semelhante na computação pessoal e corporativa.
Mais chocado fico quando assisto a demonstração e Doug Engelbart (1968!) de um sistema com conceitos que, até bem pouco tempo atrás (década de 90, ou mesmo em 1982/83 com o lançamento do Apple Lisa), eram considerados novidade absoluta (mouse, copiar e colar, video conferência, redes internas, gráficos, links, etc), tal como pode ser visto no trecho que reproduzo abaixo:
Se levarmos em consideração esta demonstração, podemos perceber que estamos não 20 e poucos anos atrasados, mas no mínimo 40!
Indo um pouco mais além, a partir da minha própria experiência como desenvolvedor, percebo que, além de já estarmos atrasados devido a esta opção infeliz feita pelo mercado (infeliz do ponto de vista tecnológico, não econômico), percebo que, na minha experiência com outros desenvolvedores, infelizmente, também estamos muuuuito atrasados.
Acredito que, no nosso caso (desenvolvedores), a causa do atraso consista no que chamo de “prisioneiros da caverna”, tais como os apresentados por Platão em seu mito da caverna. Estes “prisioneiros” consistem naqueles profissionais que, mesmo hoje, optam por continuar trabalhando com as mesmas ferramentas com as quais iniciaram suas carreiras décadas atrás (Visual Basic 6 e Delphi pré 8 dominam neste cenário). É incrível: são os mesmos conceitos de 20 anos atrás aplicados em um mundo totalmente diferente.
Uma vez que estes prisioneiros tenham de conviver com novos desenvolvedores (com suas novas ferramentas), é extremamente comum que os novatos sejam hostilizados. Afinal de contas, tratam-se de uma ameaça aos prisioneiros da caverna. No caso, estes novatos poderiam ser vistos como aqueles poucos prisioneiros que conseguiram fugir da caverna, se depararam com o Sol e voltaram para contar a novidade.
O engraçado da situação é que, ao contrário do mito de Platão, um lado teme o outro. O calouro teme o veterano por este ser melhor conhecido na empresa. E o veterano teme o novato por este expor sua obsolescência. A solução deveria ser a execução de um movimento dialético, em que a tese (veterano) interagisse com o calouro (antítese) visando chegar a uma síntese. Nesta síntese, o veterano se atualizaria com as novidades do calouro e, na outra ponta, o calouro se enriqueceria com a experiência do veterano.
Infelizmente, o que ocorre costuma ser a desistência do calouro. Como no mito da caverna, ele será assassinado por aqueles que não desejam modificar seu modo de vida. E com isto a empresa continua usando conceitos de um mundo hoje inexistente em seus projetos de software, e assim se propaga a “informática” e oculta-se a engenharia por trás do desenvolvimento de sistemas.
Grails: é InnoDB que eu quero, não MyISAM!
Ao desenvolver uma aplicação feita em Grails (1.0.2) que utiliza o MySQL como banco de dados, percebi que não estavam sendo gerados os relacionamentos entre as tabelas. Investigando a estrutura das tabelas, percebi que o Grails na realidade estava criando minhas tabelas usando o formato MyISAM ao invés do InnoDB. Como tudo (ou quase) no Grails, solucionar o problema é incrívelmente simples: basta seguir o procedimento abaixo:
Edite o arquivo /grails-app/conf/DataSource.groovy e adicione a seguinte linha de comando:
dialect='org.hibernate.dialect.MySQL5InnoDBDialect'
Esta instrução irá selecionar como dialeto padrão do Hiberante o dialeto do InnoDB e, consequentemente, todas as suas tabelas serão geradas neste formato, possibilitando assim a criação dos relacionamentos entre as tabelas. Simples assim.
Criando consultas complexas no Hibernate de maneira dinâmica
Neste post pretendo expor um padrão bastante simples a ser aplicado na criação de consultas usando a API Criteria do Hibernate. A API Criteria consiste em uma ferramenta extremamente poderosa que o Hibernate nos oferece para solucionar um problema muito comum no desenvolvimento de aplicações que precisem fazer consultas a banco de dados: a maldita concatenação de strings no momento de criação de nossas consultas.
Em nosso exemplo, utilizaremos a classe Pessoa, que encontra-se descrita no código abaixo:
class Pessoa {
private String nome;
private String sobrenome;
private String endereco;
private String observacoes;
// gets e sets omitidos
}
Para buscar valores já persistidos da classe Pessoa em um banco de dados, poderíamos criar uma nova classe chamada FactoryPessoa, responsável por encapsular as consults ao banco de dados. Suponhamos que esta nossa classe possua a implementação abaixo:
import org.hibernate.*;
import org.hibernate.criterion.*;
import java.util.*;
class FactoryPessoa extends FactoryBase {
/*
Suponha que exista uma classe chamada FactoryBase, que já possua métodos básicos para
a instanciação de objetos Session
*/
public List<Pessoa> getPessoaPorNome(String valor)
{
Session sessao = null;
try
{
sessao = getSession();
Criteria busca = sessao.createCriteria(Pessoa.class);
busca.add(Expression.ilike("nome", MatchMode.ANYWHERE, valor);
return busca.list();
}
catch (Exception ex) // Exception ex apenas para fins didáticos. Não tentem isto em casa
{
return null; // não tente isto em casa também
}
}
public List<String> getPessoaPorSobreNome(String valor)
{
try {
Session sessao = getSession();
Criteria busca = sessao.createCriteria(Pessoa.class);
busca.add(Expression.ilike("sobrenome", MatchMode.ANYWHERE, valor);
return busca.list();
} catch (Exception ex) {return null;}
}
}
Um factory simples com base em dois critérios de busca simples. Mas e se quisessemos buscar por nome e sobrenome ao mesmo tempo? Uma solução poderia ser criar mais um método. E depois, se quisessemos buscar por nome, sobrenome e alguma observação? Outro método, claro! E no final de pouco tempo teríamos uma classe com inúmeros métodos, um para cada tipo de busca que viesse à nossa mente. Hmm… cheira a merda, não acha?
Uma solução para este problema consiste em um padrão que criei e venho aplicando a já algum tempo. Consiste em primeiro criar uma classe que represente uma consulta genérica, que chamo de CriterioBusca, cuja implementação pode ser similar a abaixo:
class CriterioBusca {
private int tipoBusca;
private Object valorBusca;
// gets e sets implícitos
}
O atributo tipoBusca identifica o tipo de busca que queremos fazer. Na classe responsável por fazer consultas ao banco de dados, deveremos criar uma série de constantes estáticas que definam os tipos de pesquisa que queremos fazer ao banco de dados. Sendo assim, poderíamos modificar nossa classe FactoryPessoa para se tornar semelhante à descrita abaixo:
class FactoryPessoa {
// Constante que identifica uma busca por nome
public static final int BUSCA_NOME = 1;
// Constante que identifica uma busca por sobrenome
public static final int BUSCA_SOBRENOME = 2;
// Constante que identifica uma busca por observações
public static final int BUSCA_OBSERVACOES = 3;
// e quantas outras constantes forem necessárias
}
Já o atributo valor identifica o valor que queremos utilizar em nossa pesquisa. Opta-se por Object ao invés de String ou qualquer outro tipo para que tenhamos a busca mais flexível possível ao final de nosso projeto.
Em seguida, nossa classe FactoryUsuario pode também ter sua interface externa simplificada ao extremo. Por que ter 234987234 métodos de busca quando podemos ter apenas um?
Sendo assim, podemos finalizar a implementação de FactoryPessoa do seguinte modo:
class FactoryPessoa extends FactoryBase {
(...)
// as constantes encontram-se definidas acima
public List<Pessoa> buscarPessoas(List<CriterioBusca> criterios)
{
try
{
Session sessao = getSession();
Criteria busca = sessao.createCriteria(Pessoa.class);
for (CriterioBusca criterio : criterios)
{
prepararBusca(criterio, busca);
}
return busca.list();
} catch (Exception ex) {return null;}
}
private void prepararBusca(CriterioBusca criterio, Criteria busca)
{
switch (criterio.getTipoBusca())
{
case BUSCA_NOME:
busca.add(Expression.ilike("nome", MatchMode.ANYWHERE, busca.getValor().toString());
break;
case BUSCA_SOBRENOME:
busca.add(Expression.ilike("sobrenome", MatchMode.ANYWHERE, busca.getValor().toString());
break;
// quantos outros casos de acordo com o número de constantes definidas na classe
}
}
}
O código acima foi super simplificado, no entanto já é possível ter uma noção básica da idéia por trás do padrão. Ao invés de termos uma série de métodos, um para cada tipo diferente de busca, teremos apenas um único método: e este fará qualquer tipo de combinação de busca que quisermos, basta passar o número de instancias de CriterioBusca necessárias.
No entanto, nem tudo são flores neste padrão. Há seus problemas: o maior que vejo consiste na implementação do método responsável por popular sua instância de Criteria. Quanto maior for o número de tipos de busca definidos na classe, maior será a sua complexidade. Outro problema consiste no fato de não ficar óbvio àqueles que estão tendo seu primeiro contato com o código como trabalhar com o mesmo. Afinal de contas, não há mais um método como buscarPessoasPorNome, que torna nítidida a sua utilização.
Com base no exemplo exposto neste artigo, podemos pensar em mais um refinamento em nosso padrão. E se quisermos trabalhar com operadores como maior, menor, igual ou diferente? Simples: adicionamos mais um atributo à nossa classe CriterioBusca chamada operador. O resto, deixo por conta de vocês como exercício. :)
Criando seu próprio Codec de strings
Grails nos possibilita criar codecs dinâmicos de strings de uma maneira incrívelmente simples. Um dos principais usos para estes codecs consiste na encriptação/decriptação de strings, porém, como exporei, mais do que isto, estes codecs na realidade nos possibilitam enriquecer a classe java.lang.String.
O primeiro passo a ser adotado consiste em criar a classe que representa nosso codec. Tal como tudo no Grails, devemos nos ater às convenções definidas pela plataforma. O nome da classe deverá seguir a seguinte sintaxe: [Nome do codec]Codec.groovy, e deverá ser criada dentro do diretório grails-app/utils.
Em nosso exemplo, irei recriar um codec retirado da documentação do próprio Grails, que pode ser visto logo abaixo:
import java.security.MessageDigest
import sun.misc.BASE64Encoder
import sun.misc.CharacterEncoder
class SenhaCodec {
static encode = { str ->
MessageDigest md = MessageDigest.getInstance('SHA')
md.update(str.getBytes('UTF-8'))
return (new BASE64Encoder()).encode(md.digest())
}
}
Ao escrevermos um codec, novamente devemos adotar as convenções definidas pelo Grails. Segundo estas convenções, devemos implementar pelo menos um dos enclosures estáticos encode ou decode. Ambos recebendo um único parâmetro como valor (lógicamente, uma String).
Ao iniciarmos nossa aplicação, Grails irá incluir dinâmicamente na classe java.lang.String duas funções que retornam um valor do tipo String. São eles: encodeAs[nome do seu codec] e decodeAs[nome do seu codec]. Em nosso caso, serão criados dois métodos: encodeAsSenha e decodeAsSenha.
Em nosso exemplo, implementei apenas a closure encode, isto porquê trata-se de um codificador de senhas, e não é uma escolha inteligente armazenar senhas, mesmo que encriptadas em um banco de dados. Sendo assim, armazena-se apenas o hash das mesmas.
Se quisesse portanto utilizar este codec em qualquer parte da minha aplicação (detalhe, não preciso importar classe alguma, uma vez que todos os métodos do meu codec já foram incluídos na classe java.lang.String), poderia proceder tal como no exemplo abaixo:
class Usuario {
String hashSenha
String senha
void setSenha(String valor)
{
this.senha = valor
if (valor)
this.hashSenha = valor.encodeAsSenha()
}
}
