Arquivo para a categoria ‘Java’
Criando um tipo de dados personalizado no Grails + Hibernate
Olá Pessoal,
Estou invadindo o Blog do meu grande amigo Lucas para postar uma solução que encontrei para um problema no mapeamento de dados de uma base de dados existente. Gostaria, em primeiro lugar, de agradecer o espaço cedido pelo Lucas e parabenizá-lo pelo excelente Blog.
Hoje em dia são poucos os projetos que precisamos desenvolver do zero — criar modelagem, tabelas e etc… — por isso é muito comum depararmos com padrões proprietários que muitas vezes não se “encaixam” na ferramenta de desenvolvimento. Não preciso dizer que não é uma tarefa fácil convencer os desenvolvedores a se adequarem aos novos padrões, então, se não pode com eles, una-se a eles.
Bom, vamos direto ao assunto, no meu projeto atual me deparei com um padrão que utiliza ‘S’ e ‘N’ para o mapeamento de propriedades booleanas no banco de dados Oracle. Na pesquisa que realizei encontrei várias pseudo-soluções mas a única que atendeu 100% as necessidades foi a implementação de um tipo de dados do Hibernate.
Abaixo a implementação da classe SNUserType, não tem segredo é apenas a implementação da interface org.hibernate.usertype.UserType. Salve este código no pacote persistence na pasta de src/groovy do seu projeto Grails.
package persistence;
import org.hibernate.*;
import org.hibernate.usertype.*;
import java.sql.*;
import java.util.*;
import java.io.Serializable;
public class SNUserType implements UserType {
def SQL_TYPES = [Hibernate.YES_NO.sqlType()];
public int[] sqlTypes() {
return SQL_TYPES;
}
private Class targetClass;
public void setParameterValues(Properties params) {
String targetClassName = params.getProperty("targetClass");
try {
targetClass = Class.forName(targetClassName);
} catch (ClassNotFoundException e) {
throw new HibernateException("Class " + targetClassName + " not found ", e);
}
}
public Class returnedClass() {
return targetClass;
}
public boolean isMutable() {
return false;
}
public Object deepCopy(Object value) {
return value;
}
public Serializable disassemble(Object value) {
return (Serializable) value;
}
public Object assemble(Serializable cached, Object owner) {
return cached;
}
public Object replace(Object original, Object target, Object owner) {
return original;
}
public boolean equals(Object x, Object y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
return x.equals(y);
}
public int hashCode(Object x) {
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
String value = rs.getString(names[0]);
if ("S".equals(value))
return true;
else
return false;
}
public void nullSafeSet(PreparedStatement ps, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
ps.setNull(index, Hibernate.YES_NO.sqlType());
} else {
if((Boolean)value) {
ps.setString(index, "S");
} else {
ps.setString(index, "N");
}
}
}
}
Pronto, agora tudo que você precisa fazer é utilizar o novo tipo de dados no mapeamento de suas classes de domínio.
class Pessoa {
String nome
Boolean ativo
static mapping = {
ativo type: "persistence.SNUserType"
}
}
Com isso, resolvi o problema de integração e concluímos este post.
Um abraço
Volnei
Como, e por que usar um DataSource JNDI.
Recebi uma pergunta esses dias por aqui.
Lucas,
gostaria de saber como trabalhar com arquivos .properties pra conexão com o banco de dados.
No Grails a gente nota que a conexão fica no código-fonte (DataSorce.class)…
Estou tentando descobrir como faço para ter um arquivo de propriedade com os parametros da conexão. Caso eu precise apontar para outro banco, não terei que recoompilar tudo.
production {
dataSource {
jndiName = "<nome_datasource>"
}
}
Data corrente no nome do artefato gerado com ant
Algumas pessoas costumam versionar (e manter guardado) o histórico de versões geradas pelo seu projeto. Uma coisa que não se deve esquecer, é de renomear os pacotes de modo que quando necessário encontrar um específico, seja fácil.
O que eu faço, é sempre renomear o pacote para: projeto-yyyyMMddHHmmss.[jar/war/ear] . Como ant, fica fácil fazer isso usando uma task chamada tstamp, que recupera o timestamp corrente e grava em uma variável seguindo o pattern que você escolher. Segue exemplo que uso aqui.
Armazenando valor:
<tstamp> <format property="build-datetime" pattern="yyyyMMddHHmmss"/> </tstamp>
Pronto, agora basta usar a variável build-datetime como outra qualquer,
<target name="dist" depends="compile" description="gera o jar com a distribuição">
<jar jarfile="${dist}/${project}-${build-datetime}.jar" basedir="${build}"/>
</target>
É uma boa também para guardar os arquivos em estruturas de pastas separadas por dia/mes/ano.
yyyyMMddHHmmssCriando um Transformer customizado para o Solr
Solr é um framework uma ferramenta para a construção de servidores de indexação e busca on top of índices lucene.
Possui todas as funcionalidades que existem em um sistema moderno de busca, como paginação, highlight de campos, flexão das palavras e etc. Na minha opinião, de tudo que trabalhei nos últimos anos, sem dúvida, formam o conjunto mais poderoso de frameworks.
O Solr abstrai a camada Java do Lucene e disponibiliza uma interface http para a execução da consulta, fazendo com que fique muito fácil a integração com sistemas não-java.
Um dos recursos avançados de indexação com Solr são os Transformers a serem usados juntamente com o DataImportHandler. Com eles, você pode processar o texto antes de ser indexado. (Um outro post sobre configuração básica de Solr e de DIH deve vir em breve).
Semana passada, precisei de um transformer que pudesse retirar todas as tags HTML do texto a ser indexado (malditos editores rich text em javascript). Como o projeto aqui está usando Solr 1.3 não tive a possibilidade de usar o HTMLStripTransformer que virá no Solr 1.4 (e está enroscado pra sair). Então acabei tendo que criar um semelhante para a funcionalidade.
A criação de transformers é muito simples (imho, simplista até demais, fazendo com que as vezes, o desenvolvedor se perca durante a implementação). Deve ser desenvolvida uma classe simples, que implementa um método com a seguinte assinatura:
public Map<String, Object> transformRow(Map<String, Object> aRow, Context context)
Quando disse que é simplista demais, é pelo fato de que, (imho novamente), condições restritivas como esta deveriam estar documentadas em interfaces, mas não, quando necessário, o método é chamado via reflection, tendo certeza que ele está implementado.
No meu caso, eu precisaria além de implementar o Transformer, deixar explícito no arquivo de configuração do DIH (data-config.xml) quais os campos que deveriam receber o tratamento desta tag, fiz isso adicionando a tag removeHtml no nó de cada campo (não, este xml não passa por validação).
<field name="txt" column="texto" removeHtml="true" />
Com isto, estamos deixando claro para o DIH que o valor que retornar da query na coluna texto será repassado ao Solr para indexação no field txt (definido no schema.xml) e terá processamento do meu HtmlTransformer. Ah, não podemos esquecer da declaração do transformer para a entidade:
<entity name="posts" transformer="br.com.lucastex.dih.transformer.HtmlTransformer" />
Pronto, a classe está agora carregada e pronta para ser chamada quando um campo necessitar. Agora, vamos a implementação da classse. No meu caso, ela ficou bem trivial, pois o campo em questão nunca será multivalorado (ou seja, não preciso tratar a possibilidade do argumento ser um List, mas apenas uma String.
package br.com.lucastex.dih.transformer;
import java.util.Map;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.solr.handler.dataimport.Context;
import org.apache.solr.handler.dataimport.DataImporter;
import org.apache.solr.handler.dataimport.RegexTransformer;
import org.apache.solr.handler.dataimport.Transformer;
public class HtmlTransformer extends Transformer {
public static final String TAG = "removeHtml";
public Map<String, Object> transformRow(Map<String, Object> aRow, Context context) {
for (Map<String, String> map : context.getAllEntityFields()) {
if (!Boolean.TRUE.toString().equals(map.get(TAG)))
continue;
String columnName = map.get(DataImporter.COLUMN);
String sourceColumnName = map.get(RegexTransformer.SRC_COL_NAME);
if (sourceColumnName == null)
sourceColumnName = columnName;
Object value = aRow.get(sourceColumnName);
if (value instanceof String) {
String result = stripHtml((String) value);
aRow.put(columnName, result);
}
}
return aRow;
}
private String stripHtml(String text) {
try {
String cleanText = StringEscapeUtils.unescapeHtml(text.replaceAll("\\<.*?\\>", " "));
cleanText = cleanText.trim().replaceAll("\n"," ");
cleanText = cleanText.replaceAll("\t"," ");
cleanText = cleanText.replaceAll("[\\s]+", " ");
return cleanText;
} catch (Exception e) {
//trata exception de acordo com seu contexto solr
}
return text;
}
}
Bom, o código é bem auto-explicativo. Basicamente recebe o além do contexto em execução do Solr/DIH, um Map de String e Object, que contém todos os campos e valores da linha que está sendo analisada/indexada no momento. Vale lembrar que este modelo de passagem de parâmetro é feito exatamente como descrito no Pattern BCDR.
Enfim, as linhas 18 e 19 garantem que o código só será executado para campos que tenham declarado a tag removeHtml=true como descrito anteriormente.
A linha 24 recupera o valor original do campo, ou seja, o que foi retornado pela query no banco de dados, e nas linhas 26 e 27 o resultado é tratado (através da chamada para o método stripHtml) e devolvida ao Map.
Ou seja, este é o momento onde você tem acesso a todos os campos que estão vindo do banco antes de irem ao índice. É nesta hora que você duplica seus campos, faz tratamentos, aplica padrões e templates, enfim trabalha a informação bruta original do banco de dados.
Já o método de remoção de tags HTML, não é nada além de um conjunto de regexes encadeadas com uma ajudinha do StringEscapeUtils (que saudade deste post) do Commons-lang.
Simples como
Maravilhas do Groovy: A propriedade metaClass
Uma das facilidades que o groovy também traz, é a possibilidade de adicionar métodos em nossas classes em tempo de execução através da propriedade metaClass dos objetos.
Agora mesmo, eu precisava de um recurso para criar “slugs” (essas URLs amigáveis que o WordPress cria) de titulos de artigos. Tradicionalmente, o processo é criar aquelas classes **Utils.java com todos os métodos utilitários, mas com a metaprogramação, o mais usual passa a ser adicionar o métodos nas próprias classes que geram este comportamento.
No exemplo abaixo, eu adicionei o método slug() em runtime dentro da classe String e a partir de agora, qualquer objeto da classe java.lang.String possui o método slug(), com o comportamento descrito abaixo.
String.metaClass.slug { ->
def s = delegate.toLowerCase()
s = s.replaceAll(/[^a-z0-9\s-]/, "").replaceAll(/\s+/, " ").trim()
if (s.length() > 45)
s = s.substring(0, 45).trim()
s.replaceAll(/\s/, "-")
}
Em primeiro, definimos uma varíavel interna ’s’ com o valor da própria string que está sendo usada (através da propriedade delegate), após isso, aplicamos a primeira regex de caracteres especiais, outra para substituir os espaços em excesso e cortamos a string caso ela tenha mais que 45 caracteres. Por fim, substituímos os espaços por dashes.
Você pode rodar este script neste endereço, basta clicar em “Execute script”