Como carregar várias classes com o groovy (GroovyClassLoader)?

Olá pessoal.
Estou com um problema com o Groovy que não estou conseguindo resolver de jeito nenhum.
Meu caso é o seguinte. Escrevi duas classes em Groovy (as duas estão no mesmo pacote). No momento de carregar elas em uma classe Java normal, dependendo da ordem com que eu faço o parseClass() da erro ou não.
Gostaria de saber se tem alguem com bastante experiência em Groovy que possa me dar uma luz de como é que eu faço para carregar uma quantidade X de classes Groovy sem ter que me preocupar com a ordem do parseClass().

Segue abaixo o meu código:

Arquivo “/br/teste/scripts/Livro.groovy”:

package br.teste.scripts;

class Livro {
    String nome;
    String autor;
    Integer ano;

    // construtor
    Livro() {}

    // construtor
    Livro(String nome, String autor, Integer ano) {
        this.nome = nome;
        this.autor = autor;
        this.ano = ano;
    }
}

Arquivo “/br/teste/scripts/Estante.groovy”:

package br.teste.scripts;

import br.teste.scripts.Livro;

class Estante {
    ArrayList<Livro> conteudo;

    Estante() {
        conteudo = [];
    }

    void addLivro(Livro livro) {
        conteudo += livro;
    }

    void printLivros() {
        conteudo.each { println "${it.nome} | ${it.autor} | ${it.ano}"; }
    }
}

// Arquivo “/br/teste/Main.java”

package br.teste;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyObject;

public class Main {

    public static void main(String[] args) {
        try {
            String[] sources = {
                "br/teste/scripts/Livro.groovy",
                "br/teste/scripts/Estante.groovy"
            };
            // prepara um GroovyClassLoader;
            GroovyClassLoader loader = new GroovyClassLoader();
            for (String fileneme : sources) {
                GroovyCodeSource source = new GroovyCodeSource(
                        loader.getResource(fileneme));
                loader.parseClass(source);
            }
            Class objClass;
            objClass = loader.loadClass("br.teste.scripts.Livro");
            GroovyObject livro = (GroovyObject) objClass.newInstance();
            objClass = loader.loadClass("br.teste.scripts.Estante");
            GroovyObject estante = (GroovyObject) objClass.newInstance();

            livro.setProperty("nome", "Groovy in Action");
            livro.setProperty("autor", "CodeHaus");
            livro.setProperty("ano", 2007);

            estante.invokeMethod("addLivro", livro);
            estante.invokeMethod("printLivros", null);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

É um código bem simples, mas esse problema esta me atrapalhando pq a idéia é colocar scripts no BD.
Mas se eu troco a ordem dos elementos do vetor “sources”, o algoritmo para de funcionar… dá erro na linha “estante.invokeMethod(“addLivro”, livro);”.

Já procurei resposta pra esse problema em varios lugares, inclusive aqui, mas só acho exemplos simples (com uma classe)…
Se alguem puder me ajudar, fico muito grato! :smiley:

Fácil:

package br.teste.scripts;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

import java.io.File;
import java.net.URL;

public class Main {
	public static void main(String[] args) {
		try {
			GroovyClassLoader loader = new GroovyClassLoader();
			//diretório onde estão os seus arquivos groovy (não é a melhor forma de se construir uma URL)
			URL url = new URL("file://" +  new File("").getAbsolutePath() + "/src/br/teste/scripts");
			loader.addURL(url);
			
			Class objClass;
			
			objClass = loader.loadClass("br.teste.scripts.Livro");
			GroovyObject livro = (GroovyObject) objClass.newInstance();
			objClass = loader.loadClass("br.teste.scripts.Estante");
			GroovyObject estante = (GroovyObject) objClass.newInstance();
			
			livro.setProperty("nome", "Groovy in Action");
			livro.setProperty("autor", "CodeHaus");
			livro.setProperty("ano", 2007);
			
			estante.invokeMethod("addLivro", livro);
			estante.invokeMethod("printLivros", null);
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

A questão aqui é adicionar um diretório ao classloader, e deixar que ele carregue as classes na ordem correta.

:smiley:

neófito!

Tu me deu uma senhora ajuda, cara!
Só que ainda tem um probleminha, hehe… Vc usa URLs, e a minha necessidade é transportar isso pra BD.
Tipo… fazer exatamente como vc postou, mas em vez de utilizar URLs para os arquivos, trazer os scripts do BD… e é aí que a coisa pega! Ainda mais que sou meio iniciante, pq na minha cabeça eu teria que trazer o codigo dos scripts do banco e fazer parse um por um, ja que não dá pra por um select no classpath, como o de baixo:

select * from SCRIPTS
where NOME_SCRIPT like 'br.teste.scripts.%'

Esta tabela seria assim:

create table SCRIPTS (
    ID_SCRIPT            integer not null auto-increment,  // codigo do script
    NOME_SCRIPT       varchar(255) not null,                // full qualified Groovy class name
    SOURCECODE        clob(100000) not null,               // groovy source-code do script
    primary key(ID_SCRIPT)
)

Valeu pela força!

Então, a solução mais simples e rápida seria criar um “cache” local dos scripts. A sua aplicação cria um diretório, pega os scripts do banco e salva no diretório criado. Aí você cria uma URL apontando para aquele diretório e carrega os scripts normalmente. Um problema que você teria a princípio seria sincronizar o cache com o banco. Isso teria que ser tratado de alguma forma.

:smiley:

Ah, me exemplo tá errado. Na linha 14 o certo é:

URL url = new URL("file://" +  new File("").getAbsolutePath() + "/src");

:smiley:

Valeu cara!

É verdade… de repente uma cache local ajude… Vou dar uma mexida aqui.
Abração.

Aqui vai uma brincadeirinha (hello word) com a classe Compiler do groovy:

package br.teste;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import java.io.File;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.tools.Compiler;

public class MainCompile {

    public static void main(String[] args) {
        CompilerConfiguration config = new CompilerConfiguration();
        Compiler compiler = new Compiler(config);

        String code =
                "class Hello {\n" +
                "    void sayHello() {\n" +
                "        println 'hello word!';\n" +
                "    }\n" +
                "}\n";

        // o arquivo Hello.class vai ser criado no classpath da app.
        // no dir raiz pra ser mais exato...
        compiler.compile("Hello", code);

        try {
            URL url = new URL("file://" + new File("").getAbsolutePath() + "/");
            GroovyClassLoader loader = new GroovyClassLoader();
            loader.addURL(url);
            Class objClass = loader.loadClass("Hello");
            GroovyObject hello = (GroovyObject) objClass.newInstance();
            hello.invokeMethod("sayHello", null);
        } catch (Exception ex) {
            Logger.getLogger(MainCompile.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

é cara…

tava falando com o meu chefe… e eu acho que a solução por criação de cache local não seria uma boa…

acredita-se que se vá ter milhares de scripts… daí a criação de cache com certeza vai comer muito da performance, pq tem que buscar o script do banco, guardar ele em arquivo (na cache), o groovy então vai ler este arquivo, fazer o parse, fazer a instância, executar o script (que pode conter mais acessos a BD), …

Tá loko… ja não aguento mais pesquisar…
Andei dando uma olhada no forum lá da CodeHaus… mas me parece meio monótono… os tópicos têm poucas respostas (qdo têm…)

Também olhei a interface GroovyResourceLoader… Mas essa droga só tem um método que retorna uma URL… :cry:
Pelo que eu tô vendo, o groovy não é muito amigável com scripts que venham de outros locais que não arquivos… :?

Mas… além disso, me surgiu outra dúvida…
Para criar uma instância do objeto livro eu faço:

GroovyObject livro = (GroovyObject) objClass.newInstance();

Como eu faria pra criar uma instância utilizando um construtor que recebe argumentos, como o que tem na minha classe Livro???

Agradeço desde já a atenção. :slight_smile:

Não importa a quantidade de scripts. Uma implementação correta de um cache resolveria tranquilamente o problema, nem que seja um cache distribuído em memória (nossa, acho q fui meio estúpido agora). :smiley:

mas acredito que seja mais ou menos isso que eu queria fazer :slight_smile:
tipo… criar uma cache (uma lista ou array) em memória contendo os GroovyCodeSources que eu catei do banco, e “setar” essa lista como classpath.
bem… pelo menos acho eu que isso de certa forma seja uma cache (mesmo que beeeeem simples e nao distribuída)

ah! neófito…
e com relação a chamada de construtores com argumentos… por acaso vc não teria uma dica na manga? :roll:

Pra você chamar o construtor, primeiro você tem que obter uma instância da classe java.lang.reflect.Contructor. Pra isso você tem que fazer:

obj.getClass().getConstructor(Class<?>... parameterTypes) 

Aí no objeto constructor tem um método pra invocá-lo.

Dá uma olhada no javadoc da classe java.lang.Class. E veja também o tutorial de reflection da sun.

Te mandei uma MP, dá uma olhada aí.

:wink:

Bem…
Só pra compartilhar com o pessoal o código pra invocar construtor da classe feita em groovy.
Sei que pode ter mais pessoas com a mesma dúvida… :roll:
Tá bem simples…

Arquivo org/samplepj/MainClass.java

package org.samplepj;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyObject;
import java.lang.reflect.Constructor;

public class MainClass {

    public static void main(String[] args) {
        try {
            GroovyClassLoader gloader = new GroovyClassLoader();
            GroovyCodeSource gcs = new GroovyCodeSource(
                    gloader.getResource("org/samplepj/scripts/Book.groovy"));
            gloader.parseClass(gcs);

            Class clazz = gloader.loadClass("org.samplepj.scripts.Book");
            Constructor c = clazz.getConstructor(String.class, String.class, Integer.class);
            GroovyObject book = (GroovyObject) c.newInstance(
                    "Book's Name", "Book's Author", 2007);
            book.invokeMethod("printInfo", null);
        } catch (Exception ex) {
            System.out.println("Man! Something went wrong!!!");
            ex.printStackTrace();
        }
    }
}

Arquivo org/samplepj/scripts/Book.groovy

package org.samplepj.scripts;

class Book {
    String title;
    String author;
    Integer year;

    Book() {}

    Book(String title, String author, Integer year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }

    void printInfo() {
        println "${title} | ${author} | ${year}";
    }
}

Feitow!

bem…

quem estiver interessado no andamento deste tópico, criei um outro no forum do Groovy…

http://www.groovy-forum.org/viewtopic.php?t=511&start=0&postdays=0&postorder=asc&highlight=&sid=1b156b4be28dac6f33f63b5507d8f1ea

vamos ver se dá algum resultado…
a propósito… como tem andado lento o portal do GUJ ultimamente… afff maria! :?

feitow