You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@groovy.apache.org by Marc Paquette <ma...@mac.com> on 2015/11/14 16:31:45 UTC

Reloading of script with GroovyClassLoader

Hello all,

I am using `GroovyClassLoader` to execute groovy scripts at runtime and I want modifications to the script text (stored on the filesystem) to be picked up on subsequent execution of the script but I don't want the script to be re-compiled each time.  From my understanding, creating a `GroovyClassLoader` with a `CompilerConfiguration` where `recompileGroovySource` is set to true should give this outcome, but it does not seems to work the way I am using it.

I expect the following script to pass, but the second assert fails, anybody can tell me what I am doing wrong (or why my assumptions on how this is supposed to work are false) ?


----
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.runtime.InvokerHelper
class MyGcl {

    public static void main(String[] args) {
        CompilerConfiguration config
        config = new CompilerConfiguration()
        config.setRecompileGroovySource(true)
        GroovyClassLoader gcl = new GroovyClassLoader(getClassLoader(), config)
        Binding binding = new Binding()

        File fooFile = new File("foo.groovy")
        fooFile.text = '''
alpha='abc'
num='123'
alpha
'''
        def result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
        assert 'abc' == result

        fooFile.text = fooFile.text + '''
num
'''
        result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
        assert '123' == result
    }
}
----

Thanks in advance,
Marc


Re: Reloading of script with GroovyClassLoader

Posted by Marc Paquette <ma...@mac.com>.
Answering my own question in case it helps others.

My ultimate goal is to be able to execute DSL scripts at run-time from the filesystem, supporting reloading, import customizers and a base script class.

First, `GroovyShell` does not do caching, so it always re-parse the scripts.  `GroovyClassLoader` supports reloading, but with `loadClass` methods, not `parseClass` (found it by inspecting the source code).  But `loadClass` is for classes, not for scripts (it is possible to use it with scripts, but much more complicated).  `GroovyScriptEngine` is more suited to doing this with scripts.  To support reloading, its `CompilerConfiguration` must have `recompileGroovySource` set to true.  Since it has no constructor taking a compiler configuration but exposes the `config` through getters/setters, one can get reloading by setting the compiler configuration after instance creation of `GroovyScriptEngine`.  However, when a script is actually executed, the compiler configuration in effect is not inherited from our instance of `GroovyScriptEngine`, thus customization of the `scriptBaseClass` or import customizers added to the `compilationCustomizers` are not available for the script.

The solution, as found while looking at `groovy.util.GroovyScriptEngineReloadingTest#testCompilerConfigurationInheritance` in groovy sources, is to first create a `GroovyClassLoader` with our `CompileConfiguration` properly setup and use that as the parent class loader when instantiating `GroovyScriptEngine`.  This way, the customized compiler configuration is used when checking if a script should be reloaded and when running scripts.

Here is a script showing all of this.  Note the `Thread.sleep(1000)` statements : on my system, file modification time has a granularity of a second, so each change to the script file needs to be at least one second after the last one or the previous compiled version of the script will be used.

----
import org.codehaus.groovy.control.CompilerConfiguration

class MyGse {

    static abstract class MyBaseScript extends Script {
        String hello() { return 'Je suis Paris' }
    }

    public static void main(String[] args) {

        CompilerConfiguration config = new CompilerConfiguration()
        config.setRecompileGroovySource(true)
        config.scriptBaseClass = MyBaseScript.name
        GroovyScriptEngine gse = new GroovyScriptEngine(".", new GroovyClassLoader(getClassLoader(), config))
        Binding binding = new Binding(alpha:'abc', num:'123')
        File fooFile = new File("foo.groovy")

        fooFile.text = 'alpha'
        def result = gse.run(fooFile.name, binding)
        assert 'abc' == result

        Thread.sleep(1000)
        fooFile.text = 'num'
        result = gse.run(fooFile.name, binding)
        assert '123' == result

        Thread.sleep(1000)
        fooFile.text = 'hello()'
        assert 'Je suis Paris' == gse.run(fooFile.name, binding)
        Thread.sleep(1000)
    }
}
----

Drop this in a file named 'MyGse.groovy', cd to its parent directory and run it with `groovy MyGse.groovy`.
  
Hope this helps someone,
Marc


> Le 2015-11-14 à 10:31, Marc Paquette <ma...@mac.com> a écrit :
> 
> Hello all,
> 
> I am using `GroovyClassLoader` to execute groovy scripts at runtime and I want modifications to the script text (stored on the filesystem) to be picked up on subsequent execution of the script but I don't want the script to be re-compiled each time.  From my understanding, creating a `GroovyClassLoader` with a `CompilerConfiguration` where `recompileGroovySource` is set to true should give this outcome, but it does not seems to work the way I am using it.
> 
> I expect the following script to pass, but the second assert fails, anybody can tell me what I am doing wrong (or why my assumptions on how this is supposed to work are false) ?
> 
> 
> ----
> import org.codehaus.groovy.control.CompilerConfiguration
> import org.codehaus.groovy.runtime.InvokerHelper
> class MyGcl {
> 
>    public static void main(String[] args) {
>        CompilerConfiguration config
>        config = new CompilerConfiguration()
>        config.setRecompileGroovySource(true)
>        GroovyClassLoader gcl = new GroovyClassLoader(getClassLoader(), config)
>        Binding binding = new Binding()
> 
>        File fooFile = new File("foo.groovy")
>        fooFile.text = '''
> alpha='abc'
> num='123'
> alpha
> '''
>        def result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
>        assert 'abc' == result
> 
>        fooFile.text = fooFile.text + '''
> num
> '''
>        result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
>        assert '123' == result
>    }
> }
> ----
> 
> Thanks in advance,
> Marc
>