Skip to content

Instantly share code, notes, and snippets.

@geraldodev
Created May 25, 2015 00:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geraldodev/31095f87f1e3ca53c5c0 to your computer and use it in GitHub Desktop.
Save geraldodev/31095f87f1e3ca53c5c0 to your computer and use it in GitHub Desktop.
# encoding: utf-8
require "rubygems"
require "jdbc/postgres"
require "java"
require "metadata"
require "fileutils"
require "stringio"
require "tree"
require "builder"
require "pathname"
#Java::java.sql.Types
$CLASSPATH << 'libsqltogwt/jsqlparser.jar'
$CLASSPATH << 'libsqltogwt/classes'
java_import 'java.sql.Types'
java_import 'java.util.Date'
java_import 'java.math.BigDecimal'
java_import 'java.io.StringReader'
java_import 'net.sf.jsqlparser.parser.CCJSqlParserManager'
java_import 'net.sf.jsqlparser.statement.select.Select'
java_import 'net.sf.jsqlparser.schema.Table'
java_import 'sqltogwt.ColetaNaExpressao'
REG_EXP_JAVA_LANG = /^java\.lang.+/
RENOMEIA_RESERVADAS_JAVA = {'public' => 'publik' }
class Diretorios
attr_reader :diretorio,
:pacote_base_servidor
def initialize(diretorio)
unless File.directory?(diretorio)
raise "Diretório inválido #{diretorio}"
end
@diretorio = diretorio
end
def pacote_base_servidor
'app.server.sqltogwt'
end
def pacote_shared
'app.shared.sqltogwt'
end
def pacote_query(nome_classe)
"#{pacote_base_servidor}.queries.#{nome_classe.downcase}"
end
def pacote_entidades(schema=nil)
schema = '.' + schema if schema
"#{pacote_base_servidor}.entidades#{schema}".nome_seguro_pro_java
end
def dir_pacote_query(nome_classe)
dir_relativo(pacote_query(nome_classe).package_to_dir)
end
def dir_pacote_base_servidor
dir_relativo(pacote_base_servidor.package_to_dir)
end
def dir_entidades(schema=nil)
dir_relativo(pacote_entidades(schema).package_to_dir)
end
def dir_pacote_shared
dir_relativo(pacote_shared.package_to_dir)
end
def dir_relativo(dir)
@diretorio + '/' + dir
end
end
class SqlToGwt
attr :database
attr :diretorios
def initialize(jdbc_url, usuario, senha, diretorio=nil)
Java::org.postgresql.Driver
userurl = jdbc_url
@conexao = java.sql.DriverManager.get_connection(userurl, usuario, senha)
# tenta achar o diretório src do java se este não foi passado como parametro
unless diretorio
p = Pathname.new(File.dirname(__FILE__)) # pega o path do script atual
while p.to_s != p.parent.to_s # vai subindo de path em path
tmp = File.join(p.to_s, 'src') # concatenando o src
diretorio = tmp if File.exists?(tmp) && File.directory?(tmp) # se for dir válido inicializa diretório
p = p.parent
end
raise "Não foi possível achar o diretório src que é a base dos fontes java" unless diretorio
end
raise "É necessário o diretório base para gravar os fontes gerados" unless diretorio
@diretorios = Diretorios.new(diretorio)
@database = Metadata::Database.new(@conexao, @diretorios)
@debug = false
end
def mostra_metadata(qry)
cmd = @conexao.create_statement
begin
result_set = cmd.execute_query(qry)
metadata = result_set.get_meta_data
nr_colunas = metadata.get_column_count
n = 1
while (n <= nr_colunas)
puts "#{n} #{metadata.get_catalog_name(n)} #{metadata.get_schema_name(n)} #{metadata.get_table_name(n)}name: #{metadata.get_column_name(n)} type: #{metadata.get_column_type(n)} type_name: #{metadata.get_column_type_name(n)}"
n +=1
end
ensure
cmd.close
end
end
def renomeia_palavras_reservadas(palavra)
end
def gera_entidade(table)
return if table.entidade_gerada?
return_this = "\t\t\treturn this;\n\t\t\}\n"
src = StringIO.new
src << "package #{table.pacote}; \n\n"
imports_dic = {}
# computa imports fixos
['app.server.entidades.EntidadeIdLong',
'lombok.Data',
'lombok.EqualsAndHashCode',
'app.server.sqltogwt.GeneratedCriteria',
'java.util.List'].each {|classe| guarda_import(classe, imports_dic)}
# computa imports dos campos
table.fields.each do |campo|
guarda_import(Metadata.jdbc_type_to_java(campo.column_type,
campo.scale,
campo.precision),
imports_dic)
end
# escreve no stream
imports_dic.each_value do |classe|
src << "import #{classe};\n" unless REG_EXP_JAVA_LANG.match(classe)
end
src << "\n@Data\n"
src << "@EqualsAndHashCode(callSuper=true)\n"
src << "public class #{table.nome_classe_java} extends EntidadeIdLong { \n"
table.fields.each do |campo|
tipo_java = guarda_import(Metadata.jdbc_type_to_java(campo.column_type,
campo.scale,
campo.precision),
{}) # na verdade nao quero guardar por isso passo dic vazio
if campo.column_name != 'id' && campo.column_name != 'version'
src << "\tprivate #{tipo_java} #{campo.column_name.camelize(false)};\n"
end
end
src << "\n\tpublic static class Criteria extends GeneratedCriteria {\n"
src << "\t\tpublic Criteria(String alias) {\n"
src << "\t\t\tsuper(alias);\n";
src << "\t\t}\n"
table.fields.each do |campo|
tipo_java = guarda_import(Metadata.jdbc_type_to_java(campo.column_type,
campo.scale,
campo.precision),
{}) # na verdade nao quero guardar por isso passo dic vazio
src << "\n"
{'IsNull' =>'is null',
'IsNotNull' =>'is not null'}.each_pair do |nome, operador|
src << "\t\tpublic Criteria #{campo.column_name.camelize(false)}#{nome}() {\n"
src << "\t\t\taddCriterion(alias + \".#{campo.column_name} #{operador}\");\n"
src << return_this
end
{"EqualTo" => "=",
"NotEqualTo" => "<>",
"GreaterThan" => ">",
"GreaterThanOrEqualTo" => ">=",
"LessThan" => "<",
"LessThanOrEqualTo" => "<="}.each_pair do |nome, operador|
src << "\t\tpublic Criteria #{campo.column_name.camelize(false)}#{nome}(#{tipo_java} value) {\n"
src << "\t\t\taddCriterion(alias + \".#{campo.column_name} #{operador}\", value, \"#{campo.column_name.camelize(false)}\");\n"
src << return_this
end
{"In" => "in",
"NotIn" => "not in"}.each_pair do |nome, operador|
src << "\t\tpublic Criteria #{campo.column_name.camelize(false)}#{nome}(List<#{tipo_java}> value) {\n"
src << "\t\t\taddCriterion(alias + \".#{campo.column_name} #{operador}\", value, \"#{campo.column_name.camelize(false)}\");\n"
src << return_this
end
{"Between" => "between",
"NotBetween" => "not between"}.each_pair do |nome, operador|
src << "\t\tpublic Criteria #{campo.column_name.camelize(false)}#{nome}(#{tipo_java} value1, #{tipo_java} value2) {\n"
src << "\t\t\taddCriterion(alias + \".#{campo.column_name} #{operador}\", value1, value2, \"#{campo.column_name.camelize(false)}\");\n"
src << return_this
end
end
src << "\t}\n"
src << "} \n"
FileUtils.mkdir_p(table.dir_entidade) if ! File.exists?(table.dir_entidade)
sobrescreve_se_necessario(table.src_file_name,src)
table.entidade_gerada = true
end
def gera_corpo_da_classe(noh)
if noh.children.empty? && noh.content.expressions.empty?
noh.content.corpo_da_classe = nil
return
end
src = StringIO.new
noh.content.expressions.each do |expression|
src << "\tprivate #{expression.nome_classe_java} #{expression.identificador_java(false)}; \n"
end
noh.children do |filho|
# se o campo que estamos declarando tem corpo da classe
if filho.content.corpo_da_classe
# ele é um tipo complexo que vai ser declarado na outer class
# então apenas nos referimos a seu nome de classe
# que vai ser resolvido dentro da própria outer class
tipo = filho.content.nome_classe_java
else
# se não tem corpo de classe o campo deve referenciar
# as entidades padrão
tipo = filho.content.table.nome_qualificado
end
if filho.content.associacao_pai_filho == :muitos_para_um
src << "\tprivate #{tipo} #{filho.content.identificador_java(false)}; \n"
else
src << "\tprivate java.util.List<#{tipo}> #{filho.content.identificador_java(false)} = new java.util.ArrayList<#{tipo}>(); \n"
end
end
noh.content.corpo_da_classe = src
end
def gera_classe_qry(qry)
nome = qry.nome
nos = []
qry.raiz.each{|noh| nos << noh} # coloca nós no array
# para gerar corpo da classe de baixo pra cima para que o nó pai que
# vai ser gerado possa saber se o nó filho tem corpo de classe
# pois isso afeta o tipo do campo (se referencia entidade ou se referencia a inner class)
nos.reverse.each {|noh| gera_corpo_da_classe(noh)}
tabela_raiz = qry.raiz.content.table
src = StringIO.new
src << "package #{@diretorios.pacote_base_servidor}; \n\n"
src << "import lombok.Data; \n"
src << "import lombok.EqualsAndHashCode; \n\n"
src << "@Data \n"
src << "@EqualsAndHashCode(callSuper=true) \n"
src << "public class #{nome} extends #{tabela_raiz.nome_qualificado}{ \n"
src << qry.raiz.content.corpo_da_classe.string if qry.raiz.content.corpo_da_classe
src << "\n"
qry.raiz.each do |noh|
unless noh == qry.raiz
if noh.content.corpo_da_classe
src << "\t@Data\n"
src << "\t@EqualsAndHashCode(callSuper=true)\n"
src << "\tpublic static class #{noh.content.nome_classe_java} extends #{noh.content.table.nome_qualificado} {\n"
noh.content.corpo_da_classe.rewind
noh.content.corpo_da_classe.each_line {|linha| src << "\t#{linha}"}
src << "\n\t}\n"
end
end
end
src << "}"
src_dir = @diretorios.dir_pacote_base_servidor
FileUtils.mkdir_p(src_dir) if ! File.exists?(src_dir)
src_file = src_dir + '/' + nome + '.java'
sobrescreve_se_necessario(src_file, src)
end
def gera_result_map(xml, noh, nome_classe_raiz, ident=0)
noh.content.columns.each do |c|
if c.column_name == 'id'
xml.id(:property=>'id', :column=>c.whole_column_name)
else
xml.result(:property=>c.column_name.camelize(false), :column=>c.whole_column_name)
end
end
noh.content.expressions.each do |e|
xml.result(:property=>e.identificador_java(false), :column=>e.select_expression_item.alias)
end
noh.children.each do |filho|
if filho.content.corpo_da_classe
tipo = "#{nome_classe_raiz}$#{filho.content.nome_classe_java}"
else
tipo = filho.content.table.nome_qualificado
end
if filho.content.associacao_pai_filho == :um_para_muitos
xml.collection(:property => filho.content.get_alias.camelize(false),
"ofType" => tipo) do
gera_result_map(xml, filho, nome_classe_raiz, ident+2)
end
else
xml.association(:property=>filho.content.get_alias.camelize(false),
"javaType"=>tipo) do
gera_result_map(xml, filho, nome_classe_raiz, ident+2)
end
end
end
end
def gera_xml_mapper(qry)
nome = qry.nome
tabela_raiz = qry.raiz.content.table
src = StringIO.new
src << '<?xml version="1.0" encoding="UTF-8" ?>' + "\n"
src << '<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"' + "\n"
src << ' "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">' + "\n"
nome_classe_raiz = "#{@diretorios.pacote_base_servidor}.#{nome}"
nome_mapper = "#{nome}Mapper"
nome_qualificado = "#{@diretorios.pacote_base_servidor}.#{nome_mapper}"
xml = Builder::XmlMarkup.new(:indent=>2, :margin=>0)
xml.mapper(:namespace=>nome_qualificado) do
qry_edentada = qry.sql.split("\n").collect{|s| ' '*5 + s}.join("\n")
xml.select(:id=>"find#{nome}", 'resultMap'=>"#{nome}ResultMap") do
xml.text! qry_edentada
xml.text! "\n"
end
xml.text! "\n"
xml.tag!("resultMap", :id=>"#{nome}ResultMap", :type => nome_classe_raiz) do
gera_result_map(xml, qry.raiz, nome_classe_raiz)
end
end
src << xml.target!
src_dir = @diretorios.dir_pacote_base_servidor
FileUtils.mkdir_p(src_dir) if ! File.exists?(src_dir)
src_file = src_dir + '/' + nome + 'Mapper.xml'
sobrescreve_se_necessario(src_file, src)
end
def gera_corpo_do_proxy(noh, imports_dic)
src = StringIO.new
# gera properties dos tipos java normais (items)
noh.content.columns.each do |c|
field = noh.content.table.fields_by_column_name[c.column_name]
raise "Não encontrei o campo #{c.column_name}" unless field
tipo_java = Metadata.jdbc_type_to_java(field.column_type,
field.scale,
field.precision)
tipo_nao_qualificado = guarda_import(tipo_java, imports_dic)
src << "\t#{tipo_nao_qualificado} get#{c.column_name.camelize(true)}();\n"
src << "\tvoid set#{c.column_name.camelize(true)}(#{tipo_nao_qualificado} #{c.column_name.camelize(false)});\n"
end
# gera properties dos campos computados (expressions)
noh.content.expressions.each do |e|
tipo_nao_qualificado = guarda_import(e.nome_classe_java, imports_dic)
src << "\t#{tipo_nao_qualificado} get#{e.identificador_java}();\n"
end
#gera properties dos tipos compostos
noh.children.each do |filho|
if filho.content.associacao_pai_filho == :muitos_para_um
src << "\t#{filho.content.nome_classe_java}Proxy get#{filho.content.identificador_java}();\n"
src << "\tvoid set#{filho.content.identificador_java}(#{filho.content.nome_classe_java}Proxy #{filho.content.identificador_java(false)});\n"
else
guarda_import('java.util.List', imports_dic)
src << "\tList<#{filho.content.nome_classe_java}Proxy> get#{filho.content.identificador_java}();\n"
src << "\tvoid set#{filho.content.identificador_java}(List<#{filho.content.nome_classe_java}Proxy> #{filho.content.identificador_java(false)});\n"
end
end
noh.content.corpo_do_proxy = src
noh.children do |filho|
gera_corpo_do_proxy(filho, imports_dic)
end
end
def gera_proxy(qry)
nome = qry.nome
nome_proxy = "#{qry.nome}Proxy"
imports_dic = {}
guarda_import("com.google.web.bindery.requestfactory.shared.EntityProxy", imports_dic)
guarda_import("com.google.web.bindery.requestfactory.shared.ProxyFor", imports_dic)
guarda_import("#{@diretorios.pacote_base_servidor}.#{nome}", imports_dic)
guarda_import("app.server.entidades.LocatorIdLong", imports_dic)
qry.raiz.each do |noh|
gera_corpo_do_proxy(noh, imports_dic)
# gera imports pras classes que são entidades
unless noh == qry.raiz
unless noh.content.corpo_da_classe
guarda_import(noh.content.table.nome_qualificado, imports_dic)
end
end
end
src = StringIO.new
src << "package #{@diretorios.pacote_shared};\n\n"
imports_dic.values.sort.each do |import|
# gera import se não for classe de java.lang
src << "import #{import};\n" unless REG_EXP_JAVA_LANG.match(import)
end
src << "\n"
src << "@ProxyFor(value=#{nome}.class, locator=LocatorIdLong.class)\n"
src << "public interface #{nome_proxy} extends EntityProxy{\n"
src << qry.raiz.content.corpo_do_proxy.string
qry.raiz.each do |filho|
unless filho == qry.raiz
if filho.content.corpo_da_classe
proxy_for = "#{nome}.#{filho.content.nome_classe_java}"
else
proxy_for = "#{filho.content.table.nome_classe_java}"
end
src << "\n\t@ProxyFor(value=#{proxy_for}.class, locator=LocatorIdLong.class)\n"
src << "\tinterface #{filho.content.nome_classe_java}Proxy extends EntityProxy{\n"
filho.content.corpo_do_proxy.rewind
filho.content.corpo_do_proxy.each_line do |linha|
src << "\t#{linha}"
end
src << "\t}\n"
end
end
src << "}"
src_dir = @diretorios.dir_pacote_shared
FileUtils.mkdir_p(src_dir) if ! File.exists?(src_dir)
src_file = src_dir + '/' + nome_proxy + '.java'
sobrescreve_se_necessario(src_file, src)
end
def gera_mapper(qry)
nome_mapper = qry.nome + 'Mapper'
src = StringIO.new
src << "package #{@diretorios.pacote_base_servidor};\n\n"
src << "import java.util.List;\n\n"
src << "public interface #{nome_mapper} {\n"
src << "\tList<#{qry.nome}> find();\n"
src << "}\n"
src_dir = @diretorios.dir_pacote_base_servidor
FileUtils.mkdir_p(src_dir) if ! File.exists?(src_dir)
src_file = src_dir + '/' + nome_mapper + '.java'
sobrescreve_se_necessario(src_file, src)
end
def gera(sql, nome)
qry = Metadata::Query.new(sql, nome, @database)
mostra_arvore(qry.raiz)
qry.raiz.each do |noh|
gera_entidade(noh.content.table)
end
gera_classe_qry(qry)
gera_xml_mapper(qry)
gera_proxy(qry)
gera_mapper(qry)
end
def tipo_java_sem_qualificar(tipo_qualificado)
return tipo_qualificado if tipo_qualificado == nil
ponto = tipo_qualificado.rindex('.')
return tipo_qualificado unless ponto
tipo_qualificado[ponto+1,tipo_qualificado.length]
end
# pega o tipo qualificado java : java.io.File
# e guarda no imports_dic utilizando o nome da classe
# como chave então neste caso 'File' => 'java.io.File'
# retorna a string correspondente a class (File no exemplo)
def guarda_import(tipo_java_qualificado, imports_dic)
tipo_sem_qualificar = tipo_java_sem_qualificar(tipo_java_qualificado)
tipo_armazenado = imports_dic[tipo_sem_qualificar]
if tipo_armazenado && tipo_java_qualificado != tipo_armazenado
raise "Não foi possível armazenar import #{tipo_java_qualificado} pois imports_dics já tinha #{tipo_armazenado}"
end
imports_dic[tipo_sem_qualificar] = tipo_java_qualificado if ! tipo_armazenado
return tipo_sem_qualificar
end
def mostra_arvore(noh, ident=0)
puts '-' * ident + noh.content.get_alias + " associação:#{noh.content.associacao_pai_filho} chave:#{noh.content.column_fk}"
noh.children.each {|filho| mostra_arvore(filho, ident + 3)}
end
def testaparser
manager = Java::net.sf.jsqlparser.parser.CCJSqlParserManager.new
# parser retorna um Select
select = manager.parse(java.io.StringReader.new(PRODUTO_PRODUTO_CLIENTE.to_java_string)) # SQL_GUIA_E_ITENS
raiz = Tree::TreeNode.new('raiz', Metadata::TabelaQuery.new(select.select_body.from_item))
constroi_arvore(raiz, select.select_body.get_joins)
mostra_arvore(raiz)
exit
# com o select eu acesso o select_body
puts select.select_body.from_item.name
puts select.select_body.from_item.alias
puts select.select_body.from_item.schema_name
select.select_body.get_joins.each do |i|
puts i
puts i.right_item
puts i.on_expression
puts i.on_expression.expression
puts i.on_expression.expression.class
puts i.on_expression.expression.left_expression
puts i.on_expression.expression.right_expression
puts i.on_expression.expression.right_expression.whole_column_name
puts i.on_expression.expression.right_expression.class
exit
end
end
def sobrescreve_se_necessario(nome_arquivo, string_io)
sobrescrever = true
f = Java::java.io.File.new(nome_arquivo)
if f.exists
File.open(nome_arquivo, 'r') do |f|
f.rewind
string_io.rewind # posiciona StringIO no byte 0 para posterior comparacao
sobrescrever = ! FileUtils.compare_stream(string_io, f)
end
end
if sobrescrever
File.open(nome_arquivo, 'w+') {|f| f.write string_io.string}
puts "Sobrescreveu #{nome_arquivo}"
end
end
end
class String
def camelize(first_letter_in_uppercase = true)
if first_letter_in_uppercase
self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
else
self[0..0].to_s + self.camelize(true)[1..-1]
end
end
def package_to_dir
self.gsub('.','/')
end
def nome_seguro_pro_java
self.split('.').map {|s| RENOMEIA_RESERVADAS_JAVA[s] ? RENOMEIA_RESERVADAS_JAVA[s] : s}.join('.')
end
def mybatis_to_jdbc
self.gsub(REG_EXP_MY_BATIS_PREPARED_UNPREPARED,'?')
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment