Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
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)]

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
PS: Below is what I get when I look at the Demo.class with the "JD"
decompiler...

Is this maybe some kind of Grape concurrency issue similar to GROOVY-7407?

   https://issues.apache.org/jira/browse/GROOVY-7407

(I still get the same result when I am applying the workaround listed
there - which I wrote myself, by the way - but maybe the workaround does
not cover all possibilities?)


import groovy.grape.Grape;
import groovy.lang.Binding;
import groovy.lang.Script;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class Demo extends Script
{
   public Demo()
   {
     Demo this;
     CallSite[] arrayOfCallSite = $getCallSiteArray();
   }

   public Demo(Binding context)
   {
     super(context);
   }

   public static void main(String[] args)
   {
     CallSite[] arrayOfCallSite = $getCallSiteArray();
     arrayOfCallSite[0].call(InvokerHelper.class, Demo.class, args);
   }

   public Object run()
   {
     CallSite[] arrayOfCallSite = $getCallSiteArray();
     try { try {
arrayOfCallSite[1].call(arrayOfCallSite[2].callConstructor(SimpleEmail.class));
       } catch (EmailException e)
       {
         return Boolean.valueOf(true);
       } } finally {  }

     return Boolean.valueOf(false); return null;
   }

   static
   {
     $getCallSiteArray()[3].callStatic(Grape.class,
ScriptBytecodeAdapter.createMap(new Object[0]),
ScriptBytecodeAdapter.createMap(new Object[] { "group",
"org.apache.commons", "module", "commons-email", "version", "1.3.3" }));
   }
}


On 27.02.17 10:51, Alain Stalder 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)]
>
> .
>

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
Tonight I got this to load the EmailException class a few times without
any changes to the source - so it must be a race condition.

I guess the static initializer at the bottom is a call to
Grape#grab(Map<String, Object> dependency) with the effect of adding the
commons-email JAR to the classpath.

And I guess the race condition is with the VM trying to load classes in
the script in parallel, including EmailException.class which is only
available after the commons-email JAR has been added to the classpath...

(For additional verification, instead of calling newInstance(), I added
an empty static init() method in the demoScriptText and called it in
order to run static initializers in the script, and again
NoClassDefFoundError.)

Is this known behavior? Is there a reported issue for it? Any ideas for
a workaround?

As a "stupid workaround", I fed the following script text to loader2
before loading the script from the class file, and that worked:

def demoStupidWorkaroundText = """\
@Grab('org.apache.commons:commons-email:1.3.3')
import org.apache.commons.mail.*
"""

I guess if I had a defined set of scripts and remembered all all grabs
during compilation, I could feed the GroovyClassLoader before loading
the classes from bytecode, but this would be quite ugly...

Without previous initialization of the GroovyClassLoader, I guess any
grabbed class that is used in a Groovy source would have to be wrapped
during compilation into something that loads it dynamically by name,
which sounds very hairy in practice...

Any ideas?

Alain

On 27.02.17 13:06, Alain Stalder wrote:

> PS: Below is what I get when I look at the Demo.class with the "JD"
> decompiler...
>
> Is this maybe some kind of Grape concurrency issue similar to
> GROOVY-7407?
>
>   https://issues.apache.org/jira/browse/GROOVY-7407
>
> (I still get the same result when I am applying the workaround listed
> there - which I wrote myself, by the way - but maybe the workaround
> does not cover all possibilities?)
>
>
> import groovy.grape.Grape;
> import groovy.lang.Binding;
> import groovy.lang.Script;
> import org.apache.commons.mail.EmailException;
> import org.apache.commons.mail.SimpleEmail;
> import org.codehaus.groovy.runtime.InvokerHelper;
> import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
> import org.codehaus.groovy.runtime.callsite.CallSite;
>
> public class Demo extends Script
> {
>   public Demo()
>   {
>     Demo this;
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>   }
>
>   public Demo(Binding context)
>   {
>     super(context);
>   }
>
>   public static void main(String[] args)
>   {
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>     arrayOfCallSite[0].call(InvokerHelper.class, Demo.class, args);
>   }
>
>   public Object run()
>   {
>     CallSite[] arrayOfCallSite = $getCallSiteArray();
>     try { try {
> arrayOfCallSite[1].call(arrayOfCallSite[2].callConstructor(SimpleEmail.class));
>       } catch (EmailException e)
>       {
>         return Boolean.valueOf(true);
>       } } finally {  }
>
>     return Boolean.valueOf(false); return null;
>   }
>
>   static
>   {
>     $getCallSiteArray()[3].callStatic(Grape.class,
> ScriptBytecodeAdapter.createMap(new Object[0]),
> ScriptBytecodeAdapter.createMap(new Object[] { "group",
> "org.apache.commons", "module", "commons-email", "version", "1.3.3" }));
>   }
> }
>
>
> On 27.02.17 10:51, Alain Stalder 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)]
>>
>> .
>>
>
> .
>

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

paulk_asert
In reply to this post by Alain Stalder
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 <[hidden email]> 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)]
>
Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
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 <[hidden email]> 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)]
>>
> .
>

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
Correction:

     if (args.containsKey("classLoader") && args.get("classLoader") !=
ClassLoader.getSystemClassLoader()) {


not:
>     if (args.containsKey("classLoader") && args.get("classLoader" !=
> ClassLoader.getSystemClassLoader())) {

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
In reply to this post by Alain Stalder
Paul King wrote:

 > Is there a reason you don't want to use:
 >
 > @GrabConfig(systemClassLoader=true)
 >
 > along with your @Grab?

Actually, it looks like I found a reason that really makes this
no option: It apparently does not work in a web container, tried
in a Tomcat (8.0) and an embedded Jetty.

I get a "java.lang.RuntimeException: No suitable ClassLoader found for
grab".

The stack trace for Tomcat is further below, the exception occurs when
parsing the script, the webapp is written in Groovy and calls the test
script of my first post in this thread (plus
@GrabConfig(systemClassLoader=true)).

Not important to me to have this working, since I chose a different,
more generic/flexible approach anyway.

Alain


java.lang.RuntimeException: No suitable ClassLoader found for grab
     at
org.codehaus.groovy.vmplugin.v7.IndyInterface.selectMethod(IndyInterface.java:232)
     at groovy.grape.GrapeIvy.chooseClassLoader(GrapeIvy.groovy:182)
     at
org.codehaus.groovy.vmplugin.v7.IndyInterface.selectMethod(IndyInterface.java:232)
     at groovy.grape.GrapeIvy.grab(GrapeIvy.groovy:249)
     at groovy.grape.Grape.grab(Grape.java:167)
     at
groovy.grape.GrabAnnotationTransformation.visit(GrabAnnotationTransformation.java:378)
     at
org.codehaus.groovy.transform.ASTTransformationVisitor$3.call(ASTTransformationVisitor.java:321)
     at
org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:943)
     at
org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:605)
     at
org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:581)
     at
org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:558)
     at
groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
     at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
     at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:254)
     at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:211)
     at groovy.lang.GroovyClassLoader$parseClass.call(Unknown Source)
     at
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
     at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
     at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
     at net.jexler.war.Test.run(Test.groovy:34)
     at
net.jexler.war.JexlerContextListener.contextInitialized(JexlerContextListener.groovy:72)
     at
org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4853)
     at
org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5314)
     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
     at
org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:753)
     at
org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:729)
     at
org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
     at
org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1092)
     at
org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1834)
     at
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
     at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
     at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
     at java.lang.Thread.run(Thread.java:745)

Reply | Threaded
Open this post in threaded view
|

Re: Grape and loading classes that extend java.lang.Exception from compiled Groovy bytecode

Alain Stalder
In reply to this post by Alain Stalder
For completeness, I have created a GROOVY issue for this, just to have
it reported in case somebody else stumbles into this behavior:

https://issues.apache.org/jira/browse/GROOVY-8108