groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Alain Stalder <astal...@span.ch>
Subject Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode
Date Mon, 27 Feb 2017 23:37:32 GMT
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 <astalder@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)]
>>
> .
>


Mime
View raw message