I was not aware of that option, that's already better :)
... but I am not sure if it goes far enough, as follows.
I have two use cases, both involving Grengine.
The more immediate one is a webapp with a single Grengine instance based
on a set of Groovy script files and so far it is the only webapp in the
container (Tomcat) in all installations. So I would not mind if
dependencies would accumulate in the system class loader over time when
scripts are edited, stopped and started again in the webapp's GUI - with
one exception, and I am not sure how Grape handles this: Newer versions
of the same dependency. Will then both JARs become part of the classpath
(undefined behavior) or only the newest version or the one added last, etc.?
The second use case is Grengine itself. There I would want to be able to
separate the class loaders that accumulate dependencies from each other
so that several Grengine instances can be used independently in the same VM.
For Grengine, I use a fresh instance of a GroovyClassLoader for
compiling each set of sources to bytecode and - if I want things to work
with Grape at all - I set a GroovyClassLoader as the parent class loader
of the Grengine.
Since I am already wrapping the GrapeEngine in the mentioned webapp as a
workaround for GROOVY-7407, I tried the following in that GrapeEngine
wrapper, namely to apply the grab to both classloaders:
@Override
Object grab(Map args, Map... dependencies) {
synchronized(lock) {
if (args.get('calleeDepth') == null) {
args.put('calleeDepth', DEFAULT_DEPTH)
}
if (args.containsKey("classLoader") && args.get("classLoader" !=
ClassLoader.getSystemClassLoader())) {
Map args2 = new HashMap()
args2.putAll(args)
args2.put("classLoader", grengineGroovyClassLoader)
innerEngine.grab(args2, dependencies)
}
return innerEngine.grab(args, dependencies)
}
}
That seems to work (can't be 100% sure with things that are not exactly
reproducible), and could well be good enough for both use cases: Apply
in the webapp and document it as a general workaround in the Grengine
User Manual, with sample code in the Grengine project, as part of unit
tests, like for the GROOVY-7407 workaround...
On 27.02.17 22:47, Paul King wrote:
Is there a reason you don't want to use:
@GrabConfig(systemClassLoader=true)
along with your @Grab?
On Mon, Feb 27, 2017 at 7:51 PM, Alain Stalder <astal...@span.ch> wrote:
Maybe someone can point me in the right direction regarding the following
issue with Grape...
Issue in code (in words further below):
--
import org.codehaus.groovy.control.CompilerConfiguration
def demoScriptText = """\
@Grab('org.apache.commons:commons-email:1.3.3')
import org.apache.commons.mail.*
try {
new SimpleEmail().send()
} catch (EmailException e) {
// expected
return true
}
return false
"""
// setup/clean target directory for script class
File targetDir = new File('demo-target')
if (!targetDir.exists()) {
assert targetDir.mkdir()
}
File classFile = new File(targetDir, 'Demo.class')
if (classFile.exists()) {
assert classFile.delete()
}
// parse+run script with first GroovyClassLoader
def config = new CompilerConfiguration()
config.setTargetDirectory(targetDir)
def loader1 = new
GroovyClassLoader(Thread.currentThread().contextClassLoader, config)
def script1 = loader1.parseClass(demoScriptText,
'Demo.groovy').newInstance()
assert script1.run()
// load compiled script class with second GroovyClassLoader
def loader2 = new GroovyClassLoader()
loader2.addClasspath(targetDir.path)
try {
def script2 = loader2.loadClass('Demo').newInstance()
assert false
} catch (NoClassDefFoundError e) {
println 'Failed as expected:'
println()
println e
println()
println e.stackTrace
}
--
Issue in words:
- Compile a script that uses Grape to load a dependency and use a class
that extends java.lang.Exception from that dependency in the script.
- Loading and running that script with the GroovyClassLoader that compiled
it, works fine.
- Then try to load the class from its class file (bytecode) with a new
GroovyClassLoader; this fails with a NoClassDefFoundError.
- (Apparently has nothing specifically to do with commons-email, same effect
with Commons httpclient and an Exception class declared there.)
The strange thing about this is that in the example above, EmailException is
not found, but SimpleEmail - which is in the same grabbed JAR - is found!
In fact, if I change the catch in the script to "catch (Exception e)", I can
load and run the script from its class file without any problems.
Question:
Any ideas what could cause this, or where to look closer or maybe how to
circumvent this by compiling the class differently?
(My concrete use case is with Grengine, where I do not compile to a class
in the file system, but to cached bytecode.)
Alain
--
Here is the output I get (Groovy 2.4.8, JDK 1.8.0_121, Mac):
Failed as expected:
java.lang.NoClassDefFoundError: org/apache/commons/mail/EmailException
[java.lang.Class.forName0(Native Method),
java.lang.Class.forName(Class.java:348),
org.codehaus.groovy.runtime.callsite.CallSiteArray$1.run(CallSiteArray.java:68),
org.codehaus.groovy.runtime.callsite.CallSiteArray$1.run(CallSiteArray.java:65),
java.security.AccessController.doPrivileged(Native Method),
org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:65),
org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:162),
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48),
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113),
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117),
demo.run(demo.groovy:37),
groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:263),
groovy.lang.GroovyShell.run(GroovyShell.java:518),
groovy.lang.GroovyShell.run(GroovyShell.java:497),
groovy.lang.GroovyShell.run(GroovyShell.java:170),
groovy.lang.GroovyShell$run$1.call(Unknown Source),
groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy:1005),
groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy),
sun.reflect.GeneratedMethodAccessor296.invoke(Unknown Source),
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43),
java.lang.reflect.Method.invoke(Method.java:498),
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93),
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325),
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294),
groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1027),
groovy.lang.Closure.call(Closure.java:414),
groovy.lang.Closure.call(Closure.java:408),
groovy.lang.Closure.run(Closure.java:495),
java.lang.Thread.run(Thread.java:745)]
.