Created
May 25, 2015 00:57
-
-
Save geraldodev/31095f87f1e3ca53c5c0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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