Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling Groovy inside Groovy cannot use dynamically grabbed classes #7

Open
ctrueden opened this issue Nov 14, 2019 · 0 comments
Open

Comments

@ctrueden
Copy link
Member

ctrueden commented Nov 14, 2019

Consider the following Groovy script:

Jython-from-Groovy.groovy
#@ ScriptService ss

@Grab ('org.knowm.xchart:xchart:3.5.4')
import org.knowm.xchart.XChartPanel

println("Grabbed class: " + XChartPanel.class)
println("Groovy script's class loader = " + Thread.currentThread().getContextClassLoader())
println("Grabbed class's class loader = " + XChartPanel.class.getClassLoader())

script = '''
\n#@ java.lang.ClassLoader classLoader
\n#@output String msg
from java.lang import Thread
previousCL = Thread.currentThread().getContextClassLoader()
#Thread.currentThread().setContextClassLoader(classLoader)
explicitlyLoadedClass = classLoader.loadClass("org.knowm.xchart.XChartPanel")
from org.knowm.xchart import XChartPanel
implicitlyLoadedClass = XChartPanel
msg = "[INFO]\\n"
msg += " :: Inner script's class loader was = {}\\n".format(previousCL)
msg += " :: Now overridden to = {}\\n".format(classLoader)
msg += " :: Explicitly loaded = {}\\n".format(explicitlyLoadedClass)
msg += " :: Implicitly loaded = {}\\n".format(implicitlyLoadedClass)
msg += " :: same? {}\\n".format(explicitlyLoadedClass == implicitlyLoadedClass)
msg += " :: THAT'S ALL FOLKS\\n"
'''
args = ['classLoader': XChartPanel.class.getClassLoader()]
m = ss.run('.py', script, true, args).get()
println(m.getOutput("msg"))

Inside the ImageJ Script Editor, this script fails with:

[ERROR] Traceback (most recent call last):
  File ".py", line 10, in <module>
ImportError: No module named knowm

And emits output:

Started Jython-from-Groovy.groovy at Thu Nov 14 15:50:37 CST 2019
Grabbed class: class org.knowm.xchart.XChartPanel
Groovy script's class loader = java.net.URLClassLoader@780fe662
Grabbed class's class loader = groovy.lang.GroovyClassLoader@106426ee
null

But if you uncomment the Thread.currentThread().setContextClassLoader(classLoader) line, then it works:

Started Jython-from-Groovy.groovy at Thu Nov 14 15:53:47 CST 2019
Grabbed class: class org.knowm.xchart.XChartPanel
Groovy script's class loader = java.net.URLClassLoader@780fe662
Grabbed class's class loader = groovy.lang.GroovyClassLoader@4b329d0
[INFO]
 :: Inner script's class loader was = java.net.URLClassLoader@780fe662
 :: Now overridden to = groovy.lang.GroovyClassLoader@4b329d0
 :: Explicitly loaded = <type 'org.knowm.xchart.XChartPanel'>
 :: Implicitly loaded = <type 'org.knowm.xchart.XChartPanel'>
 :: same? 1
 :: THAT'S ALL FOLKS

As an aside: it seems that this hack (semi-?)persistently taints the thread's context class loader on subsequent executions, which is probably a bad thing. I do not understand why, though. Each script executes in a new thread. But somehow, the context class loader mysteriously begins as a GroovyClassLoader in subsequent executions. Sometimes? Maybe there is a race condition?


However, this same trick does not work to execute Groovy from Groovy. Consider this script:

Groovy-from-Groovy.groovy
#@ ScriptService ss

@Grab ('org.knowm.xchart:xchart:3.5.4')
import org.knowm.xchart.XChartPanel
println(XChartPanel.class)

script = '''
\n#@ java.lang.ClassLoader classLoader
\n#@output String msg
previousCL = Thread.currentThread().getContextClassLoader()
//Thread.currentThread().setContextClassLoader(classLoader)
explicitlyLoadedClass = classLoader.loadClass("org.knowm.xchart.XChartPanel")
implicitlyLoadedClass = org.knowm.xchart.XChartPanel.class
msg = "[INFO]\\n"
msg += " :: Inner script's class loader was = " + previousCL + "\\n"
msg += " :: Now overridden to = " + classLoader + "\\n"
msg += " :: Explicitly loaded = " + explicitlyLoadedClass + "\\n"
msg += " :: Implicitly loaded = " + implicitlyLoadedClass + "\\n"
msg += " :: same? " + (explicitlyLoadedClass == implicitlyLoadedClass) + "\\n"
msg += " :: THAT'S ALL FOLKS\\n"
'''
args = ['classLoader': XChartPanel.class.getClassLoader()]
m = ss.run('.groovy', script, true, args).get()
println(m.getOutput("msg"))

The above script also fails similarly to the Jython one:

[ERROR] groovy.lang.MissingPropertyException: No such property: org for class: Script18
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:53)
	at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:52)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:307)
	at Script18.run(Script18.groovy:9)

But if you uncomment the setContextClassLoader line, it still fails with the same error.

Maybe the Groovy JSR-223 engine does not use the current thread's context class loader? This theory could be verified by setting a custom one within a Groovy script (not nested Groovy-inside-Groovy, at least at first) and seeing whether you can then use fully qualified classes from that class loader. But I am out of time for now.

To conclude: the motivation for wanting to call Groovy from Groovy is to be able to execute Groovy scripts from inside a BeakerX Groovy notebook. In the BeakerX notebook we dynamically grab all of ImageJ, typically in the first cell, and there is nothing from the SciJava world on the system classpath. But then when we do something like ij.script().run('.groovy', myAwesomeScript, true) and the script wants to import SciJava classes, it doesn't work. 😢 See also this discussion on Gitter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant